Skip to content

refactor(agent-runtime): unify declarative tool approval policy - #18865

Merged
DeJeune merged 22 commits into
mainfrom
settings-builder-permission-analysis
Aug 20, 2026
Merged

DeJeune merged 22 commits into
mainfrom
settings-builder-permission-analysis

Conversation

@DeJeune

@DeJeune DeJeune commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Branch strategy

  • Active development targets main.

What this PR does

Before this PR: Claude settings construction mixed environment, MCP, lifecycle state, hooks, and permission policy; fixed approvals were maintained as parallel tool-name lists, and bypassPermissions still prompted inconsistently across runtimes.

After this PR: settings construction is split by responsibility, Claude conduct rules use one declarative evaluator, Cherry-owned MCP tools declare structured approval entries, and Full Access bypasses per-call approvals consistently across Claude, Pi, and DSH while explicit safety blocks remain enforced.

Fixes # N/A

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

The following tradeoffs were made: SDK and bridge arrays/sets remain as derived boundary formats, while the structured tool policy registry is the only maintained source; Assistant-only entries are filtered by actual MCP availability.

The following alternatives were considered: a mutable GoF Builder, a hook-per-rule directory, and attaching cross-runtime policy to the AI-SDK-only ToolEntry were rejected because they preserve the wrong ownership or synchronization boundary.

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

Breaking changes

Full Access (bypassPermissions) no longer shows per-call approval cards for knowledge-base edits, image generation, CLI installation, or Assistant settings/file tools on any Agent runtime. Users who relied on those prompts should switch the Agent to Approve for Me or Ask Before Acting; disabled tools, shared-global installation blocks, protected built-in destructive-operation blocks, and unattended responder requirements remain enforced.

Special notes for your reviewer

The seven commits intentionally separate mechanical responsibility extraction from the policy behavior change. Validation covered pnpm lint, pnpm test:lint, formatting, type checks, i18n, documentation links, all main/renderer/package tests, and the DSH bridge's independent test/build; transient local disk pressure and two unrelated timing/teardown flakes passed on rerun.

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.

Release note

action required: Full Access now bypasses per-call approvals for knowledge-base edits, image generation, CLI installation, and Assistant settings/file tools across all Agent runtimes. Switch to Approve for Me or Ask Before Acting if those operations should still prompt.

DeJeune and others added 8 commits August 18, 2026 15:23
…ity modules

Extract environment.ts (env assembly + token-budget math), mcpCatalog.ts
(server specs, tool metadata, bounded cache warm), pathContainment.ts
(workspace containment helpers), and ClaudeCodeSessionStateService (the
four session-keyed maps + MCP catalog subscription, now a lifecycle
service per naming-conventions §5.2 with onStop/onDestroy cleanup).
settingsBuilder keeps orchestration and re-exports the historical import
surface, so driver/warmup consumers and their module mocks are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
…ator

toolGuards.ts models the PreToolUse policy plane: rules declare what they
match, their effect, and — via bypassBehavior — whether bypassPermissions
lifts them, instead of each imperative hook deciding ad hoc. The evaluator
folds all matching candidates deny > ask (mirroring the SDK's parallel
severity fold) with deterministic reasons, and headless overrides apply
independently of the bypass skip. guardRules.ts transcribes the ten
policy hooks into rows; reasons are byte-identical to the hooks they will
replace. Additive only — nothing consumes the table yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
buildClaudeCodeHooks(ctx) now owns all 13 PreToolUse hooks and the
PostToolUse timing hook, moved verbatim from buildToolPermissions with an
explicit ClaudeCodeHookContext instead of closure capture. Shared name
constants come from guardRules so the table and the hooks cannot drift.
No behavior change; hook function names are preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Replace the ten imperative policy hooks with one toolGuardHook that
evaluates CLAUDE_TOOL_GUARD_RULES at fire time (live permission mode and
disabled set from the session snapshot; interaction state per turn).
canUseTool keeps its structure; its approval-required input now derives
from guardRules' canonical list.

Behavior change (approved): bypassPermissions no longer raises approval
cards for the per-call approval list (kb_manage / generate_image /
cli_install / mounted assistant tools) — it is the user's explicit
opt-out of per-call approval. Headless denials are not approvals and
survive bypass unchanged. The role asks (Assistant feedback, Support
Bash) and the workspace-escape ask stop firing under bypass, which is
net-equivalent: canUseTool's mode auto-approval already pierced those
asks before this change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Move the approvalRequiredTools check behind the bypassPermissions gate in
decideToolCall so the mode is a real opt-out of per-call approval,
matching the claude and pi runtimes. Outside bypass the list still beats
auto-approval. Delegated subagents inherit the change: under bypass an
approval-required call now runs instead of dead-ending in the
ask-degrades-to-deny path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
…missions

The guard protects the shared cross-agent environment (~/.bun,
~/.local/share/uv, …), not just the session, so it is a safety block —
denied outright, never a prompt — and bypassPermissions must not lift it.
This aligns pi with the claude and dsh runtimes.

The pi-specific Full Access warning copy is retired: bypass now means the
same thing on every runtime (approvals lifted; explicit safety blocks
still apply), so the generic card copy is accurate. Locale catalogs
synced; the 2026-08-13 pi permission-mode breaking-change entry updated
to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Comment thread src/main/ai/runtime/claudeCode/guardRules.ts Outdated
Signed-off-by: suyao <sy20010504@gmail.com>
Signed-off-by: suyao <sy20010504@gmail.com>
@claude

claude Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Code review complete. No actionable findings.

The declarative guard table architecture correctly separates policy from implementation, with explicit bypass behavior declarations. Safety blocks (disabled tools, global installs, protected built-in destructive operations, non-bypassable cross-Session delegation) remain enforced under Full Access across all runtimes. The lifecycle-managed session state service properly cleans up resources. Test coverage is comprehensive.

DeJeune and others added 8 commits August 19, 2026 05:56
The fire-time no-responder denial had no bypassPermissions exemption, so it
agreed with the guard table only because the SDK skips canUseTool under that
mode. Derive it from the tool's `bypassApproval` and the snapshot's live mode,
and reuse the same predicate for `requiresUserResponse`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
`askStrength` was never read: the snapshot's auto-allow exceptions come from the
policy registry, not the table, so a 'hard' row without a matching `required`
entry got none of the enforcement its doc comment promised. Also un-export
`resolveRealOrNearestExistingPath` (no external consumer) and log a guard
condition that throws instead of failing open silently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
The note claimed unattended turns still deny every tool needing a live
responder. Since the headless bypass exemption landed, approval-only tools
(kb_manage, image generation, CLI/skill installs, assistant tools) do run on
channel and scheduled turns; only user-authored-answer tools, config mutation
and the delegation ceiling still deny.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
…iltin flag

`ToolGuardContext.isProtectedBuiltin` put "is this one of Cherry's two built-in
agents" into the generic evaluator's context type. A plain `appliesTo.roles`
list expresses the same scoping without the concrete-agent concept, and drops
the field from the context, the hook context and settingsBuilder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
… runtime

The guard table meant to hold cross-cutting policy also held three rules that
only Cherry Assistant / Support need (builtin-destructive, assistant-feedback,
support-bash) plus their Lark/GitHub command detectors. They now live with the
Agents that declare them, and the Claude runtime appends that table to its own.

Putting them under agents/ required `ToolGuardRule` to be reachable without
agents/ importing a runtime, so hoist the tool-approval layer to
src/main/ai/toolApproval/ and move the guard language into it. That layer
already imported nothing from runtime/ and was already consumed from
agentSession/, so "runtime-neutral" was only true of its contents, not its
path. Dependencies now point one way: runtime -> agents -> toolApproval.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
…of branching on role

The built-in Agents' reach — host MCP tools, all-knowledge-base access, skill
and plugin sources, filesystem settings — was re-derived as `builtin_role ===`
branches in 13 places across three runtimes, and had already drifted: Cherry
Support got host tools on Claude but not on Pi or DSH.

Move it to one table the Agents own (`builtinAgentCapabilities`) and have the
runtimes read capabilities instead of asking which Agent this is. The Pi/DSH
divergence is preserved verbatim as an explicit `hostTools.runtimes` field
rather than silently unified; behavior is unchanged on every runtime.

Assistant tool names move to `@shared/ai/assistantTools` so the MCP server, the
approval registry and the capability table name the same tools without any new
cross-layer import — `agents/` must not depend on `mcp/` or `runtime/`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
…not an agent name

`BuiltinToolAvailability = 'always' | 'assistant'` gave a runtime-neutral
registry an enum member named after one Agent family, and forced an
`assistantMcpEnabled` boolean through nine files including the generic guard
context. An entry's availability was already implied by its `serverName`: it
applies exactly when its MCP server is mounted.

Query by the mounted server set instead. `resolveMountedMcpServers` derives that
set once from the Agent's capabilities, so the servers a session builds and the
policy derived from them cannot disagree — this also drops the special-cased
`mcp__skills__search_skills` filter, which existed because the registry claimed
the skills tools were always available while Support never mounts that server.

The capability interface collapses with it: `skillSource`, `skillDiscovery`,
`filesystemSettings` and `pluginSources` had exactly two combinations across
every role — one bit spelled four ways. They become `environment: open|sealed`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Two conflicts, both where main moved code this branch had already rewritten:

- settingsBuilder `autoAllowRuntimeNames`: kept this branch's registry query
  (`listBuiltinToolPolicies({ approval: 'auto', mountedServers })`) over main's
  `CHERRY_BUILTIN_AUTO_APPROVED_TOOL_NAMES` name lists, and took main's comment,
  which drops the reference to the now-deleted CHANNEL_SECURITY_PROMPT.
- PiRuntimeConnection.test: dropped the CHANNEL_SECURITY_PROMPT import main
  removed, kept this branch's hoisted `@main/ai/toolApproval` path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
@claude

claude Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Code review complete. No actionable findings.

The declarative guard table architecture correctly separates policy from implementation, with explicit bypass behavior declarations. Safety blocks (disabled tools, global installs, protected built-in destructive operations, non-bypassable cross-Session delegation) remain enforced under Full Access across all runtimes. The lifecycle-managed session state service properly cleans up resources. Test coverage is comprehensive.

Two conflicts:

- AgentSessionRuntimeService imports: both sides added a different line. Kept
  this branch's hoisted `../toolApproval/ToolApprovalRegistry` and main's
  widened `../types` import (ApprovalRequestedEvent).
- ProviderSpecific/__tests__/CherryInOauth.test.tsx (modify/delete): took
  main's deletion. #18895 removed this duplicate suite because it synchronized
  on the IPC call before the balance effect had run; the surviving suite in
  ProviderSettings/__tests__ now waits on the rendered balance and covers the
  top-up refresh. That supersedes 26758a0, which was patching the same flake
  in the copy main deleted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
if (!rule.match.when) return {}
try {
return await rule.match.when(ctx)
} catch (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Observation: Fail-Open on Condition Exceptions

This catch block treats exceptions from guard conditions as "no match", which could bypass security rules if a condition throws unexpectedly.

Failure scenario:

  1. A malformed input, filesystem error, or bug in a condition detector (e.g., detectGlobalInstall, isPathWithinAllowedRoots) causes an exception
  2. The rule is skipped, potentially allowing a deny rule like global-install or workspace-escape to be bypassed
  3. The tool proceeds with the runtime's default permission-mode semantics instead of the guard's intended protection

Impact:

  • Deny rules (global-install, builtin-destructive) could be bypassed if their condition functions throw
  • Ask rules could be skipped, allowing tools without prompts

Suggested consideration:

  • For deny rules, consider whether failing closed (returning {} to match) would be safer
  • At minimum, consider logging at error level rather than warn to ensure visibility of potential bypasses
Suggested change
} catch (error) {
} catch (error) {
logger.error('Guard condition threw — treating as no match', { ruleId: rule.id, toolName: ctx.toolName, error })
return null
}

Note: The current conditions appear to internalize their own failure postures (e.g., treating unresolvable paths as outside the workspace), but this fail-open behavior is still worth explicit consideration for defense in depth.

}
},
{
// Installing third-party skill code needs a responder — except under bypassPermissions, the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Clarity: Meaningless bypassBehavior

This rule has bypassBehavior: 'skipInteractiveEffect' but no effect field, making the bypassBehavior declaration meaningless.

Analysis:

  • bypassBehavior: 'skipInteractiveEffect' only affects rules with an interactive effect (deny/ask)
  • This rule has no effect field—it only has a headless override
  • The skipHeadlessDenyInBypass: true on line 128 already handles the bypass case for the headless denial

Behavior verification (from tests):

  • Interactive turn: no decision (undefined) — canUseTool handles it elsewhere
  • Headless turn, no bypass: deny
  • Headless turn, with bypass: undefined (allowed via skipHeadlessDenyInBypass)

The rule works correctly, but bypassBehavior is dead code here.

Suggested fix:
Either remove bypassBehavior or document this pattern if it's intentional for consistency with other rules.

Suggested change
// Installing third-party skill code needs a responder — except under bypassPermissions, the
{
// Installing third-party skill code needs a responder — except under bypassPermissions, the
// user's explicit opt-in to unattended installation.
id: 'skill-install',
bypassBehavior: 'enforce', // No interactive effect to skip; headless deny is explicitly lifted below
match: { tool: 'mcp__skills__install_skill' },
headless: {
predicate: 'turn-headless',
reason:
'This channel or scheduled turn cannot approve a skill installation. Use bypassPermissions for unattended installation, or install it from an interactive turn.',
skipHeadlessDenyInBypass: true
}
},

Or simply remove bypassBehavior since it has no effect on headless-only rules.

@vaayne vaayne left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was translated automatically.

Found 1 blocking cross-runtime security contract gap and 1 shared-layer architecture boundary issue. The current non-bypassable built-in Agent guard only integrates with Claude, while Pi and DSH's Full Access can still bypass similar restrictions, which is inconsistent with the PR documentation and the product contract in #18898. Suggest REQUEST_CHANGES: first integrate the hard limits into the evaluator shared by all three runtimes, and move tool names exclusively for Main use out of src/shared/.


Original Content

发现 1 个阻断性跨运行时安全契约缺口和 1 个 shared-layer 架构边界问题。当前不可绕过的 built-in Agent guard 只接入 Claude,Pi 与 DSH 的 Full Access 仍可绕过同类限制,与 PR 文档及 #18898 的产品契约不一致。建议 REQUEST_CHANGES:先将硬限制接入三个 runtime 共用的 evaluator,并将仅供 Main 使用的工具名称移出 src/shared/。

Comment thread src/main/ai/agents/builtin/builtinAgentGuardRules.ts Outdated
Comment thread src/shared/ai/assistantTools.ts Outdated
DeJeune and others added 2 commits August 19, 2026 16:43
… placement

- Raise the fail-open guard-condition catch from warn to error (@claude): it is
  the only signal that a deny rule was skipped by a throwing detector.
- Make `bypassBehavior` part of the effect-bearing arms of `ToolGuardRule`
  (@claude). It was dead on the three rules whose only decision is a headless
  denial, and the compiler now rejects declaring it there. The suggested
  `bypassBehavior: 'enforce'` was not viable — `validateToolGuardRules` rejects
  `enforce` combined with `skipHeadlessDenyInBypass`.
- Move assistant tool names out of `@shared` (@vaayne): all consumers are in
  `src/main`, so the shared layer's cross-process rule did not admit them. They
  live in the approval layer, which already owns every Cherry tool's canonical
  identity, as an import-free leaf so `mcp/servers/assistant.ts` cannot close a
  cycle back through the MCP layer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
… runtime

Review of #18865 established that three of the limits the note claimed "on every
runtime" are enforced by the Claude guard table alone: built-in Agent
destructive operations, unattended agent-configuration changes, and the
Assistant/Support external-submission commands.

Verified rather than assumed. Pi's authorizer runs disabled -> global-install ->
`if (bypass) return`, and only reaches `detectDestructiveCommand` inside
`requiresApproval` under `mode === 'auto'` — two steps after Full Access has
returned. dsh has no equivalent guard, and neither runtime supplies built-in
role context, so the role-scoped rules cannot fire at all. `cherryConfig` is
`approval: 'auto'`, so unattended Pi/dsh turns can still mutate agent config.

The gap predates this PR; the false guarantee did not. Split the bullet list
into cross-runtime limits and Claude-only ones, and point both the note and
`builtinAgentGuardRules` at #18898, which already proposes the shared evaluator
that would close it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>

@vaayne vaayne left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was translated automatically.

LGTM. Re-review confirmed that original code findings have been closed, and no new correctness, security, or architecture issues were found in the incremental changes; cross-runtime permission unification continues to be tracked by #18898.


Original Content

LGTM. 复审确认原有代码 findings 已闭环,增量未发现新的 correctness、security 或 architecture 问题;跨 runtime 权限统一继续由 #18898 跟进。

@vaayne vaayne left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was translated automatically.

Non-blocking: The Breaking changes in the PR description still claims that protected built-in destructive-operation blocks and unattended responder requirements remain in effect under Full Access for all runtimes; the updated breaking-change document has clarified these restrictions as Claude-only, and left cross-runtime unification for #18898. Please sync the PR description to avoid making overly broad guarantees for Pi and DSH in the release notes. The code review conclusion remains approved.


Original Content

非阻断:PR 描述的 Breaking changes 仍声称 protected built-in destructive-operation blocks 和 unattended responder requirements 在所有 runtime 的 Full Access 下保持生效;更新后的 breaking-change 文档已经将这些限制明确为 Claude-only,并把跨 runtime 统一留给 #18898。请同步 PR 描述,避免 release note 对 Pi 和 DSH 作出过宽保证。代码复审结论仍为通过。

Signed-off-by: suyao <sy20010504@gmail.com>
@claude

claude Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Review Summary

This is a well-structured refactoring that unifies tool approval policy across runtimes. After reviewing the policy registry, guard table evaluation logic, runtime consistency, and session state management, I found no high-confidence correctness bugs, security issues, or contract violations.

Key verification points:

  • ✅ Policy registry correctly classifies all tools with proper bypass semantics
  • ✅ Guard table correctly implements deny > ask folding and bypass behavior
  • ✅ All runtimes derive consistently from the centralized registry
  • ✅ Session state service correctly manages session-keyed state
  • ✅ Breaking change documentation is accurate and complete
  • ✅ Tests comprehensively cover bypass and headless scenarios

The documented behavioral change (Full Access lifting per-call approvals) matches the implementation, and the known gap about guard coverage (#18898) is properly acknowledged.

@zhangjiadi225 zhangjiadi225 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The implementation centralizes Cherry-owned tool approval metadata, preserves the explicit non-bypassable limits, and provides a sound transition toward the shared evaluator tracked in #18898. One non-blocking documentation consistency note remains.

Comment on lines +16 to +19
"Full Access" on a Fast (Pi) agent now lifts every approval prompt: tools that used to keep asking even there
(knowledge-base edits, image generation, CLI installs, assistant settings tools) run without a prompt. Two
explicit safety blocks still apply — disabled tools, and shell commands that install into the shared global
environment (`npm install -g`, `pip install --user`, …), which are denied outright rather than prompted.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[A1] Non-blocking: this paragraph says Full Access lifts every approval prompt and that only disabled tools or global installs remain restricted, but session_create and session_send still require live approval in every mode. Please mention that cross-session delegation remains the explicit exception, matching the newer 2026-08-18 breaking-change note.

@DeJeune
DeJeune merged commit 9512afa into main Aug 20, 2026
24 of 26 checks passed
@DeJeune
DeJeune deleted the settings-builder-permission-analysis branch August 20, 2026 05:22
@kangfenmao kangfenmao mentioned this pull request Aug 24, 2026
6 of 13 tasks
kangfenmao added a commit that referenced this pull request Aug 24, 2026
<!-- 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
>
> - Active development targets `main`.

### What this PR does

Before this PR:

Cherry Studio remains at version 2.0.8, and the bundled release notes,
stable release history, and generated product manifest still identify
2.0.8.

After this PR:

Cherry Studio is prepared for the stable 2.0.9 release with bilingual
user-facing release notes, a matching stable-history entry, and a
regenerated built-in product manifest.

<!-- (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

This release packages the user-visible features, fixes, improvements,
and performance work merged since v2.0.8. Release notes were generated
from commit-level `release-note` blocks first, with targeted inspection
for user-visible commits without one. Internal refactors, CI, tests,
documentation-only changes, dependency maintenance, and commits marked
`NONE` were excluded.

The following tradeoffs were made:

- Keep one user-facing line per included commit, which makes this large
release comprehensive.
- Preserve the exact bilingual notes in both `electron-builder.yml` and
`release-history.json`.
- Regenerate the product manifest from its source instead of editing it
manually.

The following alternatives were considered:

- A prerelease was not used because 2.0.9 is being prepared as a stable
patch release.
- A shorter thematic summary was not used because it would omit
user-visible commit-level changes required by the release workflow.

Links to places where the discussion took place: None.

### Breaking changes

This release includes user actions or behavior changes that reviewers
should verify:

- GitHub Models has been retired; affected users must configure another
provider.
- Full Access now skips per-call approval for additional Agent actions;
users who still want prompts should choose Approve for Me or Ask Before
Acting.
- Existing flomo users must authorize the migrated MCP connection once
more.
- Windows users whose `CLAUDE_CODE_GIT_BASH_PATH` points to
`git-bash.exe` must change it to Git Bash's `bin/bash.exe`.
- Users with anonymous data collection enabled must review the August
2026 privacy policy before analytics resumes.

### Special notes for your reviewer

Release preparation checklist:

- [ ] Review generated release notes in `electron-builder.yml`
- [ ] Verify stable release notes are preserved in
`resources/cherry-studio/release-history.json`
- [ ] Verify version bump in `package.json`
- [ ] Verify generated product manifest uses the release version
- [ ] CI passes
- [ ] Merge to trigger release build

<details>
<summary>Included commits since v2.0.8 (147)</summary>

- fix(provider-model): reconcile ambiguous delete failures (#19301)
- refactor(code): retire CLI config read IO, broadcast managed-tool
status (#18671)
- perf(dsh-runtime): bundle packaged dependencies (#19191)
- feat(data): support JSON Merge Patch for provider settings (#19280)
- fix(screenshot): keep overlays on the active macOS Space (#19281)
- fix(composer): show first picker attachment on unused assistant
(#19293)
- perf(painting): parallelize history file hydration (#19283)
- fix(agent-runtime): serialize stop and retry teardown (#19252)
- fix(chat-shell): keep sidebar resize handle aligned after settings
navigation (#19123)
- fix(code-editor): resolve editor height and language extension lookups
(#18951)
- feat(wechat-channel): support native iLink media (#19278)
- feat(agent-dialogs): refine create and edit experience (#19013)
- feat(pi-icon): use official Pi mark (#19277)
- fix(painting-size): keep selected chip outline visible (#18982)
- fix(settings): default settings entries to model services (#19137)
- fix(provider): add defaultChatEndpoint to lmstudio preset so Pi/DSH
endpoint resolution succeeds (#19006)
- fix(api-gateway): restore Gemini 3 thought signatures on replayed tool
calls (#19047)
- fix(provider-registry): distinguish integer image ranges (#19256)
- feat(settings): clarify Global Memory scope and add memory overview
(#19229)
- fix(agent): add back navigation to subagent flows (#19266)
- fix(chat-markdown): animate streamed inline code (#19156)
- perf(home-composer): skip unchanged streaming renders (#18468)
- feat(skills): restore file browser and translation (#18993)
- feat(skills): open installed skill folders (#18929)
- fix(agent-search): exclude hidden reasoning from search (#17661)
- fix(composer): isolate referenced conversation instructions (#19257)
- fix(agent-creation): show go-to-chat action after tool creation
(#19195)
- fix(quick-panel): preserve labels while aligning descriptions (#19199)
- fix(composer): preserve pasted screenshots with text flavors (#19260)
- fix(agent): discover workspace .agents skills (#19258)
- fix: remove unsupported 'includeServerSideToolInvocations' field from
Gemini tool config (#15098)
- fix(agents): delete an agent's task schedules with the agent (#19215)
- refactor(i18n): flatten locale catalogs to dotted keys (#19143)
- fix(agent-migration): tolerate unreadable legacy database (#18912)
- docs(development): remove empty Development section heading (#19237)
- feat(appearance): surface the chat and work list positions in settings
(#19200)
- fix(popups): stop IME candidate Enter from resolving PromptPopup with
raw pinyin (#19224)
- refactor(observability): remove orphaned IPC trace patch (#19246)
- fix(stream-manager): ignore stale terminal callbacks (#17997)
- docs(references): verify stale paths fixed for #18850 (#19227)
- feat(sidebar): support middle-click and context menu to open in new
tab (#19202)
- fix(settings): avoid router-hook crash when ModelSettings renders off…
(#19205)
- feat(provider-registry): add DeepSeek V4 Flash Vision Exp with image
input (#19236)
- fix(markdown): copy tables from local source blocks (#19193)
- perf(screenshot): make the capture overlay appear immediately (#18933)
- fix(deepseek-harness): heal stale managed keys at top-level of
versioned credentials (#19162)
- feat(export): convert markdown alerts to notion callouts (#18611)
- fix(filesystem-mcp): parse ripgrep JSON output (#17598)
- feat(artifact-pane): add copy path context menu to workspace file tree
(#18776)
- fix(prompt-interpolation): preserve literal replacement text (#17574)
- fix(artifact-pane): clear the elevated composer in a maximized pane
(#19034)
- feat(launchpad): refresh built-in app icons (#19059)
- fix(quick-panel): prevent hidden action hitboxes after tab switches
(#19187)
- feat(prompt-bindings): add contextual prompt visibility (#18378)
- fix(prompt-editor): align prompt action button styles (#19190)
- chore(gh-create-issue): add restricted engineering task template
(#19173)
- perf(mini-app): stabilize pool callbacks (#19169)
- fix(models): hydrate custom model registry metadata (#19174)
- fix(binary-manager): make BabelDOC installable from mainland China
(#19098)
- fix(binary-manager): retry pipx installs across PyPI mirrors in China
(#19102)
- ci(provider-registry): author catalog sync commits through GraphQL
(#19181)
- fix(model-drawer): drop native number spinners from model settings
inputs (#19125)
- fix(composer): preview Markdown attachments (#19154)
- fix(models): hydrate custom model registry limits (#19155)
- fix(screenshot): show the IME candidate window while annotating text
(#18934)
- test(agents-migration): fix flaky high-fan-out copy test on Windows CI
(#19132)
- fix(api-gateway): map chat reasoning_effort to Responses (#18854)
- fix(quick-panel): keep skill names readable (#19142)
- fix(screenshot): let Escape dismiss an overlay before any selection
(#18932)
- fix(model-selector): distinguish duplicate model names (#19018)
- fix(api-gateway): fold inline system messages for
openai-chat-completions (#19145)
- fix(anthropic-provider): accept thinking blocks without a signature
(#19146)
- fix(screenshot-overlay): keep the toolbar on screen for a full-screen
selection (#19150)
- feat(mcp): add built-in install_mcp_server tool for one-click MCP
registration (#18609)
- chore(download-binaries): share downloaded tools across git worktrees
(#19126)
- fix(app-startup): apply launch-on-boot preference to system (#18225)
- fix(agent-runtime): default missing model context window to 256K
(#18847)
- fix(model-list): read the Ollama context window from /api/show
(#19046)
- fix(deepseek-harness): write the versioned dsh credentials layout
(#19131)
- fix(provider): align Responses behavior and provider state ownership
(#18449)
- docs(claude-md): replace broken pnpm test <path> advice with working
targeted commands (#19135)
- fix(quick-assistant): use the configured quick model (#19080)
- fix(mcp): collect prompt arguments in a dialog before insert (#18946)
- fix(mini-app): relay guest keydown so renderer shortcuts work in
webviews (#19076)
- fix(api-gateway): convert max_tokens to max_completion_tokens for
OpenAI reasoning models (#18768)
- fix(composer): prevent editable toggle from restoring sent draft text
(#19113)
- fix(composer): surface full url on truncated link chips and speed up
og hover (#19107)
- fix(provider): remove retired GitHub Models integration (#19072)
- fix(pi-agent): resolve citations for pi's tool_call wrapper (#19071)
- fix(message-locate): bind global-search locate requests to their
conversation (#19094)
- fix(data-migration): skip Dexie records with unreadable large values
(#19086)
- fix(binary-manager): validate installs by the bins a recipe exposes
(#19087)
- test(trace-viewer): drop the virtualizer scroll debounce before jsdom
teardown (#19101)
- fix(keyboard): make message scroll containers focusable for arrow-key
navigation (#19070) (#19099)
- perf(chat): remove unused flow navigation listener (#19085)
- fix(html-artifact): prevent bare-text height jitter in inline preview
(#18998)
- feat(usage): redesign usage analytics with ECharts (#19084)
- fix(chat): prevent arrow-key scroll jitter in message list (#18769)
- fix(topic-rename): wait for persistence before showing success
(#19005)
- fix(citations): clear sources when switching conversations (#18996)
- fix(ovms): list all configured OVMS models regardless of loading state
(#18826)
- fix(chat): restore clear-messages shortcut after event removal
(#19091)
- fix(message-list): isolate message targeting by tab (#18997)
- fix(composer): preserve drafts when stream open is blocked (#18999)
- fix(conversation-routing): prevent stale session actions (#18970)
- fix(topic-list): clear messages for non-active topics (#19004)
- fix(right-pane): animate maximize and restore instead of committing at
the boundary (#19083)
- fix(paths): divert dev logs directory away from packaged installs
(#18068)
- fix(user-data-relocation): copy symlink targets during migration
(#19033)
- fix(agents): lazy-load AgentSessionImageCaptureHost in Sessions
(#19078)
- fix(notes): preserve navigation after renaming (#19020)
- fix(agent-composer): show full AskUserQuestion titles (#19016)
- fix(code-style): isolate theme catalog updates (#19069)
- fix(command-execution): restore Bash output on Windows (#17682)
- fix(topics): prevent old title flash after rename (#18760)
- feat(mini-app): split the mini app view into two panes (#18425)
- feat(sidebar): pin assistants and agents to the sidebar (#18345)
- fix(agent-session): update lastActivityAt when reusing placeholder
session (#19036)
- test(job-scheduler): fix flaky nextRun assertion on natural interval
fire (#19057)
- feat(local-model): enable inference hardware acceleration by default
(#19055)
- refactor(external-app): unify external open targets (#19027)
- fix(migration): preserve agent identity files when v1 source workspace
is removed (#19053)
- fix(pdf-translation): show resource download progress (#19051)
- refactor(mcp): move builtin server knowledge out of the MCP runtime
(#18630)
- fix(api-gateway): downgrade inline system messages for Ollama (#19042)
- fix(job-scheduler): persist automatic next runs (#19017)
- fix(code-mate): ignore stale provider selections (#19015)
- fix(permission-mode): improve option legibility (#19035)
- fix(topic-navigation): recover deleted active topics (#18995)
- fix(message-locate): cancel stale requests on conversation switch
(#18994)
- fix(ai-messages): drop unanswered approvals from replayed history
(#18066)
- fix(code-viewer): highlight the first frame without the debounce delay
(#18950)
- feat(assistant-settings): add model settings navigation (#19030)
- fix(api-gateway): keep Agent SDK system messages out of the assistant
turn (#18697)
- fix(skills): only block skills on provable dependency gaps, and split
SkillService (#18549)
- feat(shortcuts): restore Ctrl+L clear messages shortcut (#19021)
- ci(claude-review): keep review failures advisory (#19031)
- feat(privacy): publish August 2026 policy update (#19022)
- docs(code-review): require dual sources for network downloads (#18953)
- fix(api-gateway): skip Anthropic tools without an input_schema
(#18780)
- fix(pi-agent): resolve citations for third-party MCP tool outputs
(#18829)
- perf(chat): lazy-load disabled message outline (#18971)
- fix(tabs): prevent pinning transient mini-apps (#18904)
- fix(provider-registry): send enable_thinking for Qwen on
openai-compatible providers (#18772)
- refactor(agent-runtime): unify declarative tool approval policy
(#18865)
- feat(provider-registry): remote catalog hot-update without an app
release (#16857)
- fix(skills): support valid GitHub SKILL.md links (#18907)

</details>

### 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] Branch: This PR targets `main`
- [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
- [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
Cherry Studio 2.0.9 - Features, Fixes, and Experience Improvements

✨ New Features
- [WeChat] Agents can now send documents and videos through WeChat channels, while incoming video and voice messages are saved to the Agent workspace.
- [Skills] Installed Skill details once again show the source file tree and content preview, with optional one-click Chinese translation for Markdown files.
- [Skills] Installed Skill details now include an action to open the managed skill folder.
- [Agent] Agent sessions now automatically discover workspace skills stored under .agents/skills.
- [Appearance] Chat conversation list and work task list positions can now be changed from Settings → Appearance.
- [Sidebar] Sidebar items can now be opened in a new tab with middle-click or the context menu.
- [Models] Added DeepSeek V4 Flash Vision Exp with image input support.
- [Export] Notion export now converts GitHub-style Markdown alerts into native Notion callout blocks.
- [Files] The Agent workspace file tree now offers a context-menu action to copy a file path.
- [Prompts] Prompts can now be global or limited to selected contexts, with context-specific ordering and management in Settings.
- [MCP] Agents can now register an MCP server in one step; new servers remain inactive and untrusted until enabled unless activation was explicitly requested.
- [Usage] Redesigned usage analytics with a clearer overview, independent request sorting, and interactive charts.
- [Mini Apps] Mini apps can now be shown side by side in a split view.
- [Sidebar] Assistants and Agents can now be pinned to the sidebar for quick access to their latest conversation.
- [Providers] Provider and model catalog updates can now be delivered without waiting for an app release.

🐛 Bug Fixes
- [Providers] Model deletion now keeps the model list synchronized when a request reports failure after the model was already removed.
- [Providers] Updating provider settings now preserves untouched nested options and correctly removes explicitly cleared values.
- [Screenshot] Capture overlays now remain on the active macOS Space.
- [Composer] Fixed the first paperclip attachment not appearing immediately after switching to an unused assistant.
- [Agent] Fixed an immediate retry after stopping a session returning an empty or stalled response or losing Claude Code resume state.
- [UI] Fixed the left sidebar resize handle becoming misaligned after returning from Settings.
- [Code Editor] Fixed artifact file previews not filling or scrolling correctly and restored syntax highlighting for Markdown, TSX, TOML, Vue, PHP, and other file types.
- [Painting] Painting size selections now keep their full outline visible.
- [Settings] Generic Settings entry points now consistently open Model Services.
- [LM Studio] Fixed LM Studio models not appearing in Pi and DeepSeek Harness Agent model pickers.
- [Gemini] Fixed Gemini 3 latest aliases failing after the first tool call because replayed calls lacked thought signatures.
- [Painting] Image count controls now reject fractional values while continuous controls retain decimal precision.
- [Markdown] Fixed streamed inline code appearing abruptly and occasionally causing a rendering error.
- [Agent] Global search no longer exposes hidden model reasoning, and existing search data is cleaned automatically.
- [Agent] Referenced conversations are now treated as historical context and cannot authorize Agent actions.
- [Composer] Fixed pasted Windows screenshots being dropped when the clipboard also contains text.
- [Gemini] Fixed Gemini 3.x requests failing with HTTP 400 when web search and MCP tools are used together.
- [Agent] Deleting an Agent now also removes its scheduled tasks instead of leaving them running and failing in the background.
- [Migration] An unreadable legacy Agent database no longer blocks v2 migration; it is preserved and reported while remaining data continues migrating.
- [Input] Confirming a Chinese, Japanese, or Korean IME candidate no longer saves raw composition text when renaming a conversation.
- [Onboarding] Fixed a fresh installation crashing on the model-selection step.
- [Markdown] Copying a table now returns the original Markdown table instead of unrelated message content.
- [CodeMate] Fixed DeepSeek Harness failing to launch when old managed API-key entries remain in its credentials file.
- [MCP] Fixed workspace file search failing to parse ripgrep results correctly.
- [Prompts] Prompt and translation placeholders now preserve literal replacement text, including special replacement characters.
- [Artifact Pane] Fixed the elevated composer remaining visible after maximizing the artifact pane.
- [QuickPanel] Fixed hidden actions remaining clickable after switching back to a tab.
- [Models] Recognized custom models now inherit missing registry descriptions and input/output capabilities.
- [PDF] Fixed BabelDOC installation failing in mainland China by using accessible Python, font, and model mirrors.
- [Tools] Python-based tool installation in mainland China now retries alternate package mirrors when the default mirror is not ready.
- [Composer] Markdown attachments can once again be opened in the file preview from the Composer or sent messages.
- [Models] Existing custom models now inherit context limits and pricing when a registry update recognizes them.
- [Screenshot] The IME candidate window now remains visible while annotating screenshots with text.
- [API Gateway] Fixed reasoning effort being lost when Chat Completions requests are converted to OpenAI Responses requests.
- [Screenshot] Escape now dismisses a capture overlay before a selection is made, including on another hovered display.
- [API Gateway] Fixed Agent sessions failing on OpenAI-compatible endpoints that require system messages to appear first.
- [Providers] Fixed Anthropic-compatible providers failing when thinking blocks omit a signature.
- [Screenshot] Fixed the annotation toolbar appearing off-screen after a full-screen capture.
- [Startup] Launch on Startup now registers with the operating system and reapplies the saved preference when Cherry Studio starts.
- [Agent] Pi and DeepSeek Harness Agents can now use models without a configured context window by falling back to 256K.
- [Ollama] Local Ollama models now use their reported context window, preventing tool-based Agent sessions from being truncated and failing.
- [CodeMate] Fixed DeepSeek Harness 0.1.1 and newer rejecting the credentials layout written by Cherry Studio.
- [Providers] Fixed Volcengine Ark multi-turn Responses and web search, Hugging Face reasoning and tool use, compatible third-party reasoning settings, OpenAI reasoning summaries, and Fast mode for supported Doubao models.
- [Quick Assistant] Quick Assistant now uses the configured Quick Model instead of silently falling back to the default model.
- [MCP] MCP prompts with arguments now collect values in a form before inserting the rendered prompt.
- [Mini Apps] Keyboard shortcuts such as Ctrl+Tab now work while focus is inside a Mini App.
- [API Gateway] Fixed Agent requests to OpenAI reasoning models failing because compatible providers sent max_tokens instead of max_completion_tokens.
- [Composer] Fixed the input box restoring an already-sent message after sending.
- [Agent] Pi Agent citations now render as citation badges with a source list instead of literal markers.
- [Search] Opening another conversation while message location is in progress no longer loads the entire history into the newly opened conversation.
- [Migration] V1-to-v2 migration now skips IndexedDB records whose large backing value can no longer be read.
- [Tools] Dependency installs now validate the executables a recipe actually provides, fixing false “installed but not runnable” errors.
- [Artifacts] Fixed inline HTML previews continuously jumping between two heights when content ends with bare text.
- [Chat] Fixed arrow-key scrolling jitter in message lists.
- [Chat] Topic rename success now appears only after the title is saved, and automatic rename failures are reported.
- [Citations] Switching conversations now closes the citations panel so sources from the previous conversation are not shown.
- [OVMS] Downloaded OVMS models now remain visible in Model Manager even when server-side loading fails.
- [Shortcuts] The clear-messages shortcut once again clears the active conversation.
- [Shortcuts] Restored Ctrl+L as the clear-messages shortcut, with a confirmation step.
- [Tabs] Message navigation and selection now target the correct tab when the same conversation is open more than once.
- [Composer] Drafts, attachments, and structured tokens are preserved when the main process blocks a send request.
- [Navigation] Actions no longer target the previous assistant or Agent conversation while a newly selected conversation is loading.
- [Chat] Clearing messages from a non-active conversation now affects the selected conversation instead of the active one.
- [Migration] Data-directory migration on Windows now copies symbolic-link targets as ordinary files and directories.
- [Notes] The first click after renaming a note now switches to the selected note correctly.
- [Windows] Restored Bash command output; if CLAUDE_CODE_GIT_BASH_PATH points to git-bash.exe, update it to Git Bash's bin/bash.exe.
- [Chat] Conversation and Agent session titles no longer briefly revert after an inline rename.
- [Agent] Reused empty Agent sessions now appear in the correct recent date group.
- [Agent] V2 Agent identity and memory files are preserved when their old v1 source workspace no longer exists.
- [Ollama] Fixed Claude Agent SDK system updates being dropped by some Ollama model templates.
- [Tasks] Scheduled tasks now report accurate next-run times after pause, resume, manual runs, and restarts.
- [CodeMate] Fixed stale provider selections interfering with the currently selected Code Mate configuration.
- [Navigation] A chat window no longer remains on an assistant conversation that was deleted elsewhere.
- [Search] Message-search navigation now stops loading old history after switching conversations.
- [Agent] Continuing after an unanswered tool approval no longer fails with “No tool output found for tool call”.
- [Code] Code blocks no longer flash as unhighlighted text before syntax highlighting appears.
- [API Gateway] Agent sessions on non-Anthropic models no longer receive internal Agent SDK context as an assistant reply.
- [Skills] Missing subagent dependencies now show a clear error, and unresolved CLI executables show a warning instead of silently producing unrelated output.
- [API Gateway] Anthropic requests containing server tools without an input schema no longer fail the whole request on providers that cannot run those tools.
- [Agent] Pi Agent citations now resolve for web-search results returned by third-party MCP tools.
- [Tabs] Transient Mini App tabs no longer offer an unsupported Pin Tab action.
- [Qwen] Fixed the thinking toggle for locally deployed Qwen models on OpenAI-compatible providers.
- [Skills] Fixed GitHub Skill installation from raw links, repository-root descriptors, lowercase skill.md files, and explicit branches or tags.

💄 Improvements
- [Agent] Improved Agent creation and editing with consistent navigation, clearer runtime choices, integrated avatar editing, and reliable permission defaults.
- [Agent] Pi Agent now uses its official icon.
- [Memory] Added a memory overview and clarified the roles of Agent File Memory, Knowledge Base, and MCP Memory after removal of v1 Global Memory.
- [Agent] Added a back button to subagent flow details.
- [Agent] Agent creation tools now refresh the Agent list and offer a Go to chat action for the newly created Agent.
- [QuickPanel] Improved row layout so command and Skill names remain readable while descriptions use the available space.
- [Launchpad] Refreshed built-in apps with dedicated multicolor icons and consistent sizing.
- [Prompts] Aligned prompt editor action buttons for consistent sizing, shape, color, and icon scale.
- [Models] Removed native number spinners from numeric model-setting fields.
- [QuickPanel] Improved Skill-name readability.
- [Models] Model selectors now identify duplicate names by provider and model ID.
- [Composer] Truncated pasted links now show an ellipsis, reveal the full URL on hover, and display preview cards faster.
- [Artifact Pane] Maximizing and restoring the right pane now animates smoothly without content jumps, overlap, or dark-mode transparency.
- [Files] File-opening menus now show bounded application choices and remember the preferred app for each file type.
- [PDF] PDF translation now shows accurate progress while required resources are downloaded.
- [Agent] Agent questions now show their full title instead of truncating it to one line.
- [Permissions] Improved permission-mode readability and restored warning tooltips.
- [Assistant] Selection Assistant and Quick Assistant settings now show the active model and link directly to its global configuration.
- [Providers] Action required: the retired GitHub Models provider has been removed; configure another provider if you previously relied on it.
- [MCP] Built-in flomo and Nowledge Mem servers now use their real Streamable HTTP endpoints; existing servers migrate automatically, but flomo users must authorize once more.
- [Privacy] The August 2026 privacy policy adds model-relay and third-party billing disclosures; users with anonymous data collection enabled must review it before analytics resumes.
- [Permissions] Full Access now skips per-call approval for knowledge-base edits, image generation, CLI installation, and Assistant settings/file tools; choose Approve for Me or Ask Before Acting if these actions should still prompt.

⚡ Performance
- [Installation] Reduced unpacked DeepSeek Harness runtime files to improve Cherry Studio installation speed.
- [Painting] Painting history now loads independent file metadata concurrently.
- [Screenshot] Capture overlays now appear immediately.
- [Local Models] Hardware acceleration is now enabled by default for local inference.
```

Signed-off-by: kangfenmao <kangfenmao@qq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants