Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f27f180
feat(sync): 云同步数据范围可自选 —— 配置层与设置页 UI
leic4u Oct 14, 2026
70970bb
feat(sync): 接通自选同步范围的 Personal 与 OnetCloud 两条链路
leic4u Oct 15, 2026
2f0cf8d
feat(sync): 笔记同步(Personal 与 OnetCloud)
leic4u Oct 15, 2026
a0ad06b
fix(sync): 补齐 with_sync_scopes 的笔记适配器参数
leic4u Oct 15, 2026
9fc773e
fix: 还原 ci.yml test job 的 if 条件以通过 release 探针契约测试
leic4u Oct 15, 2026
1e5adc5
fix(notes-sync): 以稳定 document_id 作为笔记同步身份,修复重命名导致云端重复
leic4u Oct 16, 2026
5e00d3e
fix: 移除已下线的内置 LLM 提供商过滤(rebase 遗留编译断裂)
leic4u Oct 16, 2026
0987386
fix(saved-sql): 以稳定 document_id 作为已保存 SQL 同步身份
leic4u Oct 16, 2026
1699e87
fix(sync-scope): 修复 Personal 通道记录 ID 为空导致同类数据互相覆盖等问题
leic4u Oct 17, 2026
fe56815
fix(sync-status): 连接/工作空间/钥匙串记录同步状态;上次同步改用绝对时间
leic4u Oct 20, 2026
7512c99
feat(sync-scope): 设置项变更后立即触发同步
leic4u Oct 20, 2026
22afb94
fix(sync): 修复版本号虚增,同步改为变更驱动 + 一分钟合并 + 十分钟兜底
leic4u Oct 21, 2026
30c43f7
fix(sync): 修复 reconcile_runtime 的 E0502 借用冲突
leic4u Oct 21, 2026
0eca758
fix(sync): 修复设置/LLM 提供商变更后同步不触发(合并窗口 60s→10s)
leic4u Oct 21, 2026
379c1a7
fix(sync): 修复新设备拉取多个 LLM 提供商时撞主键导致同步轮次失败
leic4u Oct 22, 2026
28e5ac9
fix(sync): 同步落库后通知 UI 刷新,修复提供商列表不更新
leic4u Oct 22, 2026
ebdbbc6
fix(sync): 修复 v0.21.4 编译错误:Subscription 在本 gpui 版本无泛型参数
leic4u Oct 22, 2026
6e039be
fix(sync): OnetCloud 通道身份映射与水位修复,杜绝重复副本与乒乓
feigeCode Oct 25, 2026
8844ef5
fix(sync): Personal 通道单例认领/冲突可见/合并基线/删除传播与外键恢复
feigeCode Oct 25, 2026
1df4241
fix(sync): 设置落盘安全与生命周期:合法 JSON/原子写/路径校验/回填收紧
feigeCode Oct 25, 2026
c05073e
fix(sync): 同步落盘后重载设置并刷新 UI,勾选即时反馈
feigeCode Oct 25, 2026
a07ad66
feat(sync): 钥匙串与 LLM 提供商放开双通道同步
feigeCode Oct 26, 2026
a48c825
refactor(sync): OnetCloud 更名 NavopCloud,统一产品命名
feigeCode Oct 26, 2026
7b8d858
fix(sync): 修复并行测试全局主密钥互踩导致的偶发解密失败
feigeCode Oct 26, 2026
c583f85
Merge remote-tracking branch 'origin/main' into pr456
feigeCode Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions crates/core/migrations/20261014000001_quick_command_is_builtin.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
-- 快捷命令:区分「内置默认」与「用户自建」
--
-- 背景:`20260720000001_default_quick_commands.sql` 把 30 条内置命令直接 INSERT 进
-- `quick_commands` 业务表,表上没有可区分来源的列。云同步若按整表同步,每台设备都会
-- 把自己那份默认副本推上云端、再拉回其他设备,造成「同步一次多 30 条」的指数级重复。
--
-- 本迁移补齐 `is_builtin` 列并回填,同步侧只处理 `is_builtin = 0` 的行。
--
-- 判定逻辑在「命令文本命中」之外收紧两点:
-- 1. `LOWER(TRIM(name))` 必须命中内置命令名(原迁移插入的 name,见
-- `20260720000001` 的 defaults CTE)——用户自建一条同文本但不同名的命令不再被误标;
-- 2. 排除 `pinned = 1` 的行——内置默认全部未置顶,用户置顶过的命令视为用户数据。
-- 代价是「用户自建了与内置完全同名同文本的命令」时仍会被误标,这个代价远小于重复污染。
--
-- 注意:`execute_batch` 中 ALTER 若因列已存在报错,整批(含回填)都不会执行。
-- 迁移执行器对 "duplicate column name" 有专门的吞错路径,因此不能依赖把回填与 ALTER
-- 放同一批来保证幂等——回填语句本身用 WHERE 条件限定即可重入。

ALTER TABLE quick_commands ADD COLUMN is_builtin INTEGER NOT NULL DEFAULT 0;

UPDATE quick_commands
SET is_builtin = 1
WHERE connection_id IS NULL
AND pinned = 0
AND LOWER(TRIM(name)) IN (
'disk usage',
'memory usage',
'uptime',
'current user',
'system information',
'running processes',
'environment variables',
'recent journal errors',
'ip addresses',
'interface config',
'listening ports',
'network connections',
'ping public dns',
'http response headers',
'dns lookup',
'print working directory',
'list files',
'directory size',
'child sizes',
'find files',
'git status',
'recent git history',
'git branches',
'git diff summary',
'git remotes',
'docker containers',
'docker images',
'compose services',
'docker stats',
'docker disk usage'
)
AND LOWER(TRIM(command)) IN (
'df -h',
'free -h',
'uptime',
'whoami',
'uname -a',
'ps aux',
'env | sort',
'journalctl -xe --no-pager',
'ip addr',
'ifconfig',
'ss -lntup',
'netstat -an',
'ping -c 4 8.8.8.8',
'curl -i https://example.com',
'nslookup example.com',
'pwd',
'ls -la',
'du -sh .',
'du -sh * | sort -h',
'find . -maxdepth 2 -type f',
'git status',
'git log --oneline --graph --decorate -20',
'git branch -vv',
'git diff --stat',
'git remote -v',
'docker ps',
'docker images',
'docker compose ps',
'docker stats --no-stream',
'docker system df'
);

CREATE INDEX IF NOT EXISTS idx_quick_commands_is_builtin ON quick_commands (is_builtin);
36 changes: 36 additions & 0 deletions crates/core/migrations/20261016000001_sync_scope_state.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- 云同步「数据范围」扩展所需的两张表
--
-- sync_item_state:逐条同步基线。把 cloud_id / checksum / 变更时间 / 上次同步时间
-- 从业务表外置出来,这样 quick_commands / sftp_favorite_paths / llm_providers
-- 不需要加列,笔记与已保存 SQL 这类文件系统数据也有地方存基线。
-- base_payload 仅供设置类 Singleton 做字段级三方合并(Q8)使用。
--
-- sync_type_status:按数据类型聚合的同步结果,供设置页每一行展示
-- 「上次同步时间 / 最近失败警告」(Q12)。

CREATE TABLE IF NOT EXISTS sync_item_state (
scope_env TEXT NOT NULL,
user_id TEXT NOT NULL,
data_type TEXT NOT NULL,
local_key TEXT NOT NULL,
cloud_id TEXT,
checksum TEXT,
content_ts INTEGER NOT NULL DEFAULT 0,
last_synced_at INTEGER,
base_payload TEXT,
PRIMARY KEY (scope_env, user_id, data_type, local_key)
);

CREATE INDEX IF NOT EXISTS idx_sync_item_state_cloud
ON sync_item_state (data_type, cloud_id);

CREATE TABLE IF NOT EXISTS sync_type_status (
scope_env TEXT NOT NULL,
user_id TEXT NOT NULL,
data_type TEXT NOT NULL,
last_success_at INTEGER,
last_error_code TEXT,
last_error_at INTEGER,
last_error_msg TEXT,
PRIMARY KEY (scope_env, user_id, data_type)
);
57 changes: 56 additions & 1 deletion crates/core/src/cloud_sync/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::crypto;
use crate::storage::{StorageManager, TeamKeyCache, TeamKeyCacheRepository};
use crate::storage::{TeamMembershipCache, TeamMembershipCacheRepository, TeamMembershipState};
use futures_util::{StreamExt, stream};
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, OnceLock};
Expand Down Expand Up @@ -87,6 +87,56 @@ pub struct SyncEngine {
handlers: Vec<Box<dyn SyncHandler>>,
/// 当前用户所在团队列表(同步开始时获取)
pub(crate) cached_teams: std::sync::RwLock<Vec<Team>>,
/// 「数据范围」扩展处理器所需的环境标识;`None` 表示未装配
pub(crate) scope_env: Option<String>,
pub(crate) scope_user_id: Option<String>,
}

impl SyncEngine {
/// 装配「用户自选同步数据范围」:注册新增类型的处理器。
///
/// `enabled` 为空表示用户未勾选任何新增类型,此时不注册任何处理器。
pub fn with_sync_scopes(
mut self,
scope_env: String,
user_id: String,
enabled: HashSet<String>,
notes: Option<crate::cloud_sync::scope::notes::SharedNoteAdapter>,
) -> Self {
if enabled.is_empty() {
return self;
}
self.scope_env = Some(scope_env.clone());
self.scope_user_id = Some(user_id.clone());

let state = crate::cloud_sync::scope::state::SyncItemStateRepository::new(
self.storage.connection(),
);
for descriptor in crate::settings::SYNC_SCOPE_DESCRIPTORS {
if !enabled.contains(descriptor.data_type) {
continue;
}
// 连接 / 工作空间由各自既有 handler 负责,避免重复注册。
// 钥匙串(credential)已放开 NavopCloud 通道:走 ScopeSyncTypeHandler
// 通用流程(载荷同样是主密钥端到端加密的 CredentialPlainData)。
if matches!(
descriptor.data_type,
crate::cloud_sync::data_type::CONNECTION | crate::cloud_sync::data_type::WORKSPACE
) {
continue;
}
self.handlers.push(Box::new(TypedSyncBridge {
handler: crate::cloud_sync::scope::cloud::ScopeSyncTypeHandler::new(
descriptor.data_type,
state.clone(),
scope_env.clone(),
user_id.clone(),
notes.clone(),
),
}));
}
self
}
}

impl SyncEngine {
Expand All @@ -108,6 +158,8 @@ impl SyncEngine {
Box::new(ConnectionSyncHandler),
],
cached_teams: std::sync::RwLock::new(Vec::new()),
scope_env: None,
scope_user_id: None,
}
}

Expand Down Expand Up @@ -141,6 +193,9 @@ impl SyncEngine {
fn ensure_unlocked(&self) -> Result<(), SyncError> {
// 以本地 crypto 模块为准:主密钥刚被修改过时,服务里可能还留着旧密钥,
// 那样会用旧密钥去解已经换成新密文的云端数据。
//
// pinned 的密钥是调用方显式声明的权威密钥(改密迁移、测试自备密钥等),
// 引擎不会用全局主密钥覆盖它,避免并行测试互踩全局密钥时注入错误密钥。
if crypto::has_master_key() {
if let Some(raw_key) = crypto::get_raw_master_key() {
let mut service_write = self
Expand Down
Loading
Loading