Commit Graph

16 Commits

Author SHA1 Message Date
saltand 8b0cb3e68b fix(cloud-sync): confirm provider disconnect before stopping sync 2026-08-28 16:55:13 +08:00
陈大猫 b1a0a26d01 feat(settings): 设置全局搜索(可展开联想跳转) (#2816)
Closes #2616
Closes #2683

Add expandable settings search with autocomplete jump-to-anchor navigation across tabs (including AI/Sync sub-tabs).
2026-08-08 02:24:57 +00:00
陈大猫 904af93a1d fix(credentials): 防止解密失败后二次加密污染本地与云端 (#2702) (#2770)
* fix(credentials): stop double-encrypting undecryptable safeStorage blobs

When local decrypt failed (e.g. OSCrypt key churn after reboot), encrypt
used to wrap the leftover enc:v1 ciphertext again, permanently poisoning
the vault. Startup sync could also push those placeholders to cloud and
make download restore the same poison.

- Keep real enc:v1 blobs unchanged on encrypt (header check, no wrap)
- Strip device-bound placeholders when applying portable sync payloads
- Guard startup local-wins / merge round-trips before upload
- Skip vault init re-encrypt writes when secrets are still undecrypted

Fixes #2702

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(sync): strip enc:v1 secrets from smart-merge uploads

Bugbot found that after local apply sanitized placeholders, legacy v1
smart-merge could still decrypt an unstripped remote, merge it, and
re-upload the poison. Strip device-bound credentials on remote decrypt
and again on the merged payload before upload.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(credentials): address Codex P2 review on #2702

- Always re-encrypt vault init batches so plaintext siblings are not
  left unprotected when one record is a stale enc:v1 placeholder
- Sanitize device-bound secrets before convergent restore prepare so
  CRDT replica commit matches vault import
- Require a complete safeStorage blob (header + min 31 bytes) before
  treating enc:v1 as ciphertext; encrypt header-only coincidences

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(credentials): address Codex P1 review on #2702

- Decode enc:v1 payloads with atob in the renderer-safe domain helper
- Heal poisoned remote secrets from local/base before smart-merge so
  good credentials are not discarded as remote-only deletions
- Keep post-merge strip so leftover enc:v1 never uploads

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(credentials): address Codex P1 CBC min and startup heal

- Accept 19-byte v10/v11 CBC OSCrypt blobs (not only 31-byte GCM)
- Heal poisoned remote secrets before startup smart-merge
- Strip unresolved placeholders on merge round-trip upload

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(sync): sanitize payloads at every apply/commit boundary

Codex P1: vault apply stripped enc:v1 while commitRemoteInspection and
convergent materialization could keep the poison. Sanitize merged/remote
payloads before apply+base commit, strip on CRDT materialization, and
sanitize convergent apply inputs.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(sync): heal local and remote secrets before smart-merge

Smart-merge could select a locally-changed entity whose only secret
delta was enc:v1 poison, then strip and upload empty secrets. Heal
both sides from the opposite payload/base before merge on all sync
paths.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(sync): keep enc:v1 intact during convergent materialize

Stripping device-bound secrets inside materializeSyncPayloadFromConvergentState
broke envelope validation for poisoned-but-consistent v2 snapshots, so decrypt
could not hydrate the clouds #2702 needs to recover. Portable stripping stays
at apply/upload boundaries.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(credentials): detect Windows DPAPI by decoded header bytes

Real DPAPI blobs start with 01 00 00 00 d0 8c... and base64-encode as
AQAAANCM..., so the previous AQAAAA string prefix rejected them and
allowed double-wrapping after key rotation. Match decoded headers in
both the main-process bridge and renderer predicate.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* test(credentials): use complete enc:v1 fixtures for stricter detector

Short placeholders like enc:v1:djEwAAAA no longer pass the platform
header + minimum-size checks. Update auth/SFTP/proxy/sync fixtures to
full v10-shaped blobs so npm test matches production validation.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(sync): treat preferred credential deletions as authoritative

When healing enc:v1 before merge, an empty/missing secret on a present
preferred entity is an intentional clear and must not be revived from
base. Only fall back to base when preferred is absent or also poisoned.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(credentials): reject impossible v10/v11 ciphertext lengths

Accept only CBC-aligned sizes (19+16n) or GCM-sized blobs (>=31) so
coincidental enc:v1 plaintext of intermediate length is encrypted instead
of treated as an undecryptable placeholder.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(credentials): require full DPAPI provider GUID signature

Accept Windows safeStorage blobs only when they start with version
01 00 00 00 plus provider GUID df9d8cd0-1501-11d1-8c7a-00c04fc297eb,
so coincidental 01 00 00 00 prefixes are not treated as ciphertext.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
2026-08-06 13:17:24 +08:00
陈大猫 2ea5efacba fix(plugins): address residual Codex sync secret and backup gaps (#2748)
* fix(plugins): address residual Codex sync secret and backup gaps

Retain provider→plugin secret bindings so disconnect can wipe credentials
after a plugin is disabled, keep overwritten secrets on failed reconnect,
and snapshot live-empty sidecars in protective backups.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(plugins): restore overwritten sync secrets on rejected reconnect

Stash prior plaintext and SecretRef id on overwrite, restore them when
connect fails, and discard the stash after a successful reconnect so
saved provider credentials keep resolving.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(plugins): make sync secret overwrite stash opt-in

Only the syncPutSecret path stashes prior plaintext/SecretRef so ordinary
secrets.set overwrites do not leave credentials in process memory.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(plugins): host-own sync provider bindings and clear failed stashes

Move provider→plugin bindings into a dedicated DB table outside
plugin-writable secrets, validate provider namespace ownership, and drop
overwrite stashes when replacement writes fail.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(plugins): keep overwrite stash until restore write succeeds

Only drop the prior plaintext after encrypt+upsert succeeds so a failed
restore can still retry and recover the saved SecretRef.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(plugins): preserve overwrite stash across retries and prefix deletes

Do not replace an existing restore stash on later puts, and clear matching
stash entries when deleteByKeyPrefix wipes sync credentials on disconnect.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
2026-08-05 19:44:35 +08:00
陈大猫 13fa68a720 feat(plugins): add sync providers and encrypted sidecar sync (#2713)
* feat(plugins): add sync providers and encrypted sidecar sync

Implement PR 8 of the plugin platform (#2269): namespaced sync Providers
with provider.sync, encrypted-object storage operations, WebDAV through the
shared storage surface, dynamic cloud provider IDs, and non-cascade sidecars
for sync:true plugin settings plus account/CRDT baselines.

Related to #2269

* fix(plugins): wire sync providers and sidecars into production paths

Close PR 8 integration gaps: dynamic plugin provider registry and
getConnectedAdapter createPluginStorage, WebDAV through EncryptedObjectStorage,
cloud payload collect/apply for pluginSidecars, and IPC/preload bridges.

Related to #2269

* fix(plugins): preserve WebDAV auth reuse and resourceId through EOS wrap

encryptedObjectStorageAsCloudAdapter now reports initiallyAuthenticated when
config exists (matching raw WebDAVAdapter) so getConnectedAdapter reuses the
cached instance, and preserves/refreshes resourceId from the backing adapter
instead of always forcing the default object key.

Related to #2269

* fix(plugins): address Codex PR 8 review findings

Carry pluginSidecars through merge/convergent uploads, lazy-connect before
object I/O with revision-aware writes, fail closed on sidecar collect errors,
expose connectPluginProvider, preserve remote timestamps, and drop deleted
installed-plugin settings from collection.

Related to #2269

* fix(plugins): close remaining Codex P1/P2 sync issues

Lazy stream cancel for inline sync reads, must-not-exist conditional writes,
three-way sidecar merge for local resets, validated apply path for settings,
and merge remote sidecars into convergent uploads.

Related to #2269

* fix(plugins): harden sidecar collect/apply and write verification

Preserve last-known sidecars when the host is offline, apply remote
deletions for installed plugins, force convergent upload on sidecar
divergence, verify plugin object writes, and pick plugin providers in
auto-sync startup checks.

Related to #2269

* fix(plugins): close remaining Codex sidecar/sync path gaps

Include sidecars in auto-sync payload and hash, keep explicit empty bundles,
fail apply when host is offline, require getCapabilities, and carry merged
sidecars into convergent local apply/upload decisions.

Related to #2269

* fix(plugins): close Codex P1 sidecar collect/apply/merge gaps

Treat null host responses as unavailable, persist collected settings into
the non-cascade table, retain decoded convergent payloads, use three-way
sidecar merge for deletions, and fail apply only on operational errors.
Also treat non-empty plugin sidecars as meaningful sync data and re-run
auto-sync when plugin contributions change.

Related to #2269

* fix(plugins): close remaining Codex P1 sidecar and provider gaps

Replay offline-cached sidecars before collect, keep explicit empty
sidecar fields after three-way merge, thread sidecars through convergent
conflict/downgrade materialization, keep gated-off plugin providers from
joining sync as connected, and rebind plugin EOS sessions after runtime
replacement.

Related to #2269

* fix(plugins): separate pending remote sidecars from last-known cache

Replay only host-offline remote applies into the DB, keep last-known as
upload fallback, use contribution reset for deleted settings, probe real
plugin host readiness, rebind storage events for plugin providers, clear
plugin bases on reset, and swallow disconnect rejections.

Related to #2269

* fix(plugins): hydrate retained sidecars and gate missing providers

Materialize installed-plugin settings from retained sidecars before
collect pruning, clear sidecars on setting reset, fail closed when
pending remote replay fails, and only restore plugin providers that are
contribution-available. Also initialize dynamic provider counters on
connect.

Related to #2269

* fix(plugins): surface plugin sync providers and preserve conflict sidecars

Collect live sidecars before convergent conflict apply/upload, include
dynamic providers in status and disconnect paths, and list plugin sync
providers in Cloud Sync settings for connect/disconnect.

Related to #2269

* fix(plugins): per-provider sidecar baselines and safer collect/UI

Merge convergent sidecars against each provider's own baseline, keep
retained settings in sidecars without unvalidated plugin_settings writes,
guard isConnectDisabled for never-connected plugins, and fall back plugin
provider labels/icons in the status popup.

Related to #2269

* fix(plugins): correct plugin provider discovery and sidecar reverify

Read nested contribution metadata for sync provider cards, gate empty-config
connects, enforce single-provider disconnect for plugins, and re-verify after
sidecar-only convergent uploads so baselines track the post-write payload.

Related to #2269

* fix(plugins): refresh sync provider availability from live contributions

Replace the contribution-available provider ID set when plugins change so
disabled or uninstalled sync providers leave the auto-sync ready set and
drop to disconnected with config retained.

Related to #2269

* fix(plugins): preserve sidecars in backups, pending queue, and migration

Attach last-known plugin sidecars to protective local vault backups, fail
operationally when pending/last-known storage writes are rejected, carry
sidecars through convergent migration publish, and reject trailing garbage
on encrypted object parse.

Related to #2269

* fix(plugins): abort conflict collect failures and merge migration sidecars

Rethrow operational sidecar collection errors during convergent conflict
resolution, LWW-union all local/provider sidecars during convergent
migration, re-collect last-known after apply, and prefer live host collect
for protective local vault backups.

Related to #2269

* fix(plugins): restore pluginBridge tests, fix import path, union downgrade sidecars

Correct SettingsSyncTab dynamic import path so production builds resolve,
add ipcMain.on to the plugin bridge test double, union all provider
sidecars on convergent downgrade, keep empty last-known resets, and skip
force-delete when contribution reset validation rejects.

Related to #2269

* fix(plugins): abort backup collect failures and honor empty remote wipes

Only fall back to last-known sidecars when the host is unavailable during
protective backups, treat explicit empty remote sidecar bundles as
authoritative wipes, remove lint-blocking anys from harness tests, and
use a stable equal-timestamp sidecar tie-break.

Related to #2269

* fix(plugins): include sidecars in equality, preserve merge base on reconnect

Compare pluginSidecars in cloudSyncPayloadsEqual for migration freshness,
clear plugin merge state only when account/resource changes on reconnect,
cancel accepted sync read streams on failure, and stop re-connecting the
plugin EOS adapter on every I/O.

Related to #2269

* fix(plugins): drop cached adapters on contribution refresh

Invalidate plugin sync adapters when live contributions change so a
restarted runtime gets a fresh connect, take offline any non-disconnected
missing providers, and latch version-change backups only after cancellation
checks pass.

Related to #2269

* fix(plugins): preserve omitted sidecars and hydrate on plugin enable

Treat missing pluginSidecars as legacy/unsupported in three-way merge
(only explicit empty is a wipe), hydrate retained settings when a plugin
enables, and re-enable retained provider configs when contributions return.

Related to #2269

* fix(plugins): keep newer retained sidecars over older stored settings

When collection merges existing sidecars with plugin_settings rows, prefer
the newer updatedAt so a schema-rejected remote value retained only in
the sidecar table is not overwritten by a stale local setting.

Related to #2269

* fix(plugins): seal plugin provider config and clear base on config change

Encrypt opaque plugin provider configuration before persistence, clear
merge state when reconnect configuration changes, and stream sync objects
below a base64-safe inline threshold so control-plane JSON stays in budget.

Related to #2269

* fix(plugins): seal plugin configs by provider id and hydrate before start

Encrypt plugin provider configuration only for non-builtin provider IDs
so field-name collisions cannot skip sealing, hydrate retained sidecars
before plugin enable starts the runtime, and stop tearing down live
adapters on no-op availability refreshes.

Related to #2269

* fix(plugins): seal scalar configs, safe inline reads, stable config fingerprint

Encrypt any non-builtin plugin configuration JSON shape, enforce the
base64-safe inline read cutoff, and compare reconnect configurations with
order-independent fingerprints so merge bases are not cleared spuriously.

Related to #2269

* fix(plugins): thread sync credentials and harden availability checks

Pass SyncConnectPayload.credential through the object-storage and IPC host
path, treat only null/undefined config as missing, and avoid wiping the
plugin provider availability catalog on transient discovery failures.

Related to #2269

* fix(plugins): unblock schema-required sync connect and scalar configs

Add a JSON config dialog for plugin sync providers that declare a
required configurationSchema, reuse retained configs on reconnect, and
treat falsy scalar configs as present in readiness and adapter lookup.

Related to #2269

* fix(plugins): rebind sync adapters on contribution refresh

Drop cached plugin sync adapters when live contributions still include
the provider so replaced runtimes get a fresh connect, and seal falsy
scalar plugin configs with nullish presence checks end-to-end.

Related to #2269

* fix(plugins): apply sidecars under current declared setting scopes

When a plugin update changes a syncable setting scope, write under the
live declaration and drop the obsolete scope row so collection does not
republish duplicates.

Related to #2269

* fix(plugins): seal config envelopes and stabilize provider counters

Use an unambiguous host-owned sealed-config envelope, initialize
dynamic-provider sequence counters before cross-window decrypt, keep
three-way sidecar deletions during convergent migration, and create the
sidecar table for existing schema-1 plugin databases.

Related to #2269

* fix(plugins): avoid mid-sync adapter drops and sidecar resurrection

Keep plugin adapters across setting-only contribution refreshes and
rebind sessions on ensureConnected; stop preserving local-only missing
plugin sidecars when remote already mentions the plugin; compare configs
with nullish semantics across windows; swallow quick-connect failures
after toasting.

Related to #2269

* fix(plugins): restore remoteKeys for installed sidecar resets

Reintroduce the remote key set used when dropping installed-plugin
settings the remote no longer carries so applyFromSync cannot throw
after replacing the sidecar table.

Related to #2269

* fix(plugins): honor conflict policy and re-key scoped sidecars

Pass preferCloud/preferLocal into three-way sidecar merge, use
per-provider baselines on convergent downgrade so local resets are not
resurrected, and re-key applied settings under the current declared
scope coordinates.

Related to #2269

* fix(plugins): keep strategy on final sidecar merge and block dual legacy

Pass preferCloud/preferLocal through the final verified sidecar merge,
avoid auto-reactivating a retained plugin while another legacy provider
is already connected, cache plugin adapters only after initializeSync,
and preserve explicit null configuration values.

Related to #2269

* fix(plugins): strip plugin avatars and protect last-known sidecars

Do not load plugin-supplied avatar URLs in the main renderer, and avoid
overwriting last-known sidecars with an unmerged remote bundle when the
post-apply collect fails or is non-authoritative.

Related to #2269

* fix(plugins): strip top-bar plugin avatars and stop always-rebind

Hide plugin avatar URLs in the sync status popover, drop always-on
rebindSession for plugin adapters, and read convergent mode from the
local config module when deciding whether to auto-reactivate providers.

Related to #2269

* fix(plugins): use window.netcatty for sync IPC and allow null configs

Read plugin sync and sidecar APIs from the production preload global,
and treat an explicit config property (including JSON null) as present
for readiness and adapter lookup.

Related to #2269

* fix(plugins): close residual Codex P2 sync edge cases

Three-way merge sidecars during v2 convergent migration so local
resets are not resurrected, preserve explicit null plugin configs on
adapter rebuild, rebind EOS sessions after I/O failure for replaced
runtimes, and accept the public inlineObjectBytes limit on readObject.

Related to #2269

* fix(plugins): keep inline read cutoff under control-plane budget

Restore the base64-safe inline cutoff so advertised-size reads cannot
overflow the 128 KiB provider JSON envelope; providers near the public
inlineObjectBytes limit must stream.

Related to #2269

* fix(plugins): harden sidecar empty-vault, hydrate LWW, and credential seals

Close remaining Codex/review gaps for PR8: keep empty sidecar shells from
bypassing upload guards, skip startup hydrate when local settings are newer,
seal/validate durable plugin credential refs, and cover schema 1→2 migration.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(plugins): rebind plugin sync sessions and keep null configs

Address Codex P2 on 8ae52c54: re-issue connect after runtime replacement,
and treat stored config:null as present in the dashboard reconnect path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(plugins): defer empty sidecar last-known until sync succeeds

Keep prior non-empty last-known across an authoritative empty collect so the
empty-vault guard can still recognize a plugin-only reset, then commit the
empty cache after a successful upload.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(plugins): avoid restricted localStorage global in sidecar test

Bind the mock to a local binding so eslint no-restricted-globals stays clean.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(plugins): commit applied sidecar cache and reset orphan settings

Prefer merged payload sidecars when updating last-known after sync, and clear
syncable plugin_settings that lack sidecar rows on authoritative remote deletes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(plugins): reseal marker-shaped plugin configs instead of skipping

Exact __netcatty_plugin_config_v1 collisions no longer bypass encryption; trusted
host envelopes are unwrapped then resealed, and false collisions are sealed whole.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(plugins): keep merged sidecars in last-known after apply collect fails

Return applyFromSync entries from IPC and use them when follow-up collect fails,
so preserved missing-plugin rows are not dropped from the offline upload cache.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(plugins): delete stale-scope settings at stored coordinates on reset

After resetSetting against the current declared scope, also remove the row at
the sidecar's stored scope so scope migrations cannot republish remote deletes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(plugins): close sync-provider credential, stream, and sidecar gaps

Make SecretRef usable end-to-end via lease-bound network.request, keep
renderer/main transfers abortable and bounded, and stop sidecar
last-known/pending from corrupting backups and conflict apply.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(plugins): harden single-provider, secret schema, and conditional conflicts

Keep legacy restart from dual-connecting providers, extract writeOnly schema
secrets into OS storage, and surface conditional-write precondition failures
as conflict UI instead of a generic sync error.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(plugins): dedupe sync type imports for lint

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: address Codex review on PR #2713

* fix(plugins): wait for sidecar host before version backup latch

Defer the one-shot upgrade snapshot until the plugin sidecar host is ready (or a short grace), and catch getDataHash failures so pending/baseline paths do not reject unhandled.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(plugins): require sidecar grace before version backup

Always wait for contributions/grace tick before the upgrade snapshot, and only latch after a non-cancelled attempt so cleanup cannot suppress retries.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
2026-08-05 18:43:10 +08:00
ryan-wong-coder e6ffbd3f68 feat(sync): integrate convergent multi-device sync (#2259)
* feat(sync): integrate convergent multi-device sync

* fix(sync): seed v1 migration baselines

* fix(sync): join providers before downgrade

* fix(sync): close downgrade and reauth gaps

* fix(sync): redact nested array secrets

* fix(sync): close migration consistency gaps

* fix(sync): preserve migration and retry state

* fix(sync): share convergent pause state

* fix(sync): preserve paused edits on downgrade

* fix(sync): preserve paused auto-sync edits

* fix(sync): publish migrations and preserve merges

* fix(sync): route convergent remote checks

* fix(sync): avoid no-op convergent writes

* fix(sync): preserve convergent recovery safeguards

* fix(sync): commit merged replicas after apply

* fix(sync): open empty convergent startup gate

* fix(sync): seed legacy downgrade baselines
2026-07-17 09:41:28 +08:00
bincxz 25986252fd fix side panel semantic color contrast 2026-07-12 23:54:38 +08:00
bincxz 5d6b6b9769 fix theme contrast in terminal side panels 2026-07-12 23:40:28 +08:00
陈大猫 1eb877e2aa [codex] Add S3 insecure sync option (#1986)
* Add S3 insecure sync option

* Harden S3 insecure option handling

* Clean S3 dependency lockfile

* Guard S3 fallback require usage
2026-07-07 10:40:26 +08:00
陈大猫 b1f199c4d7 fix(sync): show sync dates and pending-only green dot (#1765) (#1770)
Add yyyymmdd hhmm timestamps to cloud sync history and only show the green status dot when local changes are still pending upload.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 00:12:03 +08:00
陈大猫 b74b5a442a [codex] Add vault notes markdown workspace (#1558)
* Add vault notes markdown workspace

* Improve notes tree drag interactions

* Harden notes tree move behavior

* Refine vault notes interactions

* Address vault notes review feedback

* Tighten notes tree review fixes
2026-06-22 18:23:58 +08:00
陈大猫 982569532c Sync AI API keys across devices
Sync AI API keys through cloud sync while keeping device-local external tool paths local. Also preserves local AI keys in backups and stops sync if a local encrypted key cannot be read.
2026-06-22 11:45:07 +08:00
陈大猫 c5ac85ae5b fix: OneDrive 同步 access token 自动续期与失效重连提示 (#1189) (#1208)
* Auto-refresh OneDrive sync token and prompt reconnect on dead refresh token

OneDrive cloud sync (#1189) broke for users on 1.1.20 and only recovered
after disconnecting and re-authorizing. Two gaps caused this:

1. Refreshed tokens were never persisted. When OneDriveAdapter silently
   refreshed the access token mid-session, the rotated tokens lived only in
   the adapter's in-memory state — CloudSyncManager never read them back or
   saved them. Microsoft consumer refresh tokens rotate on every refresh and
   invalidate the previous one, so the next app launch loaded a stale,
   rotated-out refresh token. Eventually that stored token was dead and sync
   failed, forcing a manual reconnect.

   Fix: OneDriveAdapter now exposes setOnTokensRefreshed(); the manager wires
   it so every silent refresh writes the rotated tokens back into provider
   state and encrypted storage (attachTokenRefreshPersistence /
   persistRefreshedProviderTokens), keeping the stored refresh token current.

2. A genuinely dead refresh token surfaced as a raw, generic error with no
   guidance. The bridge now detects invalid_grant / interaction_required /
   consent_required / login_required on refresh and tags the error with a
   stable marker. OneDriveAdapter normalizes these to
   OneDriveReauthRequiredError; the marker survives IPC and error re-wrapping
   so the condition stays detectable, and the UI strips it to show a clean
   "OneDrive session expired, please reconnect." message.

Shared marker + detection/clean helpers live in domain/sync.ts so the bridge,
adapter, and UI use one source of truth. Scope is limited to OneDrive; other
providers (Gist/iCloud/Google/WebDAV/S3) are untouched.

Tests: OneDriveAdapter refresh-persistence + reauth detection, manager
token-persistence wiring, and bridge invalid_grant tagging.

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

* Drop OneDrive to a reconnect state when its refresh token is dead

codex review: detecting a dead refresh token was not enough. A provider in
`error` state that still holds tokens stays "ready for sync"
(isProviderReadyForSync), and syncAllProviders resets such providers back to
`connected` and retries — so auto-sync kept hammering the dead refresh token
and the user never got a stable reconnect prompt.

Now, when a sync/download error indicates OneDrive reauth is required, the
manager clears the stale tokens and tears down the cached adapter
(handleProviderReauthRequired), leaving the provider in an error state with no
credentials. With no tokens, isProviderReadyForSync returns false so auto-sync
stops retrying, and the card shows a clean "please reconnect" message with a
Connect button. The account is preserved for display; the error message is
stripped of the internal marker.

Wired into the syncToProvider / uploadToProvider / downloadFromProvider error
paths. Added tests for the clear-on-reauth behavior and provider/error scoping.

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

* Clear OneDrive reauth during inspection paths; include service tests in npm test

codex review round 2:

P2 — A dead OneDrive refresh token can also surface during startup remote
inspection and the syncAllProviders preflight conflict check, both of which run
through inspectProviderRemoteState. That path swallowed the error into an
{error} tuple without clearing the stale tokens, so the provider stayed
retryable. Wired the reauth handler into inspectProviderRemoteState's catch,
covering sync preflight, syncAll preflight, and startup inspection in one place.
Made the handler idempotent so the operation's own catch can also call it
without re-saving.

P3 — New tests live under infrastructure/services/, which npm test's globs did
not cover (the pre-existing syncAllStorageMethods.test.ts was also uncovered).
Added infrastructure/services/*.test.ts and infrastructure/services/*/*.test.ts
to the test script.

Full suite: 1400 tests, 0 failures.

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

* test: drop unmatched services test glob from npm test

The npm test script listed both infrastructure/services/*.test.ts and the
nested infrastructure/services/*/*.test.ts. No .test.ts files live directly
under infrastructure/services/ (the OneDrive adapter and cloudSync tests are
in adapters/ and cloudSync/ subdirs), so the flat glob never matched.

Under a default POSIX shell (sh/bash without nullglob), an unmatched glob is
passed through literally, so node --test received the raw pattern. On Node
versions without test-runner glob support this aborts with
"Could not find '.../infrastructure/services/*.test.ts'" before any test runs,
breaking npm test.

Remove the redundant flat glob; the nested glob already covers the new
OneDrive adapter and cloudSync tests.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 16:26:33 +08:00
陈大猫 3c4746aea0 [codex] add sync reliability metadata (#1174)
* feat: add sync reliability metadata

* fix: preserve tombstones from checked remotes

* fix: keep conflict change counts typed
2026-06-01 18:40:19 +08:00
陈大猫 463dd4464f [codex] add cloud sync strategies (#1171)
* feat: add cloud sync strategies

* chore: clarify cloud sync strategy options

* chore: keep selected sync strategy concise

* fix: sync cloud-wins payload to remaining providers

* fix: apply cloud sync strategy during startup

* fix: retry partial startup cloud syncs
2026-06-01 17:07:07 +08:00
陈大猫 1fec5925eb Refactor large modules and fix runtime errors (#1136) 2026-05-28 15:12:19 +08:00