diff --git a/apps/desktop/src/lib/settings-search.ts b/apps/desktop/src/lib/settings-search.ts index f7b5e50329..9cf056c56c 100644 --- a/apps/desktop/src/lib/settings-search.ts +++ b/apps/desktop/src/lib/settings-search.ts @@ -279,6 +279,10 @@ export const SETTINGS_NAV: SettingsNavEntry[] = [ labelKey: "settings.nav.sync", titleKey: "settings.configSync.title", group: "system", + // Cloud sync (encrypted portable configuration backup) is not open to + // users yet: packaged builds hide the destination and its search hits, + // development builds keep it. Drop this flag to ship it again. + developmentOnly: true, keywordKeys: [ "settings.configSync.connectionTitle", "settings.configSync.endpoint", diff --git a/apps/desktop/test/config-sync-settings.test.mjs b/apps/desktop/test/config-sync-settings.test.mjs index ba353b3985..36ef04946c 100644 --- a/apps/desktop/test/config-sync-settings.test.mjs +++ b/apps/desktop/test/config-sync-settings.test.mjs @@ -33,6 +33,9 @@ const syncError = await read( test("cloud sync rendering follows the settings visibility gate", () => { assert.match(settingsPage, /tab === "sync" && !tabHidden && /); assert.match(settingsIndex, /id: "sync"/); + // The cloud backup ships hidden from packaged builds: the destination stays + // a development-build surface until it opens. + assert.match(settingsIndex, /id: "sync"[\s\S]{0,400}developmentOnly: true/); assert.doesNotMatch(settingsIndex, /experimentalBadgeKey: "settings\.configSync\.experimental"/); assert.match(settingsIndex, /settings\.configSync\.connectionTitle/); }); diff --git a/apps/desktop/test/settings-developer-only-destinations.test.mjs b/apps/desktop/test/settings-developer-only-destinations.test.mjs index 29e505f139..ebba0fe281 100644 --- a/apps/desktop/test/settings-developer-only-destinations.test.mjs +++ b/apps/desktop/test/settings-developer-only-destinations.test.mjs @@ -1,9 +1,9 @@ /** * Developer-only settings destinations are retained in development builds - * but omitted from packaged builds. Cloud sync is a public Experimental - * destination: it is visible in every build without developer mode. - * Navigation, search, and stale-page handling must all honor the same - * visibility rules. + * but omitted from packaged builds. Cloud sync is not open to users yet and + * carries the same build gate: development builds keep it, packaged builds + * omit its rail row, page, and settings-search hits. Navigation, search, and + * stale-page handling must all honor the same visibility rules. */ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; @@ -74,27 +74,39 @@ test("Live Voice is reachable in every build without developer mode", () => { ); }); -test("Cloud sync is reachable in every build without developer mode", () => { +test("Cloud sync is a development-build-only destination", () => { + // Cloud backup (encrypted portable configuration sync) is not open to users + // yet: development builds keep the destination, packaged builds omit it. for (const developerMode of [false, true]) { - for (const includeDevelopmentOnly of [false, true]) { - assert.ok(visibleSettingsNav(developerMode, includeDevelopmentOnly) - .some((entry) => entry.id === "sync")); - assert.equal(isSettingsDestinationHidden("sync", developerMode, includeDevelopmentOnly), false); - for (const query of [ - "configSync.connectionTitle", - "configSync.endpoint", - "configSync.syncNow", - ]) { - assert.ok(searchSettings(query, identity, { developerMode, includeDevelopmentOnly }) - .some((hit) => hit.tab === "sync")); - } + assert.ok(visibleSettingsNav(developerMode, true) + .some((entry) => entry.id === "sync")); + assert.equal(isSettingsDestinationHidden("sync", developerMode, true), false); + for (const query of [ + "configSync.connectionTitle", + "configSync.endpoint", + "configSync.syncNow", + ]) { + assert.ok(searchSettings(query, identity, { + developerMode, + includeDevelopmentOnly: true, + }).some((hit) => hit.tab === "sync")); } + + assert.equal(visibleSettingsNav(developerMode, false) + .some((entry) => entry.id === "sync"), false); + assert.equal(isSettingsDestinationHidden("sync", developerMode, false), true); + assert.deepEqual( + searchSettings("configSync.connectionTitle", identity, { + developerMode, + includeDevelopmentOnly: false, + }), + [], + ); } - // Cloud sync is a regular destination now: no developer or build gate and no - // Experimental badge remain. + // The build gate is the only gate: no developer mode and no badge. const sync = SETTINGS_NAV.find((entry) => entry.id === "sync"); assert.equal(sync?.developerOnly, undefined); - assert.equal(sync?.developmentOnly, undefined); + assert.equal(sync?.developmentOnly, true); assert.equal(sync?.experimentalBadgeKey, undefined); }); @@ -115,31 +127,37 @@ test("developer mode retains the developer-only destinations in development", () SETTINGS_NAV.filter((entry) => entry.developerOnly === true) .every((entry) => entry.experimentalBadgeKey), ); + // Development builds keep the not-yet-open Cloud sync destination. assert.equal(off.includes("sync"), true); }); -test("packaged builds still hide the developer-only remote hosts", () => { +test("packaged builds hide the developer-only destinations and Cloud sync", () => { const packaged = visibleSettingsNav(true, false).map((entry) => entry.id); for (const id of developerOnlyIds) { assert.equal(packaged.includes(id), false); assert.equal(isSettingsDestinationHidden(id, true, false), true); } - assert.equal(packaged.includes("sync"), true); - assert.equal(isSettingsDestinationHidden("sync", true, false), false); - assert.equal(isSettingsDestinationHidden("sync", false, false), false); + assert.equal(packaged.includes("sync"), false); + assert.equal(isSettingsDestinationHidden("sync", true, false), true); + assert.equal(isSettingsDestinationHidden("sync", false, false), true); assert.equal(isSettingsDestinationHidden("general", true, false), false); }); test("settings search mirrors developer and packaged visibility", () => { + for (const options of [{ developerMode: false }, { developerMode: true }]) { + assert.ok( + searchSettings("configSync.connectionTitle", identity, options) + .some((hit) => hit.tab === "sync"), + ); + } + for (const options of [ - { developerMode: false }, - { developerMode: true }, { developerMode: false, includeDevelopmentOnly: false }, { developerMode: true, includeDevelopmentOnly: false }, ]) { - assert.ok( - searchSettings("configSync.connectionTitle", identity, options) - .some((hit) => hit.tab === "sync"), + assert.deepEqual( + searchSettings("configSync.connectionTitle", identity, options), + [], ); } diff --git a/docs/spec/03-runtime/01-ipc-protocol.md b/docs/spec/03-runtime/01-ipc-protocol.md index 3e252572bb..1024f3081c 100644 --- a/docs/spec/03-runtime/01-ipc-protocol.md +++ b/docs/spec/03-runtime/01-ipc-protocol.md @@ -2406,7 +2406,9 @@ unchanged. See [provider configuration](12-provider-config-schema.md). ## 15. Cloud configuration sync The Settings → Cloud sync page uses the following renderer-to-Main channels; -all are forwarded to the Host-owned `configSync.*` RPC methods: +all are forwarded to the Host-owned `configSync.*` RPC methods. The page is a +development-build-only surface for now; the channels and their Host contracts +are unchanged: | IPC channel | Host method | contract | |---|---|---| diff --git a/docs/spec/03-runtime/22-config-sync.md b/docs/spec/03-runtime/22-config-sync.md index 318b13ba63..dbe0e76ed6 100644 --- a/docs/spec/03-runtime/22-config-sync.md +++ b/docs/spec/03-runtime/22-config-sync.md @@ -166,6 +166,10 @@ unchanged. ## 5. Settings workflow +The destination is a development-build-only surface for now: a packaged build +omits the Settings → Cloud sync row, page, and settings-search hits, while +the Host-owned sync behavior described here is unchanged. + Settings → Cloud sync provides WebDAV endpoint credentials, vault password, device label, server compatibility mode, category selection, a capability test, sync-now, unlock, pause, folder mapping, approval/rejection, revision diff --git a/docs/spec/04-ux/06-settings-ia.md b/docs/spec/04-ux/06-settings-ia.md index af701672dc..1183b345b3 100644 --- a/docs/spec/04-ux/06-settings-ia.md +++ b/docs/spec/04-ux/06-settings-ia.md @@ -54,7 +54,7 @@ Settings is a **full-window page** that replaces the app sidebar + main chrome ( 7. **MCP** — Lucide `Server` (agent connections) 8. **Subagents / 子智能体** — Lucide `Bot` (built-in and personal parallel agents) 9. **Projects / 项目** — Lucide `Archive` (durable project index) - 10. **Cloud sync / 云同步** — Lucide `CloudDownload` (encrypted portable configuration backup and bidirectional sync) + 10. **Cloud sync / 云同步** — Lucide `CloudDownload` (encrypted portable configuration backup and bidirectional sync; development builds only) 11. **Remote Hosts / 远程主机** — Lucide `Globe` (SSH bootstrap and pairing inventory; developer mode only) 12. **Info / 信息** — Lucide `Info` (versions, logs, updates, developer) Icons are decorative (`aria-hidden` via the SVG default) and stay monochrome @@ -63,8 +63,8 @@ Settings is a **full-window page** that replaces the app sidebar + main chrome ( scanability, the destinations are shown in four titled visual clusters: `Preferences` / `偏好` (General, AI, Shortcuts), `Agent` / `智能体` (Instructions, Models, Skills, MCP, Subagents), `Workspace` / `工作区` - (Projects), and `System` / `系统` (Cloud sync, Remote Hosts, Info; - Remote Hosts is developer-only). Headings are + (Projects), and `System` / `系统` (Cloud sync, Remote Hosts, Info; Cloud sync + is development-build-only, Remote Hosts is developer-only). Headings are muted, non-interactive labels and use whitespace for separation; no divider lines are rendered. These are visual landmarks only, not a second navigation level. @@ -74,12 +74,14 @@ Settings is a **full-window page** that replaces the app sidebar + main chrome ( requirement. Its rail row, page, search hits, and idle Composer entry are available to all users. It is the only place to enable Live Voice. See the Voice section below. -- **Cloud sync / 云同步** is a regular `System` / `系统` destination - available to every user in every build: its rail row, page, and - settings-search hits never depend on developer mode and never fall back to - General. It ships as a stable destination, so neither the rail row nor the - page title carries an Experimental badge, and nothing about the sync - behavior itself changes. +- **Cloud sync / 云同步** is not open to users yet: it is a + development-build-only `System` / `系统` destination. Its rail row, page, + and settings-search hits exist in development builds only; a packaged build + omits them, and a rail position left on it falls back to General. Developer + mode is not a gate either way, neither the rail row nor the page title + carries an Experimental badge, and nothing about the sync behavior itself + changes. Removing the destination's `developmentOnly` flag reopens it for + packaged builds. - **Remote Hosts / 远程主机** is a developer-only, Experimental destination: its rail row, its page, and its settings-search hits exist only while `AppSettings.developerMode` is `true`. With developer mode off the row is @@ -832,7 +834,9 @@ system while preserving their different data ownership: - Developer-only destinations join and leave the rail, the page, and settings search as one unit: while developer mode is off the rail omits the row, settings search returns no hit for it, and an open Remote Hosts page returns - to General. Cloud sync is a regular destination and always stays reachable + to General. Cloud sync is development-build-only for now: packaged builds + omit its rail row, page, and settings-search hits and fall back to General, + while developer mode never gates it. ## 4. Acceptance @@ -840,9 +844,9 @@ system while preserving their different data ownership: 2. Rail shows the search pill at the top, the back-to-app action pinned at the foot on the main sidebar's footer icon line, and exactly General / 常规, AI, Shortcuts / 快捷键, Instructions / 指令, Models / 模型, Skills / 技能, MCP, - Subagents / 子智能体, Projects / 项目, Cloud sync / 云同步, - Remote Hosts / 远程主机, and Info / 信息 in that order. Cloud sync / 云同步 is - available to every user; Remote Hosts appears only in developer mode. Voice + Subagents / 子智能体, Projects / 项目, Cloud sync / 云同步 (development + builds only), Remote Hosts / 远程主机 (developer mode only), and Info / 信息 + in that order. A packaged build leaves Cloud sync out; Voice appears between AI and Shortcuts only in development builds with developer mode on. The rows are grouped under Preferences / 偏好, Agent / 智能体, Workspace / 工作区, and System / 系统. There is no diff --git a/docs/spec/06-delivery/04-e2e-test-plan.md b/docs/spec/06-delivery/04-e2e-test-plan.md index 80b4d01bd2..130db95c19 100644 --- a/docs/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/spec/06-delivery/04-e2e-test-plan.md @@ -2913,6 +2913,8 @@ identify the platform validation still needed. 15. Archive one project session, open it from Projects, and return to Projects. 16. Return to the app shell and open Plugins. - **Expected**: The rail contains exactly General, AI, Shortcuts, Instructions, Models, Skills, MCP, Subagents, Projects, Cloud sync, Remote Hosts, and Info in that order, each with its semantic Lucide icon (Sliders / Sparkles / Keyboard / FileText / Bot / BookOpen / Server / Bot / Archive / CloudDownload / Globe / Info). The flat directory is visually grouped under four muted, non-interactive headings — Preferences / 偏好 for General, AI, and Shortcuts; Agent / 智能体 for Instructions and Models; Workspace / 工作区 for Projects; About / 关于 for Info — with whitespace and no divider lines between groups; searching keeps the destination results flat and hides empty groups together with their headings. Appearance remains in General, while Permissions, Defaults, and the Command shell row live under 全局 AI; an available selected shell is represented by the selector without a duplicate Configured status, while default, fallback, and no-effective-shell states remain explicit; Context management has no settings card; Keyboard shortcuts and global instructions have their own destinations; Developer lives under Info; Projects shows active, closed, and archived durable rows without a visibility toggle, grouping them under the always-visible Pinned / All projects / Archived strips (D168/D267/D455) with per-section counts in a one-column workbench. The destination renders no hero block and no page-level counter run: the intro is one quiet description line, and each group strip's count agrees with its rendered rows; a click selects a row without leaving Settings; sorting by Name reorders rows inside every section without hiding any; search matches project fields and session titles and reports a match count, a session-title result selects its owning project, lists sessions in the inspector by latest activity with relative update times, and reveals history in batches of eight; clearing the search restores the complete index. The inspector menu closes on Escape and on an outside press. Bootstrap completion and background refreshes do not return Settings or Extensions to the chat home; the destination changes only after an explicit navigation action. Restore keeps the archive open and activation returns to chat with the restored project retained in the sidebar. Opening an archived session succeeds before clearing its archived state, returns to chat with that session selected, and makes it visible in the project sidebar; returning to Project archive no longer shows that session as archived. The home sidebar and global page results have no standalone Projects destination; Settings search finds Projects; Plugins remains an independent app-shell destination. + A packaged build omits Cloud sync from the rail and settings search; see + `04-ux/06-settings-ia.md`. - **Specs linked**: `04-ux/06-settings-ia.md`, `04-ux/01-ui-ia.md`, `03-runtime/11-provider-model-system.md` - **Acceptance**: B (model configuration), F (project persistence) - **Milestone**: M4 @@ -9389,9 +9391,9 @@ must keep splitting are covered by `markdown-blocks.test.mjs`. local WebDAV fixture that supports strong ETags and conditional PUT, plus a fixture variant that ignores conditional headers but supports `PROPFIND` directory listing. No real WebDAV account, provider, or production desktop. - Developer mode starts off so the public destination is exercised as shipped. -- **Steps:** 1) Open Settings with developer mode off; confirm Cloud sync is - present in the rail and returned by settings search, then open it and + Developer mode starts off so the destination is exercised as developed. +- **Steps:** 1) In a development build with developer mode off, confirm Cloud + sync is present in the rail and returned by settings search, then open it and confirm neither the rail row nor the page title carries an Experimental badge. 2) Toggle developer mode on and off and confirm the destination stays reachable either way. 3) Enter the fixture URL, @@ -9418,9 +9420,10 @@ must keep splitting are covered by `markdown-blocks.test.mjs`. refreshes in the background. Confirm a configured endpoint reuses its stored WebDAV app password, while password fields themselves remain blank and no vault password is written to renderer storage. -- **Expected:** Cloud sync is reachable in every build without developer mode, - carries no Experimental badge on the rail row or page title, and neither its - availability nor its behavior changes when developer mode is toggled. +- **Expected:** Cloud sync is reachable in development builds without developer + mode, is absent from a packaged build's rail, page, and settings search, + carries no Experimental badge, and neither its availability nor its behavior + changes when developer mode is toggled. Strict mode refuses unreliable conditional writes. The explicit compatibility mode accepts only a server that proves bounded directory @@ -9445,8 +9448,9 @@ must keep splitting are covered by `markdown-blocks.test.mjs`. - **Milestone:** M6+. - **Status:** Draft; merge/crypto, in-process WebDAV conditional-write coverage, and the two-device host/WebDAV path are automated by - `pnpm test:e2e:config-sync`. Public Cloud sync visibility without developer - mode is asserted by `settings-developer-only-destinations.test.mjs`; full + `pnpm test:e2e:config-sync`. Cloud sync's development-build-only visibility + and its packaged-build omission are asserted by + `settings-developer-only-destinations.test.mjs`; full renderer-driven persistence and checkpoint recovery fault injection remain. **E2E-CHAT-session-todo-checklist: TodoWrite to session-aware TodoDock** @@ -9870,8 +9874,9 @@ This test plan spec is accepted when: - Open Settings (footer profile → Settings). - Expect **full-page** Codex settings (no app sidebar/nav). Left rail has Back to app, search, and exactly General / AI / Shortcuts / Instructions / Models / - Skills / MCP / Subagents / Projects / Cloud sync / Remote Hosts / Info in that - order; content pane shows the selected destination. + Skills / MCP / Subagents / Projects, Cloud sync / Remote Hosts / Info in that + order (a packaged build leaves Cloud sync out); content pane shows the + selected destination. - Return to the app shell and expect Plugins to remain an independent sidebar-footer destination. - Drag the empty 46px top band over either the rail or content pane; the native @@ -10083,8 +10088,8 @@ This test plan spec is accepted when: - Expect the working theme selector without inert toggle or open-target rows. - Expect Appearance in General and Permissions + Defaults in AI. The rail contains General, AI, Shortcuts, Instructions, Models, Skills, MCP, - Subagents, Projects, Cloud sync, and Info; Remote Hosts appears only in - developer mode. Voice may appear between AI and Shortcuts in development + Subagents, Projects, Cloud sync (development builds only), and Info; Remote + Hosts appears only in developer mode. Voice may appear between AI and builds with developer mode on; plugin-contributed destinations follow the core groups. There is no Import destination. - Resize between 800px, 1200px, and 1600px widths; the content cards fill the @@ -16540,9 +16545,10 @@ the latest destination. These assertions measure work counts, not device FPS. - Automated coverage: `pnpm test:e2e:settings-scroll` mounts the production SettingsPage, store, translations, and built CSS in isolated Electron. Only preload data is stubbed; search navigation uses SearchDialog's public store - entry points. It also checks that Cloud sync has no developer-mode gate and - no Experimental badge, that Remote hosts keeps its badge, and the fallback - to General. This covers renderer interaction, not host persistence or the + entry points. It also checks that Cloud sync stays a development-build-only + destination with no developer-mode gate and no Experimental badge, that + Remote hosts keeps its badge, and the fallback to General. This covers + renderer interaction, not host persistence or the full global-search dialog. ### E2E-SCHEDULED-dispatch diff --git a/docs/spec/08-meta/decisions-log.md b/docs/spec/08-meta/decisions-log.md index 73c32f16fd..ede63d0035 100644 --- a/docs/spec/08-meta/decisions-log.md +++ b/docs/spec/08-meta/decisions-log.md @@ -37,11 +37,12 @@ This log freezes previously open questions into concrete decisions. | D640 | User MCP tools keep the normal approval path | **`mcp__` calls are `medium` risk in host-core: under `ask` and `accept-edits` each call shows the approval card ("MCP server tool requires approval"), allow-once and allow-session keep their usual scope (one call / that exact tool name in that session), `auto` runs without a card, and Plan/Goal still deny. Annotations or risk values the MCP server declares about its own tools are ignored and never lower the path. Dispatch, read-only-mode handling and the `mcp_` namespace are unchanged; no host protocol or persistence change. See ADR `mcp-tool-approval-risk` and E2E-MCP-tool-requires-approval.** | MCP tools were auto-allowed as `low` risk, so a configured server could write files, call networks or run commands without any prompt under `ask`. Configuring a server is consent to launch it, not to every action its opaque tools take. | | D641 | Custom endpoint API style precedence | **A custom endpoint uses the saved provider-row `apiStyle` ahead of a model catalog's adapter API. Named and OAuth providers can continue to use a model-level wire API pin where their published configuration requires a different transport. This keeps a user's explicit endpoint choice stable without removing model-specific routing such as OpenCode Go Responses models. No persisted format or protocol change. See E2E-005E and issue #1313.** | A publisher's adapter default must not silently redirect a custom gateway whose user-selected API format is different. | | D642 | Cloud sync is a public Experimental destination *(amended by D643)* | **Remove the developer-mode and packaged-build gates from the Settings `sync` destination: its rail row, page, and settings-search hits exist for every user in every build, and a saved `sync` tab no longer falls back to General. Remote Hosts keeps both gates and its own badge. The destination keeps its Experimental badge on the rail row and page title; sync behavior, protocol, host schema, and persisted data are unchanged. See `04-ux/06-settings-ia.md` and E2E-CONFIG-SYNC-webdav-portable-configuration.** | Encrypted WebDAV backup is the app's only multi-device configuration path, and a developer-mode gate left it undiscoverable for the users who need it. | -| D643 | Cloud sync ships without an Experimental badge | **Amend D642: the Settings `sync` destination drops `experimentalBadgeKey`, and `settings.configSync.experimental` is removed from every bundled locale. Cloud sync stays available to every user in every build. Remote Hosts keeps its own badge and both gates. Sync behavior, protocol, host schema, and persisted data are unchanged. See `04-ux/06-settings-ia.md` and E2E-CONFIG-SYNC-webdav-portable-configuration.** | Cloud sync is the app's shipped multi-device path, so an Experimental label no longer described it and only made the destination look unfinished. | +| D643 | Cloud sync ships without an Experimental badge *(amended by D649)* | **Amend D642: the Settings `sync` destination drops `experimentalBadgeKey`, and `settings.configSync.experimental` is removed from every bundled locale. Cloud sync stays available to every user in every build. Remote Hosts keeps its own badge and both gates. Sync behavior, protocol, host schema, and persisted data are unchanged. See `04-ux/06-settings-ia.md` and E2E-CONFIG-SYNC-webdav-portable-configuration.** | Cloud sync is the app's shipped multi-device path, so an Experimental label no longer described it and only made the destination look unfinished. | | D644 | Portable instruction files have no size cap | **Remove the 32 KiB per-file cap Host enforced on portable instruction files. Global and project instruction content is bounded only by the same portable-entity payload bound every other domain already has, checked when a revision is uploaded and when a remote one is validated. UTF-8 validation, symlink rejection, scope selection, mapping, and approval rules are unchanged. See `03-runtime/22-config-sync.md` §2.** | A 33 KiB project `AGENTS.md` failed the entire capture with `CONFIG_SYNC_LIMIT_EXCEEDED: instruction file is too large`, which the Settings page could only show as a generic backup-size error. | | D450 | Signed macOS GitHub Releases | **Amend D078 / ADR 0022: GitHub tag releases Developer ID-sign, notarize (`notarytool` via electron-builder 26), staple, and Gatekeeper-verify macOS DMG/ZIP before upload, using identity `Developer ID Application: XingYu Liu (DUV63RKYTW)` / team `DUV63RKYTW` from Actions secrets (`CSC_LINK`, `CSC_KEY_PASSWORD`, `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID`). Missing secrets fail the job. Local unsigned packaging without a certificate remains. `workflow_dispatch` may set `sign_macos: false` only for unsigned debug artifacts. Packaged macOS uses in-app `electron-updater` (ZIP + merged `latest-mac.yml`); Linux deb/rpm and Windows portable ZIP stay notify-and-link. No afterPack/afterSign adhoc codesign (ADR 0278).** | Production DMGs must open without a Gatekeeper warning, and signed macOS installs can download and restart into a new tag. See ADR 0289, E2E-196c, E2E-067A. | | D648 | Skill Market pins an acceptable address for mixed direct DNS answers | **Amend ADR 0272: on a direct route, when DNS includes both rejected and acceptable answers, Skill Market selects and pins one acceptable address instead of letting Chromium choose among them. Third-party content prefers a public answer; the benchmark fake-IP is eligible only under the existing opt-in. ULA-only and other non-public-only answers remain blocked. Proxied and unreadable routes keep the existing policy. See ADR 0321 and E2E-SKILL-MARKET-NET-BOUNDARY.** | Dual-stack and transparent-proxy DNS can include an unused synthetic ULA answer beside an address the request can safely use; pinning prevents the rejected address from being dialed while avoiding the false refusal. | +| D649 | Cloud backup stays closed to users | **Amend D642 / D643: the Settings `sync` destination carries `developmentOnly: true` again, so a packaged build omits its rail row, page, and settings-search hits and falls back to General, while development builds keep it. Developer mode stays irrelevant to the destination and it still carries no Experimental badge. Sync behavior, protocol, host schema, and persisted data are unchanged; dropping the flag reopens it for packaged builds. See `04-ux/06-settings-ia.md` and E2E-CONFIG-SYNC-webdav-portable-configuration.** | The encrypted WebDAV backup is not ready to be offered to packaged-build users yet, so it stays implemented but out of the way until it opens. | ## B. Secondary implementation defaults @@ -7420,7 +7421,7 @@ must keep splitting are covered by `markdown-blocks.test.mjs`. available to every user in every build, and a saved `sync` tab no longer falls back to General. Remote Hosts keeps both gates. - The destination kept its Experimental badge on the rail row and page title - at that time (amended by D643). + at that time (amended by D643 and D649). - Covered by `apps/desktop/test/settings-developer-only-destinations.test.mjs` and the Cloud sync probe in `pnpm test:e2e:settings-scroll`. See `04-ux/06-settings-ia.md` and E2E-CONFIG-SYNC-webdav-portable-configuration. @@ -7430,7 +7431,7 @@ must keep splitting are covered by `markdown-blocks.test.mjs`. - D643 amends D642: the Settings `sync` destination drops its `experimentalBadgeKey`, and `settings.configSync.experimental` is removed from every bundled locale. Cloud sync stays available to every user in - every build. + every build (amended by D649). - Remote Hosts keeps its own badge and both gates. Sync behavior, protocol, host schema, and persisted data are unchanged. - Covered by the badge assertions in @@ -7517,3 +7518,18 @@ must keep splitting are covered by `markdown-blocks.test.mjs`. benchmark/ULA cases in `apps/desktop/test/public-https-fetch-route.test.mjs`. See ADR 0321, `05-security/01-security.md` §4.1, and E2E-SKILL-MARKET-NET-BOUNDARY. + +## 2026-10-07 — Cloud backup stays closed to users (D649) + +- D649 amends D642 / D643: the Settings `sync` destination is + development-build-only again. A packaged build omits its rail row, page, and + settings-search hits, and a saved `sync` tab returns to General; development + builds keep the destination, and developer mode still plays no part. +- The destination still carries no Experimental badge, and sync behavior, + protocol, host schema, and persisted data are unchanged. Removing + `developmentOnly: true` from the destination entry reopens it for packaged + builds. +- Covered by `apps/desktop/test/settings-developer-only-destinations.test.mjs` + and `apps/desktop/test/config-sync-settings.test.mjs`; the Cloud sync probe in + `pnpm test:e2e:settings-scroll` runs a development build. See + `04-ux/06-settings-ia.md` and E2E-CONFIG-SYNC-webdav-portable-configuration. diff --git a/docs/zh-CN/spec/03-runtime/01-ipc-protocol.md b/docs/zh-CN/spec/03-runtime/01-ipc-protocol.md index deb892cfc7..86238c7808 100644 --- a/docs/zh-CN/spec/03-runtime/01-ipc-protocol.md +++ b/docs/zh-CN/spec/03-runtime/01-ipc-protocol.md @@ -1860,7 +1860,7 @@ unchanged. See [provider configuration](12-provider-config-schema.md). ## 15. 云配置同步 -设置 → 云同步页面使用以下 Renderer-to-Main 通道;所有通道都会转发到 Host 所有的 `configSync.*` RPC 方法: +设置 → 云同步页面使用以下 Renderer-to-Main 通道;所有通道都会转发到 Host 所有的 `configSync.*` RPC 方法。该页面当前仅在开发构建可见;通道与其 Host 契约不变: | IPC 通道 | Host 方法 | 契约 | |---|---|---| diff --git a/docs/zh-CN/spec/03-runtime/22-config-sync.md b/docs/zh-CN/spec/03-runtime/22-config-sync.md index efc565aafa..b6e0100b33 100644 --- a/docs/zh-CN/spec/03-runtime/22-config-sync.md +++ b/docs/zh-CN/spec/03-runtime/22-config-sync.md @@ -69,6 +69,8 @@ Host 绝不会仅因为 UI flag 被设置就激活暂存的可执行内容。新 ## 5. 设置工作流 +该目的地当前仅在开发构建可见:打包构建会省略「设置 → 云同步」的导轨行、页面与设置搜索命中,而本文描述的 Host 行为不变。 + 设置 → 云同步提供 WebDAV endpoint 凭据、vault 密码、设备标签、服务器兼容模式、类别选择、能力测试、立即同步、解锁、暂停、文件夹映射、批准/拒绝、revision history/restore、vault 密码重新包裹以及断开连接控制。严格 CAS 是默认模式。选择追加式兼容模式会显示持续风险提示,并在保存配置前要求确认;其测试成功提示目录列表支持,而不是条件写支持。Renderer 将 `notConfigured`、`locked`、`upToDate`、`localChangesPending`、`syncing`、`offline`、`unsupportedServer`、`conflict`、`awaitingActivation`、`paused` 和 `error` 显示为不同状态。断开连接会保留本地数据,不会删除远端数据。 重新打开页面时,先用短时的 Renderer 缓存绘制最近一次脱敏的 Host 状态和历史记录,随后在后台刷新 Host。连接草稿(endpoint、用户名、远程目录、设备标签、兼容模式和类别选择)保存在 Renderer 本地存储中,因此离开页面或重载后未完成的表单仍会保留;Host 确认已保存的配置后,草稿会重新标记为已保存。WebDAV 应用密码留在 Host secret store 中,且只对同一 endpoint 和账户复用;Renderer 永远不会存储任一密码,只有新设备或已锁定设备需要打开 vault 时才需要输入 vault 密码。 diff --git a/docs/zh-CN/spec/04-ux/06-settings-ia.md b/docs/zh-CN/spec/04-ux/06-settings-ia.md index e0cdabc9da..f8634cb408 100644 --- a/docs/zh-CN/spec/04-ux/06-settings-ia.md +++ b/docs/zh-CN/spec/04-ux/06-settings-ia.md @@ -34,20 +34,21 @@ 7. **MCP** — Lucide `Server`(智能体连接) 8. **子智能体** — Lucide `Bot`(内置与自建的并行工作智能体) 9. **项目** — Lucide `Archive`(持久项目索引) - 10. **云同步** — Lucide `CloudDownload`(加密的可移植配置备份与双向同步) + 10. **云同步** — Lucide `CloudDownload`(加密的可移植配置备份与双向同步;仅开发构建可见) 11. **远程主机** — Lucide `Globe`(SSH 引导与配对清单;仅开发者模式) 12. **信息** — Lucide `Info`(版本、日志、更新、开发人员) 图标具有装饰性(通过 SVG 默认设置为 `aria-hidden`)并保持单色 带有导轨标签;不要在此处重复使用 refresh/rotate 字形。 - 目录仍是保持相同顺序的可搜索扁平列表。为便于扫描,目的地分为四个带标题 的视觉分组:“偏好”(常规、AI、快捷键)、“智能体”(指令、模型、技能、 - MCP、子智能体)、“工作区”(项目)和“系统”(云同步、远程主机、信息;远程主机仅开发者模式可见)。标题使用柔和 + MCP、子智能体)、“工作区”(项目)和“系统”(云同步、远程主机、信息; + 云同步仅开发构建可见、远程主机仅开发者模式可见)。标题使用柔和 的非交互文字,分组之间只使用留白,不绘制分割线;搜索过滤后,空分组及其 标题一并隐藏。 -- **云同步**是面向所有用户、在任何构建中都可见的常规「系统」目的地:其导轨行、页面 - 和设置搜索命中从不依赖开发者模式,也不会回落到常规。它作为稳定目的地发布: - 导轨行与页面标题都不再带有实验性徽章。 - 同步行为本身没有任何变化。 +- **云同步**暂不对用户开放,是仅开发构建可见的「系统」目的地:其导轨行、页面和设置 + 搜索命中只在开发构建中存在;打包构建会省略它们,停在它的导轨位置会回落到常规。 + 开发者模式不是它的门控,导轨行与页面标题都不带实验性徽章,同步行为本身没有任何 + 变化。移除该目的地的 `developmentOnly` 标记即可对打包构建重新开放。 - **远程主机**是仅开发者可用的实验性目的地:其导轨行、页面和设置搜索命中仅在 `AppSettings.developerMode` 为 `true` 时存在。开发者模式关闭时该行是缺失而不是 禁用,设置搜索不会返回它的命中,导轨位置停在该项时会回落到常规。该行和页面标题 @@ -369,16 +370,17 @@ Token 用量**不是设置目的地**(D335 / ADR 0173)。已完成回合历 - 返回应用程序从导轨固定的底部操作返回聊天外壳 - 仅开发者可用的目的地作为一个整体加入导轨、页面和设置搜索,也同样一起离开: 开发者模式关闭时导轨省略该行,设置搜索不返回它的命中,打开的远程主机页面会 - 返回到常规;云同步是常规目的地,始终可以打开 + 返回到常规;云同步当前仅开发构建可见,打包构建省略它的导轨行、页面和设置搜索 + 命中并回落到常规,开发者模式从不门控它。 ## 4. 验收 1.打开设置隐藏编码应用侧边栏(全页接管) 2. 导轨顶部显示搜索药丸,底部固定返回应用程序操作并与主侧边栏底部图标行同一条线, 并精确显示常规、AI、快捷键、指令、模型、技能、MCP、 - 子智能体、项目、云同步、远程主机和信息(远程主机仅在开发者模式开启时显示), - 并按偏好、智能体、工作区、系统分组。没有 - 用量设置目的地。 + 子智能体、项目、云同步(仅开发构建)、远程主机(仅开发者模式)和信息, + 按此顺序排列;打包构建不显示云同步,并按偏好、智能体、工作区、系统分组。 + 没有用量设置目的地。 3.外观是常规的一部分,没有独立的导轨目的地 4. Providers 是 Agent 的一部分,没有独立的导轨目的地 5. 插件没有设置目的地; app-shell 插件页面支持 diff --git a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md index e992816351..568f5d7de9 100644 --- a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md @@ -1231,6 +1231,7 @@ task-candidate E2E 从请求工作树运行,但使用主工作区已经准备 15) 归档一个项目会话,从项目归档中打开它,然后返回项目归档。 16) 返回应用程序外壳并打开插件。 - **预期**:导轨按顺序包含常规、AI、快捷键、指令、模型、技能、MCP、子智能体、项目、云同步、远程主机和信息,分别使用其语义 Lucide 图标;目的地按“偏好”(常规、AI、快捷键)、“智能体”(指令、模型、技能、MCP、子智能体)、“工作区”(项目)和“系统”(云同步、远程主机、信息)分组。组间使用留白而非分割线,搜索保持目的地结果扁平,并同时隐藏空分组及其标题。模型、技能和 MCP 页分别拥有内嵌的外部导入工作台;打开工作台不会扫描。设置没有会话导入面板,会话导入由插件 API 提供。外观保留在“常规”下,权限、默认项和命令 Shell 行位于全局 AI 下;可用的已选 Shell 由选择器表示,不重复显示“已配置”状态;默认、回退和无实际 Shell 状态仍明确展示;上下文管理没有设置卡;键盘快捷键和全局指令都有自己的目的地;“开发者”位于“信息”下;项目存档显示活动、关闭和存档的持久行,无需可见性切换,将它们分组在一个面板内始终可见的固定/所有项目/存档分组条 (D168/D267) 下,并带有每个部分的计数。该目的地不呈现英雄区块,也没有页面级计数器串:引导区是一条安静的说明行,每个分组条的计数与其呈现的行一致;按名称排序会重新排序每个部分内的行,而不隐藏任何行;搜索匹配项目字段和会话标题并报告匹配计数,会话标题结果扩展其所属项目,按最新活动和相对更新时间列出会话,并以八个批次显示历史记录;清除搜索将恢复完整索引。行菜单在 Escape 和外部按下时关闭。恢复使存档保持打开状态,激活返回以与侧栏中保留的恢复项目聊天。打开归档会话时,应用会先成功选择该会话,再清除它的归档状态;聊天页选中该会话,项目侧边栏重新显示它,返回项目归档后它也不再处于归档状态。主页侧边栏和全局页面结果没有独立的项目目标;设置搜索找到项目存档;插件仍然是一个独立的应用程序外壳目的地。 + 打包构建会从导轨和设置搜索中省略云同步;见 `04-ux/06-settings-ia.md`。 - **链接规格**:`04-ux/06-settings-ia.md`、`04-ux/01-ui-ia.md`、`03-runtime/11-provider-model-system.md` - **接受**:B(模型配置),F(项目存档与持久化) - **里程碑**:M5 @@ -5929,7 +5930,8 @@ eleven-tool-round desktop paths are verified by - 打开设置(页脚配置文件 → 设置)。 - 预计**全页**设置(无应用程序 sidebar/nav)。左导轨有返回应用、搜索,以及常规、AI、 快捷键、指令、模型、技能、MCP、子智能体、项目、云同步、远程主机、信息, - 按该顺序排列;内容窗格显示所选设置页或项目存档。 + 按该顺序排列(打包构建不显示云同步);内容窗格显示所选设置页或 + 项目存档。 - 返回应用程序外壳并期望插件保持独立 侧边栏页脚目的地。 - 将空的 46 像素顶带拖到导轨或内容窗格上;本地人 @@ -6113,7 +6115,7 @@ eleven-tool-round desktop paths are verified by - 在约 1200×690 的窗口中打开浅色主题的“设置 → 常规”。 - 侧栏宽度约 275 像素,背景为 `#f4f4f4`,常规项为唯一选中项,并显示返回和搜索。 - 主题选择器可正常使用,且没有无效开关或打开目标行。 -- “常规”页显示外观卡片,“AI”页显示权限和默认值卡片。核心侧栏依次包含常规、AI、快捷键、指令、模型、技能、MCP、子智能体、项目、云同步和信息;仅开发者模式显示远程主机。开发版且开启开发者模式时,AI 与快捷键之间可能显示语音;插件扩展目的地位于核心分组之后。侧栏不再有“导入”入口。 +- “常规”页显示外观卡片,“AI”页显示权限和默认值卡片。核心侧栏依次包含常规、AI、快捷键、指令、模型、技能、MCP、子智能体、项目、云同步(仅开发构建)和信息;仅开发者模式显示远程主机。开发版且开启开发者模式时,AI 与快捷键之间可能显示语音;插件扩展目的地位于核心分组之后。侧栏不再有“导入”入口。 - 在 800、1200 和 1600 像素宽度之间调整窗口;内容卡片填充 每种尺寸都可用右窗格,无需更换导轨或引入 水平滚动。 @@ -8866,13 +8868,13 @@ the latest destination. These assertions measure work counts, not device FPS. ### E2E-CONFIG-SYNC-webdav-portable-configuration -- **前提:** 已构建的任务候选版本、隔离的 Host 配置(开发者模式默认关闭,用来验证公开入口),以及支持 strong ETag 和条件 PUT 的本地 WebDAV fixture,另有忽略条件头但支持 `PROPFIND` 目录列举的 fixture 变体。不使用真实 WebDAV 账户、provider 或生产桌面。 -- **步骤:** 1)在开发者模式关闭的情况下打开设置,确认“云同步”出现在导轨中且设置搜索能命中它;打开该页,确认导轨行和页面标题都不再带实验性徽章。2)开关一次开发者模式,确认该目的地始终可用且可见性不变。3)填写 fixture URL、设备标签、目录和备份密码。4)运行能力测试,确认使用临时对象。5)选择 provider/MCP/skill 类别,保持凭据和 memory 未选中;在第二次预览中启用凭据,确认只显示脱敏计数。6)配置设备 A,创建 user provider 和 MCP 定义并同步。7)让设备 B 连接同一 vault,同步后检查待激活/映射,并验证审批前不会运行命令或任务。8)批准一个变更后的安全实体,拒绝一个暂存实体,在两台设备上编辑不相交设置并再次同步。9)测试并发 head writer、错误密码、weak ETag、密文损坏、redirect、归档路径穿越和网络中断。10)使用回环/私有地址的 fixture 勾选“允许在受信任的内网地址使用 HTTP”,确认刷新状态后仍保留;即使勾选,公网 HTTP 地址也必须被拒绝。 -- **预期:** 云同步在任何构建、任何开发者模式设置下都可访问,导轨行与页面标题都不带实验性徽章;开关开发者模式既不改变入口可见性也不改变同步行为。测试拒绝不可靠的条件写入。HTTP 默认关闭,仅允许 localhost、`.local` 或私有/链路本地地址;公网 HTTP 地址会被拒绝,界面会提示凭据暴露风险。若 fixture 探测到该 endpoint 对不存在对象返回 502,后续只兼容该 endpoint 的这一行为;忽略条件头的服务器仍必须标记为不受支持。WebDAV 只能看到已认证的密文和不透明对象名;原始秘密不会出现在 Renderer 状态或日志中。相同和不相交的编辑会收敛,冲突保持可审查,明确删除使用 tombstone,类别退出不是删除,可执行导入在本地审批和映射前保持不激活。恢复不会暴露部分应用的本地配置。 +- **前提:** 已构建的任务候选版本、隔离的 Host 配置(开发者模式默认关闭,用来验证开发构建下的目的地),以及支持 strong ETag 和条件 PUT 的本地 WebDAV fixture,另有忽略条件头但支持 `PROPFIND` 目录列举的 fixture 变体。不使用真实 WebDAV 账户、provider 或生产桌面。 +- **步骤:** 1)在开发构建中、开发者模式关闭的情况下打开设置,确认“云同步”出现在导轨中且设置搜索能命中它;打开该页,确认导轨行和页面标题都不带实验性徽章。2)开关一次开发者模式,确认该目的地始终可用且可见性不变。3)填写 fixture URL、设备标签、目录和备份密码。4)运行能力测试,确认使用临时对象。5)选择 provider/MCP/skill 类别,保持凭据和 memory 未选中;在第二次预览中启用凭据,确认只显示脱敏计数。6)配置设备 A,创建 user provider 和 MCP 定义并同步。7)让设备 B 连接同一 vault,同步后检查待激活/映射,并验证审批前不会运行命令或任务。8)批准一个变更后的安全实体,拒绝一个暂存实体,在两台设备上编辑不相交设置并再次同步。9)测试并发 head writer、错误密码、weak ETag、密文损坏、redirect、归档路径穿越和网络中断。10)使用回环/私有地址的 fixture 勾选“允许在受信任的内网地址使用 HTTP”,确认刷新状态后仍保留;即使勾选,公网 HTTP 地址也必须被拒绝。 +- **预期:** 云同步在开发构建中、开发者模式关闭时即可访问,打包构建的导轨、页面和设置搜索都不含它,且导轨行与页面标题都不带实验性徽章;开关开发者模式既不改变入口可见性也不改变同步行为。测试拒绝不可靠的条件写入。HTTP 默认关闭,仅允许 localhost、`.local` 或私有/链路本地地址;公网 HTTP 地址会被拒绝,界面会提示凭据暴露风险。若 fixture 探测到该 endpoint 对不存在对象返回 502,后续只兼容该 endpoint 的这一行为;忽略条件头的服务器仍必须标记为不受支持。WebDAV 只能看到已认证的密文和不透明对象名;原始秘密不会出现在 Renderer 状态或日志中。相同和不相交的编辑会收敛,冲突保持可审查,明确删除使用 tombstone,类别退出不是删除,可执行导入在本地审批和映射前保持不激活。恢复不会暴露部分应用的本地配置。 - **规格:** `04-ux/06-settings-ia.md`、`03-runtime/22-config-sync.md`、`03-runtime/14-secrets-storage.md`、`05-security/01-security.md`、ADR 0300。 - **验收:** F(持久化)、Security、Quality。 - **里程碑:** M6+。 -- **状态:** Draft;合并/密码学和进程内 WebDAV 条件写入覆盖已存在。云同步入口对所有用户可见(不再受开发者模式门控)由 `apps/desktop/test/settings-developer-only-destinations.test.mjs` 断言;完整双设备进程路径和逐检查点本地恢复故障注入仍待自动化。 +- **状态:** Draft;合并/密码学和进程内 WebDAV 条件写入覆盖已存在。云同步仅开发构建可见、打包构建省略该目的地由 `apps/desktop/test/settings-developer-only-destinations.test.mjs` 断言;完整双设备进程路径和逐检查点本地恢复故障注入仍待自动化。 ### E2E-DIALOG-long-text-boundaries @@ -9164,10 +9166,12 @@ the latest destination. These assertions measure work counts, not device FPS. AI:插件页关闭,目标项在下一次绘制前可见,消费锚点后保持定位。没有锚点的 外部切页也会离开插件,并从顶部开始。 - 在明暗两种主题下运行。 -- 自动化覆盖:`pnpm test:e2e:settings-scroll` 在隔离 Electron 中挂载真实 - SettingsPage、store、翻译和构建后的 CSS。仅 preload 数据使用 fixture; - 搜索导航调用 SearchDialog 使用的公开 store 入口。该测试覆盖渲染层交互, - 不覆盖 host 持久化或完整全局搜索弹窗。 +- 自动化覆盖:`pnpm test:e2e:settings-scroll` 在隔离 Electron 中挂载真实的 + SettingsPage、store、翻译和构建后的 CSS,探针运行的是开发构建。仅 preload + 数据使用 fixture;搜索导航调用 SearchDialog 使用的公开 store 入口。该测试还 + 检查云同步仍是仅开发构建可见的目的地(无开发者模式门控、无实验性徽章),远程 + 主机保留其徽章,以及回落到常规。该测试覆盖渲染层交互,不覆盖 host 持久化或 + 完整全局搜索弹窗。 ### E2E-SCHEDULED-dispatch diff --git a/docs/zh-CN/spec/07-plugins/07-plugin-marketplace.md b/docs/zh-CN/spec/07-plugins/07-plugin-marketplace.md index e1ae2ff28f..aa44ce3d3d 100644 --- a/docs/zh-CN/spec/07-plugins/07-plugin-marketplace.md +++ b/docs/zh-CN/spec/07-plugins/07-plugin-marketplace.md @@ -33,7 +33,7 @@ ### B阶段✅ - Browse/search + 下载安装是针对官方提供商实施的 -- 官方提供商是插件中心 `plugins.aiuo.net`(B 阶段最初指向 GitHub 仓库 `vastsa/pi-desktop-plugins`;见下方「目录来源选择」与 [ADR 0276](../../adr/0276-official-plugin-channel-and-backup-channels.md)) +- 官方提供商是插件中心 `plugins.aiuo.net`(B 阶段最初指向 GitHub 仓库 `vastsa/pi-desktop-plugins`;见下方「目录来源选择」与 [ADR 0276](/adr/0276-official-plugin-channel-and-backup-channels)) - 默认目录 URL:`https://plugins.aiuo.net/catalog.json` - 包 URL 可以是绝对 `https://` / `http://` / `file://`,或相对路径:目录声明了 `artifactBaseUrl` 时按它解析,否则按目录 URL 解析 - HTTPS 获取在 host-core 中使用 `curl` diff --git a/docs/zh-CN/spec/08-meta/decisions-log.md b/docs/zh-CN/spec/08-meta/decisions-log.md index cf210b47a7..a3d0726a9f 100644 --- a/docs/zh-CN/spec/08-meta/decisions-log.md +++ b/docs/zh-CN/spec/08-meta/decisions-log.md @@ -40,9 +40,10 @@ | D640 | 用户 MCP 工具保持常规审批路径 | **host-core 将 `mcp__` 调用视为 `medium` 风险:在 `ask` 与 `accept-edits` 下每次调用都显示审批卡片("MCP server tool requires approval"),允许一次与本会话允许保持原有范围(单次调用 / 该会话内同一工具名),`auto` 不显示卡片直接执行,Plan/Goal 仍然拒绝。MCP 服务器对自身工具声明的标注或风险值被忽略,绝不降低审批路径。分发、只读模式处理与 `mcp_` 命名空间不变;不改主机协议或持久化。见 ADR `mcp-tool-approval-risk` 与 E2E-MCP-tool-requires-approval。** | MCP 工具此前按 `low` 风险自动放行,已配置的服务器在 `ask` 下可以不经提示写文件、访问网络或执行命令。配置服务器意味着同意启动它,而不是同意其不透明工具的每一个操作。 | | D641 | 自定义端点 API 格式优先级 | **自定义端点始终优先使用 provider 行上保存的 `apiStyle`,再考虑模型目录适配器 API。对于具名与 OAuth provider,如果已发布配置要求不同传输,仍可沿用模型级 wire API 固定项。这可确保用户为自定义 endpoint 选择的格式不会被静默覆盖,同时保留 OpenCode Go Responses 模型等特定路由。不改变持久化格式或协议。见 E2E-005E 与 issue #1313。** | 发布方的适配器默认值不应把请求从用户已选择的自定义网关格式静默重定向。 | | D642 | 云同步是对所有用户开放的实验性目的地 *(由 D643 修订)* | **移除设置中 `sync` 目的地的开发者模式与打包构建门控:其导轨行、页面和设置搜索命中在任何构建中对所有用户存在,已保存的 `sync` 标签页也不再回落到常规。远程主机保留这两道门控和它自己的徽章。该目的地继续在导轨行与页面标题上保留实验性徽章;同步行为、协议、Host schema 与持久化数据均不变。见 `04-ux/06-settings-ia.md` 与 E2E-CONFIG-SYNC-webdav-portable-configuration。** | 加密 WebDAV 备份是应用唯一的多设备配置路径,而开发者模式门控让需要它的用户无法发现该功能。 | -| D643 | 云同步不再带实验性徽章 | **修订 D642:设置中的 `sync` 目的地不再有 `experimentalBadgeKey`,各内置语言包中的 `settings.configSync.experimental` 键也已删除。云同步在任何构建中对所有用户保持可用。远程主机保留自己的徽章和两道门控。同步行为、协议、Host schema 与持久化数据均不变。见 `04-ux/06-settings-ia.md` 与 E2E-CONFIG-SYNC-webdav-portable-configuration。** | 云同步是应用已发布的多设备路径,实验性标签已不再描述它,只会让该目的地看起来尚未完成。 | +| D643 | 云同步不再带实验性徽章 *(由 D649 修订)* | **修订 D642:设置中的 `sync` 目的地不再有 `experimentalBadgeKey`,各内置语言包中的 `settings.configSync.experimental` 键也已删除。云同步在任何构建中对所有用户保持可用。远程主机保留自己的徽章和两道门控。同步行为、协议、Host schema 与持久化数据均不变。见 `04-ux/06-settings-ia.md` 与 E2E-CONFIG-SYNC-webdav-portable-configuration。** | 云同步是应用已发布的多设备路径,实验性标签已不再描述它,只会让该目的地看起来尚未完成。 | | D644 | 便携指令文件没有体积上限 | **移除 Host 对便携指令文件施加的 32 KiB 单文件上限。全局与项目指令内容只受其他域同样拥有的便携实体负载上限约束,并在上传修订与校验远端修订时检查。UTF-8 校验、symlink 拒绝、作用域选择、映射与审批规则均不变。见 `03-runtime/22-config-sync.md` §2。** | 一个 33 KiB 的项目 `AGENTS.md` 会让整次采集以 `CONFIG_SYNC_LIMIT_EXCEEDED: instruction file is too large` 失败,而设置页只能把它显示为泛化的备份体积错误。 | | D648 | 混合直接 DNS 结果时固定使用可接受地址 | **修订 ADR 0272:在直连路由上,当 DNS 同时包含被拒绝与可接受的结果时,技能市场会选择并固定到一个可接受地址,而不会让 Chromium 在这些地址中自行选择。第三方内容优先使用公网地址;仅在现有策略允许时使用 `benchmark` 假 IP。仅返回 ULA 或其他非公网地址时仍会拦截。代理与无法读取的路由保持现有策略。见 ADR 0321 与 E2E-SKILL-MARKET-NET-BOUNDARY。** | 双栈与透明代理 DNS 可能在可安全使用的地址旁返回未使用的合成 ULA 地址;固定已通过校验的地址可避免连接到被拒绝结果并消除误拦截。 | +| D649 | 云备份暂不对外开放 | **修订 D642 / D643:设置中的 `sync` 目的地重新带上 `developmentOnly: true`,因此打包构建会省略其导轨行、页面和设置搜索命中并回落到常规,开发构建则保留该目的地。开发者模式与它无关,它仍不带实验性徽章。同步行为、协议、Host schema 与持久化数据均不变;移除该标记即可对打包构建重新开放。见 `04-ux/06-settings-ia.md` 与 E2E-CONFIG-SYNC-webdav-portable-configuration。** | 加密 WebDAV 备份尚未准备好提供给打包构建用户,因此先保持已实现但不出现在界面上,直到正式开放。 | | D450 | 签名的 macOS GitHub Release | **修订 D078 / ADR 0022:GitHub tag 发布使用身份 `Developer ID Application: XingYu Liu (DUV63RKYTW)` / 团队 `DUV63RKYTW`,通过 Actions 密钥(`CSC_LINK`、`CSC_KEY_PASSWORD`、`APPLE_ID`、`APPLE_APP_SPECIFIC_PASSWORD`、`APPLE_TEAM_ID`)对 macOS DMG/ZIP 做 Developer ID 签名、`notarytool` 公证、装订和 Gatekeeper 校验;缺少密钥则失败。无证书的本地未签名打包仍可用。`workflow_dispatch` 仅可把 `sign_macos: false` 用于未签名调试产物。打包的 macOS 走应用内 `electron-updater`(ZIP + 合并后的 `latest-mac.yml`);Linux deb/rpm 与 Windows 便携版 ZIP 仍为通知并打开发布页。禁止 afterPack/afterSign adhoc 签名(ADR 0278)。** | 正式 DMG 应无需 Gatekeeper 警告即可打开,已签名 macOS 安装可下载并重启到新 tag。见 ADR 0289、E2E-196c、E2E-067A。 | ## B. 辅助实现默认值 @@ -5225,7 +5226,7 @@ Markdown 源码,不是 `text/html` 负载;对禁用行内 HTML 的外部编 命中在任何构建中对所有用户可用,已保存的 `sync` 标签页也不再回落到常规。远程主机 保留这两道门控。 - 该目的地当时在导轨行和页面标题上保留实验性徽章 - (已由 D643 修订)。 + (已由 D643 与 D649 修订)。 - 由 `apps/desktop/test/settings-developer-only-destinations.test.mjs` 与 `pnpm test:e2e:settings-scroll` 的云同步探针覆盖。见 `04-ux/06-settings-ia.md` 与 E2E-CONFIG-SYNC-webdav-portable-configuration。 @@ -5234,7 +5235,7 @@ Markdown 源码,不是 `text/html` 负载;对禁用行内 HTML 的外部编 - D643 修订 D642:设置中的 `sync` 目的地不再有 `experimentalBadgeKey`, 各内置语言包中的 `settings.configSync.experimental` 键也已删除。云同步在任何构建、 - 对所有用户都保持可用。 + 对所有用户都保持可用(已由 D649 修订)。 - 远程主机保留自己的徽章和两道门控。同步行为、协议、Host schema 与持久化数据均不变。 - 由 `apps/desktop/test/settings-developer-only-destinations.test.mjs`、 `apps/desktop/test/config-sync-settings.test.mjs` 中的徽章断言,以及 @@ -5285,3 +5286,15 @@ Markdown 源码,不是 `text/html` 负载;对禁用行内 HTML 的外部编 - 由 `apps/desktop/test/public-https-fetch-route.test.mjs` 中的固定地址传输集成测试、 公网/ULA 混合结果与 `benchmark`/ULA 测试覆盖。见 ADR 0321、`05-security/01-security.md` §4.1 与 E2E-SKILL-MARKET-NET-BOUNDARY。 + +## 2026-10-07 —— 云备份暂不对外开放(D649) + +- D649 修订 D642 / D643:设置中的 `sync` 目的地再次仅开发构建可见。打包构建会省略其 + 导轨行、页面和设置搜索命中,已保存的 `sync` 标签页会回落到常规;开发构建保留该目的地, + 开发者模式在其中不起作用。 +- 该目的地仍不带实验性徽章,同步行为、协议、Host schema 与持久化数据均不变。从目的地 + 条目上移除 `developmentOnly: true` 即可对打包构建重新开放。 +- 由 `apps/desktop/test/settings-developer-only-destinations.test.mjs` 与 + `apps/desktop/test/config-sync-settings.test.mjs` 覆盖,`pnpm test:e2e:settings-scroll` + 的云同步探针跑的是开发构建。见 `04-ux/06-settings-ia.md` 与 + E2E-CONFIG-SYNC-webdav-portable-configuration。 diff --git a/scripts/e2e/settings-scroll.jsx b/scripts/e2e/settings-scroll.jsx index 789130c5d2..11264c843b 100644 --- a/scripts/e2e/settings-scroll.jsx +++ b/scripts/e2e/settings-scroll.jsx @@ -120,10 +120,13 @@ async function setSettingsSearch(value) { await settle(); } async function checkCloudSyncVisibility() { - // Cloud sync is a regular destination: no developer mode and no badge. + // Cloud sync is still a development-build surface: this harness compiles the + // renderer with `import.meta.env.DEV` true, so the not-yet-open cloud backup + // is expected here. Packaged builds omit the destination, which the + // settings-search unit tests pin; developer mode never gated it. await setSettingsSearch("Cloud sync"); const syncButton = navButton("Cloud sync"); - assert(syncButton, "Cloud sync must appear in settings search without developer mode"); + assert(syncButton, "Cloud sync must appear in settings search in a development build"); assert( !syncButton.querySelector(".settings-nav-experimental"), "Cloud sync's rail entry must not carry the Experimental badge", @@ -133,7 +136,7 @@ async function checkCloudSyncVisibility() { await settle(); assert( useAppStore.getState().settingsTab === "sync", - "Cloud sync must open its page without developer mode", + "Cloud sync must open its page in a development build", ); assert( !document.querySelector(".settings-section-title")?.textContent?.includes("Experimental"), @@ -141,7 +144,7 @@ async function checkCloudSyncVisibility() { ); await setSettingsSearch(""); - // Developer mode no longer gates this destination, so it cannot hide the page. + // Developer mode does not gate this destination, so it cannot hide the page. settings = { ...settings, developerMode: true }; flushSync(() => useAppStore.setState({ settings })); await settle();