dsh-config-manager 0.1.78 → 0.1.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/adapters/credentials.d.ts +6 -2
- package/lib/adapters/credentials.js +19 -3
- package/lib/adapters/file-collection.js +2 -1
- package/lib/adapters/mcp.d.ts +48 -2
- package/lib/adapters/mcp.js +265 -3
- package/lib/adapters/messages.d.ts +3 -0
- package/lib/adapters/messages.js +7 -0
- package/lib/adapters/plugin-files.js +2 -1
- package/lib/adapters/plugins.js +105 -20
- package/lib/adapters/pnpm-workspace.d.ts +32 -0
- package/lib/adapters/pnpm-workspace.js +113 -2
- package/lib/adapters/prompts.d.ts +6 -2
- package/lib/adapters/prompts.js +17 -2
- package/lib/adapters/providers.d.ts +19 -2
- package/lib/adapters/providers.js +59 -3
- package/lib/adapters/settings.d.ts +6 -2
- package/lib/adapters/settings.js +13 -2
- package/lib/adapters/skills.d.ts +25 -1
- package/lib/adapters/skills.js +60 -0
- package/lib/adapters/ui.d.ts +6 -2
- package/lib/adapters/ui.js +13 -2
- package/lib/adapters/units.d.ts +18 -0
- package/lib/adapters/units.js +39 -0
- package/lib/adapters/workspaces.js +2 -1
- package/lib/client.d.ts +401 -112
- package/lib/client.js +2899 -1525
- package/lib/core/backup.js +20 -0
- package/lib/core/mcp-connector-store.d.ts +159 -0
- package/lib/core/mcp-connector-store.js +445 -0
- package/lib/core/messages-core.d.ts +2 -0
- package/lib/core/messages-core.js +4 -0
- package/lib/core/messages.d.ts +20 -0
- package/lib/core/messages.js +44 -0
- package/lib/core/phase3-host.js +18 -1
- package/lib/core/plugin-cli.d.ts +22 -2
- package/lib/core/plugin-cli.js +22 -3
- package/lib/core/recovery-orchestrator.d.ts +3 -1
- package/lib/core/recovery-orchestrator.js +75 -16
- package/lib/core/rollback-activity.d.ts +89 -0
- package/lib/core/rollback-activity.js +350 -0
- package/lib/core/rollback-ledger.d.ts +0 -9
- package/lib/core/rollback-ledger.js +37 -0
- package/lib/core/rollback-merge.d.ts +22 -2
- package/lib/core/rollback-merge.js +63 -13
- package/lib/core/rollback-point.d.ts +93 -0
- package/lib/core/rollback-point.js +420 -101
- package/lib/core/rollback-undo-journal.d.ts +123 -0
- package/lib/core/rollback-undo-journal.js +517 -0
- package/lib/core/self-update.js +2 -4
- package/lib/core/text-fidelity.d.ts +89 -0
- package/lib/core/text-fidelity.js +176 -0
- package/lib/core/types.d.ts +12 -3
- package/lib/core/update-check.js +2 -4
- package/lib/core/validator.d.ts +9 -0
- package/lib/core/validator.js +12 -0
- package/lib/foreign/bundle.js +42 -3
- package/lib/foreign/claude-code.js +358 -26
- package/lib/foreign/claude-sessions.d.ts +51 -8
- package/lib/foreign/claude-sessions.js +111 -13
- package/lib/foreign/read-claude-code.d.ts +33 -4
- package/lib/foreign/read-claude-code.js +446 -65
- package/lib/foreign/read-claude-plugins.d.ts +36 -0
- package/lib/foreign/read-claude-plugins.js +403 -0
- package/lib/foreign/registry.js +25 -2
- package/lib/foreign/truth-table.js +6 -4
- package/lib/foreign/types.d.ts +170 -2
- package/lib/index.d.ts +14 -5
- package/lib/index.js +60 -16
- package/lib/market/git-file-writer.js +7 -1
- package/lib/profiles/dsh-profile-launcher.d.ts +20 -0
- package/lib/profiles/dsh-profile-launcher.js +58 -1
- package/lib/profiles/dsh-profile-shared.d.ts +8 -2
- package/lib/profiles/dsh-profile-shared.js +8 -2
- package/lib/routes/foreign.js +8 -1
- package/lib/routes/index.d.ts +1 -1
- package/lib/routes/kit.d.ts +2 -2
- package/lib/routes/sync.js +173 -9
- package/lib/schema/manifest.d.ts +6 -1
- package/lib/schema/manifest.js +55 -0
- package/lib/schema/types.d.ts +47 -2
- package/lib/sync/ancestor.d.ts +22 -0
- package/lib/sync/ancestor.js +66 -2
- package/lib/sync/autosync-config.d.ts +73 -0
- package/lib/sync/autosync-config.js +89 -0
- package/lib/sync/autosync-scheduler.d.ts +80 -6
- package/lib/sync/autosync-scheduler.js +263 -40
- package/lib/sync/gist/gist-transport.d.ts +18 -1
- package/lib/sync/gist/gist-transport.js +42 -3
- package/lib/sync/gist/messages.d.ts +2 -0
- package/lib/sync/gist/messages.js +5 -0
- package/lib/sync/git/git-transport.d.ts +42 -3
- package/lib/sync/git/git-transport.js +146 -3
- package/lib/sync/merge.d.ts +47 -14
- package/lib/sync/merge.js +242 -222
- package/lib/sync/messages.d.ts +8 -0
- package/lib/sync/messages.js +19 -0
- package/lib/sync/risk.d.ts +19 -2
- package/lib/sync/risk.js +10 -3
- package/lib/sync/s3/messages.d.ts +6 -0
- package/lib/sync/s3/messages.js +13 -0
- package/lib/sync/s3/s3-transport.d.ts +9 -1
- package/lib/sync/s3/s3-transport.js +78 -6
- package/lib/sync/s3/sigv4.js +11 -2
- package/lib/sync/sync-engine.d.ts +212 -8
- package/lib/sync/sync-engine.js +514 -58
- package/lib/sync/sync-history.d.ts +11 -0
- package/lib/sync/sync-history.js +20 -0
- package/lib/sync/sync-state.d.ts +43 -0
- package/lib/sync/sync-state.js +44 -0
- package/lib/sync/sync-watcher.d.ts +76 -0
- package/lib/sync/sync-watcher.js +202 -0
- package/lib/sync/text-merge.d.ts +85 -0
- package/lib/sync/text-merge.js +513 -0
- package/lib/sync/transport.d.ts +48 -0
- package/lib/sync/transport.js +12 -0
- package/lib/sync/watch-scope.d.ts +38 -0
- package/lib/sync/watch-scope.js +95 -0
- package/lib/sync/webdav/webdav-transport.d.ts +16 -2
- package/lib/sync/webdav/webdav-transport.js +128 -11
- package/lib/ui/foreign-view.js +10 -0
- package/lib/ui/i18n.d.ts +9 -0
- package/lib/ui/i18n.js +22 -0
- package/lib/ui/rollback-view.d.ts +12 -0
- package/lib/ui/rollback-view.js +2 -0
- package/lib/ui/sync-settings-view.d.ts +47 -0
- package/lib/ui/sync-settings-view.js +24 -0
- package/lib/ui/types.d.ts +21 -0
- package/lib/utils/env-lock.d.ts +1 -0
- package/lib/utils/env-lock.js +7 -4
- package/package.json +6 -6
- package/src/adapters/credentials.ts +22 -4
- package/src/adapters/file-collection.ts +2 -1
- package/src/adapters/mcp.test.ts +235 -1
- package/src/adapters/mcp.ts +276 -5
- package/src/adapters/messages.ts +7 -0
- package/src/adapters/plugin-files.ts +2 -1
- package/src/adapters/plugins.test.ts +176 -2
- package/src/adapters/plugins.ts +107 -21
- package/src/adapters/pnpm-workspace.test.ts +89 -1
- package/src/adapters/pnpm-workspace.ts +127 -2
- package/src/adapters/prompts.ts +20 -3
- package/src/adapters/providers.ts +62 -4
- package/src/adapters/settings.ts +16 -3
- package/src/adapters/skills.test.ts +141 -0
- package/src/adapters/skills.ts +58 -2
- package/src/adapters/ui.ts +16 -3
- package/src/adapters/units-entries.test.ts +190 -0
- package/src/adapters/units.ts +50 -0
- package/src/adapters/workspaces.ts +2 -1
- package/src/client/about/PluginUpdateDialog.tsx +4 -2
- package/src/client/about/ReleaseNotesDialog.tsx +4 -2
- package/src/client/chat/RollbackReviewDialog.tsx +3 -2
- package/src/client/common/Skeleton.tsx +32 -0
- package/src/client/common/api-base.test.ts +36 -0
- package/src/client/common/http.test.ts +12 -1
- package/src/client/common/http.ts +8 -2
- package/src/client/common/routes.ts +46 -3
- package/src/client/config-manager.module.css +321 -11
- package/src/client/home/HomePanel.tsx +14 -6
- package/src/client/import/ForeignImportView.tsx +3 -1
- package/src/client/import/import-wizard-steps.tsx +2 -1
- package/src/client/library/ArtifactRow.tsx +12 -2
- package/src/client/library/LibraryActions.tsx +6 -3
- package/src/client/library/LibraryPanel.tsx +21 -3
- package/src/client/library/RestorePlanView.tsx +4 -2
- package/src/client/locales.ts +3 -0
- package/src/client/market/market-api.test.ts +3 -2
- package/src/client/recovery/RecoveryPanel.tsx +54 -7
- package/src/client/recovery/RollbackCard.tsx +7 -2
- package/src/client/recovery/RollbackDiffPane.tsx +3 -2
- package/src/client/recovery/recovery-locales.ts +20 -2
- package/src/client/recovery/recovery-view.test.ts +39 -1
- package/src/client/recovery/recovery-view.ts +23 -1
- package/src/client/run-store.test.ts +1 -1
- package/src/client/run-store.ts +4 -0
- package/src/client/sync/AutosyncCard.tsx +49 -6
- package/src/client/sync/MergeModal.tsx +220 -0
- package/src/client/sync/MergePreviewView.tsx +306 -0
- package/src/client/sync/SyncActivityBar.tsx +31 -0
- package/src/client/sync/SyncConfirmView.tsx +2 -1
- package/src/client/sync/SyncPanel.tsx +130 -87
- package/src/client/sync/SyncProbeRow.tsx +162 -0
- package/src/client/sync/SyncSettingsView.test.ts +27 -2
- package/src/client/sync/WorkDirCard.tsx +44 -6
- package/src/client/sync/history-model.ts +4 -1
- package/src/client/sync/merge-view.test.ts +248 -0
- package/src/client/sync/merge-view.ts +398 -0
- package/src/client/sync/probe-view.test.ts +90 -0
- package/src/client/sync/probe-view.ts +63 -0
- package/src/client/sync/remote-device-view.test.ts +35 -0
- package/src/client/sync/remote-device-view.ts +25 -0
- package/src/client/sync/sync-api.test.ts +5 -3
- package/src/client/sync/sync-api.ts +212 -2
- package/src/client/sync/sync-entry.test.ts +78 -0
- package/src/client/sync/sync-entry.ts +53 -0
- package/src/client/sync/sync-locales.ts +234 -2
- package/src/client/sync/sync-view.test.ts +18 -1
- package/src/client/sync/sync-view.ts +20 -0
- package/src/core/backup.test.ts +41 -0
- package/src/core/backup.ts +20 -0
- package/src/core/mcp-connector-store.test.ts +265 -0
- package/src/core/mcp-connector-store.ts +516 -0
- package/src/core/messages-core.ts +4 -0
- package/src/core/messages.ts +46 -0
- package/src/core/phase3-host.ts +18 -1
- package/src/core/phase3-production-integration.test.ts +4 -0
- package/src/core/plugin-cli.test.ts +26 -0
- package/src/core/plugin-cli.ts +30 -4
- package/src/core/recovery-orchestrator.test.ts +104 -5
- package/src/core/recovery-orchestrator.ts +74 -17
- package/src/core/rollback-activity.test.ts +311 -0
- package/src/core/rollback-activity.ts +367 -0
- package/src/core/rollback-cross-session.test.ts +694 -0
- package/src/core/rollback-interrupted.test.ts +137 -0
- package/src/core/rollback-ledger.ts +32 -0
- package/src/core/rollback-merge.test.ts +130 -0
- package/src/core/rollback-merge.ts +78 -14
- package/src/core/rollback-point.ts +468 -91
- package/src/core/rollback-undo-journal.test.ts +508 -0
- package/src/core/rollback-undo-journal.ts +571 -0
- package/src/core/self-update.ts +2 -5
- package/src/core/text-fidelity.test.ts +189 -0
- package/src/core/text-fidelity.ts +197 -0
- package/src/core/types.ts +11 -3
- package/src/core/update-check.ts +2 -5
- package/src/core/validator.ts +14 -0
- package/src/foreign/bundle-origin.test.ts +265 -0
- package/src/foreign/bundle.ts +45 -5
- package/src/foreign/claude-code.test.ts +268 -0
- package/src/foreign/claude-code.ts +406 -25
- package/src/foreign/claude-plugins.test.ts +234 -0
- package/src/foreign/claude-sessions.test.ts +141 -0
- package/src/foreign/claude-sessions.ts +179 -23
- package/src/foreign/file-budget.test.ts +4 -1
- package/src/foreign/read-claude-code.test.ts +320 -0
- package/src/foreign/read-claude-code.ts +490 -71
- package/src/foreign/read-claude-plugins.ts +465 -0
- package/src/foreign/registry.ts +25 -2
- package/src/foreign/session-ir.test.ts +65 -0
- package/src/foreign/truth-table.test.ts +81 -0
- package/src/foreign/truth-table.ts +6 -3
- package/src/foreign/types.ts +162 -2
- package/src/index.facade.test.ts +26 -4
- package/src/index.ts +76 -19
- package/src/market/git-file-writer.test.ts +4 -0
- package/src/market/git-file-writer.ts +7 -1
- package/src/profiles/dsh-profile-launcher.test.ts +68 -3
- package/src/profiles/dsh-profile-launcher.ts +56 -1
- package/src/profiles/dsh-profile-shared.ts +8 -2
- package/src/routes/foreign.ts +8 -1
- package/src/routes/index.ts +1 -1
- package/src/routes/kit.ts +2 -2
- package/src/routes/sync.ts +176 -9
- package/src/schema/manifest-schema.test.ts +87 -2
- package/src/schema/manifest.ts +57 -1
- package/src/schema/types.ts +45 -2
- package/src/sync/ancestor.ts +76 -2
- package/src/sync/autosync-config.test.ts +73 -1
- package/src/sync/autosync-config.ts +124 -0
- package/src/sync/autosync-risk-tier.test.ts +38 -2
- package/src/sync/autosync-scheduler.test.ts +264 -12
- package/src/sync/autosync-scheduler.ts +296 -44
- package/src/sync/gist/gist-conditional-write.test.ts +203 -0
- package/src/sync/gist/gist-transport.ts +53 -5
- package/src/sync/gist/messages.ts +5 -0
- package/src/sync/git/git-probe.test.ts +80 -0
- package/src/sync/git/git-transport.test.ts +185 -0
- package/src/sync/git/git-transport.ts +178 -5
- package/src/sync/merge.test.ts +188 -55
- package/src/sync/merge.ts +282 -224
- package/src/sync/messages.ts +19 -0
- package/src/sync/risk.ts +29 -5
- package/src/sync/s3/messages.ts +13 -0
- package/src/sync/s3/s3-conditional-write.test.ts +330 -0
- package/src/sync/s3/s3-transport.test.ts +2 -1
- package/src/sync/s3/s3-transport.ts +87 -9
- package/src/sync/s3/sigv4.test.ts +4 -1
- package/src/sync/s3/sigv4.ts +11 -2
- package/src/sync/sync-device-identity.test.ts +267 -0
- package/src/sync/sync-engine.test.ts +410 -19
- package/src/sync/sync-engine.ts +602 -57
- package/src/sync/sync-history.test.ts +16 -1
- package/src/sync/sync-history.ts +26 -0
- package/src/sync/sync-state.test.ts +64 -0
- package/src/sync/sync-state.ts +79 -0
- package/src/sync/sync-watcher.test.ts +197 -0
- package/src/sync/sync-watcher.ts +246 -0
- package/src/sync/text-merge.test.ts +358 -0
- package/src/sync/text-merge.ts +554 -0
- package/src/sync/transport.ts +57 -0
- package/src/sync/watch-scope.test.ts +122 -0
- package/src/sync/watch-scope.ts +120 -0
- package/src/sync/webdav/webdav-conditional-write.test.ts +360 -0
- package/src/sync/webdav/webdav-transport.ts +137 -14
- package/src/ui/foreign-view.test.ts +34 -0
- package/src/ui/foreign-view.ts +10 -0
- package/src/ui/i18n.ts +22 -0
- package/src/ui/rollback-view.ts +14 -0
- package/src/ui/sync-settings-view.test.ts +28 -1
- package/src/ui/sync-settings-view.ts +43 -0
- package/src/ui/types.ts +22 -0
- package/src/utils/env-lock.ts +8 -5
|
@@ -31,7 +31,7 @@ import type { SyncTransportType as HostSyncTransportType } from '../../sync/sync
|
|
|
31
31
|
// 云端点通道的**回传视图**(只带 secretStored 布尔,绝无密钥值):类型直接取宿主声明处,
|
|
32
32
|
// type-only 引用打包时被擦除,不会把 sync-config.ts 的 node:fs 带进浏览器产物。
|
|
33
33
|
import type { CloudGistChannelView, CloudS3ChannelView } from '../../sync/sync-config.ts';
|
|
34
|
-
import type { PlanItemKind, SessionFormatDisposition } from '../../core/types.ts';
|
|
34
|
+
import type { PathMapping, PlanItemKind, SessionFormatDisposition } from '../../core/types.ts';
|
|
35
35
|
import type { SectionId } from '../../schema/types.ts';
|
|
36
36
|
import type { ConsultReport } from '../../core/migration-consult.ts';
|
|
37
37
|
import { getJson, LONG_REQUEST_TIMEOUT_MS, postJson, type RequestOptions } from '../common/http.ts';
|
|
@@ -59,6 +59,11 @@ export type SyncTransportType = HostSyncTransportType;
|
|
|
59
59
|
/** GET /sync/status 响应:配置/凭据/上次同步的只读事实(无任何 secret 值) */
|
|
60
60
|
export interface SyncStatusResponse {
|
|
61
61
|
ok: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* 方案 A:是否存在同步基线(本地祖先指针;宿主只回布尔,绝不回快照 id)。
|
|
64
|
+
* **缺省 undefined = 未知 → 一律按「无基准」处理**,走老导入确认路径(fail-safe)。
|
|
65
|
+
*/
|
|
66
|
+
hasBaseline?: boolean;
|
|
62
67
|
/** 是否已保存过仓库/通道配置(sync-config.json;任一通道配置过即为 true) */
|
|
63
68
|
configured: boolean;
|
|
64
69
|
/** git 通道:已配置的仓库地址(不含 token,可回显;与当前通道无关,配置过即返回) */
|
|
@@ -121,6 +126,8 @@ export interface SyncWorkDirFacts {
|
|
|
121
126
|
ahead: number | null;
|
|
122
127
|
/** 工作树是否有未提交改动(仅 detail 自检时给出,否则 null) */
|
|
123
128
|
dirty: boolean | null;
|
|
129
|
+
/** 远端分支上的快照数(仅 detail 自检时给出,否则 null;issue #104 建议 3) */
|
|
130
|
+
remoteSnapshots: number | null;
|
|
124
131
|
}
|
|
125
132
|
|
|
126
133
|
/** 同步密码的保存状态(只回布尔;密码值永不回传浏览器)。 */
|
|
@@ -286,6 +293,42 @@ export interface SyncPullPayload extends SyncPushPayload {
|
|
|
286
293
|
decryptPassword?: string;
|
|
287
294
|
}
|
|
288
295
|
|
|
296
|
+
/* ---------------------------------------------------------------- 连通性自检(cloud-sync 借鉴项 ②) */
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* POST /sync/probe 结果。
|
|
300
|
+
*
|
|
301
|
+
* **失败也是正常返回**(HTTP 200 + \`ok:false\`):测试连接是**预期会失败**的诊断动作,
|
|
302
|
+
* 用 5xx 表达会被界面当成基础设施崩溃。\`message\` / \`hint\` 由通道层本地化并脱敏后原样带回。
|
|
303
|
+
*/
|
|
304
|
+
export interface SyncProbeSuccess {
|
|
305
|
+
ok: true
|
|
306
|
+
transport: string
|
|
307
|
+
/** 结论(可直接展示的成功文案) */
|
|
308
|
+
message: string
|
|
309
|
+
/** 附加提醒(例如「该 S3 兼容端点应把 region 填成 xxx」),有值时界面按 warn 级展示 */
|
|
310
|
+
hint?: string
|
|
311
|
+
/** 远端已有快照数(取得时回传) */
|
|
312
|
+
snapshotCount?: number
|
|
313
|
+
/** 自检过程中**自动创建**的远端目录(webdav 幂等 MKCOL;界面须逐条说明,绝不静默写远端) */
|
|
314
|
+
createdCollections?: string[]
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** 自检失败(诊断动作的预期结果,不是异常) */
|
|
318
|
+
export interface SyncProbeFailure {
|
|
319
|
+
ok: false
|
|
320
|
+
error: string
|
|
321
|
+
/**
|
|
322
|
+
* **能力边界标记**:该通道不提供连通性自检(传输层未实现 probe)。
|
|
323
|
+
*
|
|
324
|
+
* 与故障**严格区分**:true = 这个通道没有这项能力(中性提示,同步功能不受影响);
|
|
325
|
+
* 缺省/undefined = 真的没连通(按故障展示)。界面据此选 info 而不是「操作失败」。
|
|
326
|
+
*/
|
|
327
|
+
unsupported?: boolean
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export type SyncProbeResult = SyncProbeSuccess | SyncProbeFailure
|
|
331
|
+
|
|
289
332
|
/* ---------------------------------------------------------------- 一键同步(方案 A) */
|
|
290
333
|
|
|
291
334
|
/** GET /sync/snapshots-list 响应:远端历史快照列表(按 createdAt 倒序)。 */
|
|
@@ -311,6 +354,14 @@ export interface SyncSnapshotLite {
|
|
|
311
354
|
sectionCount: number;
|
|
312
355
|
platform: string;
|
|
313
356
|
dshVersion: string;
|
|
357
|
+
/**
|
|
358
|
+
* cloud-sync 借鉴项 ⑥:推送方设备身份(宿主从快照 manifest 读出)。
|
|
359
|
+
* **旧快照没有这两个字段** —— 界面必须据此不显示设备行,绝不把 undefined 渲染成文案、
|
|
360
|
+
* 也不显示空串占位。
|
|
361
|
+
*/
|
|
362
|
+
deviceName?: string;
|
|
363
|
+
/** 稳定设备 id(换主机名/改机器名都不变;用于认出「同一台设备」)。 */
|
|
364
|
+
deviceId?: string;
|
|
314
365
|
}
|
|
315
366
|
|
|
316
367
|
/** POST /sync/sync 请求体(一键同步第一步:拉取 → 差异确认会话)。 */
|
|
@@ -441,6 +492,111 @@ export interface SyncRollbackFailedItem {
|
|
|
441
492
|
manualHint?: string;
|
|
442
493
|
}
|
|
443
494
|
|
|
495
|
+
/* ---------------------------------------------------------------- 远程合并裁决(T-ui-merge) */
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* 合并冲突成因(对齐宿主 src/sync/merge.ts 的 MergeConflictReason)。
|
|
499
|
+
* 未在本联合里的字符串照样可能从宿主回传 —— 界面侧一律经 merge-view.ts 的
|
|
500
|
+
* mergeReasonKey() 映射,未知值按 both-modified 措辞,**绝不渲染裸枚举**。
|
|
501
|
+
*/
|
|
502
|
+
export type MergeConflictReason =
|
|
503
|
+
| 'both-modified'
|
|
504
|
+
| 'modify-delete'
|
|
505
|
+
| 'delete-modify'
|
|
506
|
+
| 'no-ancestor'
|
|
507
|
+
| 'binary'
|
|
508
|
+
| 'format'
|
|
509
|
+
| 'array'
|
|
510
|
+
| 'section';
|
|
511
|
+
|
|
512
|
+
/** 分区级合并决策('conflict' = 双侧改动重叠,必须由用户裁决)。 */
|
|
513
|
+
export type MergeSectionDecision = 'useRemote' | 'keepLocal' | 'merge' | 'skip' | 'conflict';
|
|
514
|
+
|
|
515
|
+
/** 单条冲突的三列预览(已由宿主脱敏/序列化,界面负责截断)。 */
|
|
516
|
+
export interface MergePreviewConflict {
|
|
517
|
+
/** 冲突位置:JSON 分区是 top-level key;FilesSection 是 relativePath。 */
|
|
518
|
+
path: string;
|
|
519
|
+
kind: 'key' | 'file';
|
|
520
|
+
reason?: MergeConflictReason;
|
|
521
|
+
localPreview: string;
|
|
522
|
+
remotePreview: string;
|
|
523
|
+
ancestorPreview: string;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** 单个分区的合并预览。 */
|
|
527
|
+
export interface MergePreviewSection {
|
|
528
|
+
id: SectionId;
|
|
529
|
+
decision: MergeSectionDecision;
|
|
530
|
+
/** 自动合并并入的变更点数量(仅自动合并成功时有意义) */
|
|
531
|
+
autoMerged?: number;
|
|
532
|
+
conflictCount: number;
|
|
533
|
+
conflicts: MergePreviewConflict[];
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** 预览摘要(可自动应用 / 待裁决 / 冲突条目 / 无变化)。 */
|
|
537
|
+
export interface MergePreviewSummary {
|
|
538
|
+
auto: number;
|
|
539
|
+
review: number;
|
|
540
|
+
conflicts: number;
|
|
541
|
+
skipped: number;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** POST /sync/merge-preview 请求体(只读;密码仅内存传输,绝不落盘/落日志)。 */
|
|
545
|
+
export interface MergePreviewPayload {
|
|
546
|
+
/** 远端快照 id;缺省 = 最新 */
|
|
547
|
+
snapshotId?: string;
|
|
548
|
+
/** 解密密码(仅当远端快照加密时使用;缺省 = 已保存的 DECRYPT 凭据) */
|
|
549
|
+
decryptPassword?: string;
|
|
550
|
+
/** 跨机路径映射(与一键同步同一口径) */
|
|
551
|
+
pathMappings?: PathMapping[];
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** POST /sync/merge-preview 响应(只读三方合并结果)。 */
|
|
555
|
+
export interface MergePreviewResponse {
|
|
556
|
+
ok: boolean;
|
|
557
|
+
snapshotId: string;
|
|
558
|
+
/** 是否存在共同基准快照(false = 退化为两方比较,双侧不一致一律按冲突) */
|
|
559
|
+
hasAncestor: boolean;
|
|
560
|
+
sections: MergePreviewSection[];
|
|
561
|
+
summary: MergePreviewSummary;
|
|
562
|
+
/** 失败/降级原因(仅在 ok=false 等场景出现) */
|
|
563
|
+
message?: string;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** 单条裁决动作。 */
|
|
567
|
+
export type MergeResolutionActionWire = 'keepLocal' | 'useRemote' | 'useMerged' | 'skip';
|
|
568
|
+
|
|
569
|
+
/** 单条裁决:id = 分区 id(分区级)或 `分区id::冲突路径`(冲突级,见 merge-view.ts)。 */
|
|
570
|
+
export interface MergeResolution {
|
|
571
|
+
id: string;
|
|
572
|
+
action: MergeResolutionActionWire;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/** POST /sync/apply-merge 请求体(写动作;过 mutation gate)。 */
|
|
576
|
+
export interface MergeApplyPayload extends MergePreviewPayload {
|
|
577
|
+
resolutions: MergeResolution[];
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** POST /sync/apply-merge 响应(字段与 /sync/apply-items 同风格 + pendingConflicts)。 */
|
|
581
|
+
export interface MergeApplyResponse {
|
|
582
|
+
ok: boolean;
|
|
583
|
+
applied: string[];
|
|
584
|
+
skipped: string[];
|
|
585
|
+
needsRestart?: boolean;
|
|
586
|
+
warnings: string[];
|
|
587
|
+
/** 执行前快照 id(界面一键回滚用) */
|
|
588
|
+
restoreId?: string;
|
|
589
|
+
/** 任一失败是否整体回滚 */
|
|
590
|
+
rolledBack?: boolean;
|
|
591
|
+
/** 整体回滚是否完整(false = 有项未恢复,见 warnings;缺省 = 旧宿主未回传) */
|
|
592
|
+
rollbackFull?: boolean;
|
|
593
|
+
/** 执行后仍待裁决的分区(下次同步仍会出现) */
|
|
594
|
+
pendingConflicts: SectionId[];
|
|
595
|
+
failed?: { itemId: string; message?: string }[];
|
|
596
|
+
ineffective?: { itemId: string; adapter: string; message?: string }[];
|
|
597
|
+
historyWriteError?: string;
|
|
598
|
+
}
|
|
599
|
+
|
|
444
600
|
/* ---------------------------------------------------------------- 自动同步 */
|
|
445
601
|
|
|
446
602
|
/** 统一间隔类型。 */
|
|
@@ -449,10 +605,24 @@ export type AutosyncInterval = '5m' | '15m' | '30m' | '60m' | '6h' | '12h' | '24
|
|
|
449
605
|
/** 最近一次自动同步执行状态。 */
|
|
450
606
|
export type AutosyncRunStatus = 'success' | 'skipped' | 'failed' | 'partial';
|
|
451
607
|
|
|
608
|
+
/** 自动应用的最高风险等级(low = 只自动落低风险分区;medium = plugins/mcp 也自动落盘)。 */
|
|
609
|
+
export type AutosyncAutoApplyTier = 'low' | 'medium';
|
|
610
|
+
|
|
611
|
+
/** 双侧都改(真冲突)时的策略(ask = 保留冲突待人工裁决;local = 保留本机并推上去)。 */
|
|
612
|
+
export type AutosyncConflictPolicy = 'ask' | 'local';
|
|
613
|
+
|
|
452
614
|
/** GET/POST /sync/autosync 响应:自动同步状态。 */
|
|
453
615
|
export interface AutosyncStatusResponse {
|
|
454
616
|
enabled: boolean;
|
|
455
617
|
interval: AutosyncInterval;
|
|
618
|
+
/** 真·自动同步:本地改动即时触发(缺省 true;host 读取层已补缺省) */
|
|
619
|
+
realtime?: boolean;
|
|
620
|
+
/** realtime 下的远端轮询间隔(ms;缺省 60000) */
|
|
621
|
+
remoteCheckMs?: number;
|
|
622
|
+
/** 自动应用的最高风险等级(缺省 medium:plugins/mcp 也自动落盘) */
|
|
623
|
+
autoApplyTier?: AutosyncAutoApplyTier;
|
|
624
|
+
/** 双侧都改时的策略(缺省 local:保留本机并推上去) */
|
|
625
|
+
conflictPolicy?: AutosyncConflictPolicy;
|
|
456
626
|
lastRunAt?: string;
|
|
457
627
|
lastRunStatus?: AutosyncRunStatus;
|
|
458
628
|
lastRunMessage?: string;
|
|
@@ -477,6 +647,14 @@ export interface AutosyncUpdatePayload {
|
|
|
477
647
|
enabled: boolean;
|
|
478
648
|
interval?: AutosyncInterval;
|
|
479
649
|
startupMinIntervalMs?: number;
|
|
650
|
+
/** 本地改动即时触发(缺省 true) */
|
|
651
|
+
realtime?: boolean;
|
|
652
|
+
/** realtime 下的远端轮询间隔(ms;宿主 clamp 到 15s–1h) */
|
|
653
|
+
remoteCheckMs?: number;
|
|
654
|
+
/** 自动应用等级(缺省 medium) */
|
|
655
|
+
autoApplyTier?: AutosyncAutoApplyTier;
|
|
656
|
+
/** 冲突策略(缺省 local) */
|
|
657
|
+
conflictPolicy?: AutosyncConflictPolicy;
|
|
480
658
|
}
|
|
481
659
|
|
|
482
660
|
/* ---------------------------------------------------------------- 同步历史 */
|
|
@@ -614,14 +792,26 @@ export class SyncApi {
|
|
|
614
792
|
* 重建 git 工作副本(issue #79 的自救入口)。
|
|
615
793
|
*
|
|
616
794
|
* - `relink`:只把 origin / push URL 重指向当前配置地址(非破坏性,不碰工作树与本地提交);
|
|
795
|
+
* - `adopt`:把当前地址的远端历史合并进工作副本(issue #104:换仓库 / 远端历史被重写后两条历史
|
|
796
|
+
* 不相交,pull --ff-only 永远失败)。不改写本地提交、可 `git revert`,冲突由 host 自动 `merge --abort`;
|
|
617
797
|
* - `reclone`:删除工作副本后重新 clone(**丢弃未上行的本地提交**)→ 必须带 `confirm: true`。
|
|
618
798
|
*
|
|
619
799
|
* 写动作:host 侧过 mutation gate(SAFE MODE / 环境锁)。
|
|
620
800
|
*/
|
|
621
|
-
async workdirReset(payload: { mode: 'relink' | 'reclone'; confirm?: boolean }): Promise<{ ok: boolean; mode: string; workDir: { facts: SyncWorkDirFacts } }> {
|
|
801
|
+
async workdirReset(payload: { mode: 'relink' | 'adopt' | 'reclone'; confirm?: boolean }): Promise<{ ok: boolean; mode: string; workDir: { facts: SyncWorkDirFacts } }> {
|
|
622
802
|
return postJson<{ ok: boolean; mode: string; workDir: { facts: SyncWorkDirFacts } }>(SYNC_API.workdirReset, payload, this.t, SYNC_OPTS);
|
|
623
803
|
}
|
|
624
804
|
|
|
805
|
+
/**
|
|
806
|
+
* cloud-sync 借鉴项 ②:连通性自检(**只读**)。
|
|
807
|
+
*
|
|
808
|
+
* 失败回 \`ok:false\` 而非抛错 —— 调用方必须按 \`result.ok\` 分流,把失败当**结论**展示,
|
|
809
|
+
* 不要落进 catch(否则「连不上」会被渲染成崩溃)。
|
|
810
|
+
*/
|
|
811
|
+
async probe(payload: SyncPushPayload): Promise<SyncProbeResult> {
|
|
812
|
+
return postJson<SyncProbeResult>(SYNC_API.probe, payload, this.t, SYNC_OPTS);
|
|
813
|
+
}
|
|
814
|
+
|
|
625
815
|
/**
|
|
626
816
|
* 分区条目清单(复用 /export-preview;**只读**,不写任何内容)。
|
|
627
817
|
*
|
|
@@ -777,6 +967,26 @@ export class SyncApi {
|
|
|
777
967
|
return postJson<SyncRollbackResponse>(SYNC_API.rollback, payload, this.t, SYNC_OPTS);
|
|
778
968
|
}
|
|
779
969
|
|
|
970
|
+
/**
|
|
971
|
+
* T-ui-merge:远程合并**只读**预览(三方合并 + 冲突清单 + 摘要),零写入。
|
|
972
|
+
*
|
|
973
|
+
* 与 /sync/sync 的区别:这里不做导入计划、不登记同步会话,只回答「远端与本地差在哪、
|
|
974
|
+
* 哪些分区需要我裁决」;密码只在请求体内传输(宿主绝不回传/落日志)。
|
|
975
|
+
*/
|
|
976
|
+
async mergePreview(payload: MergePreviewPayload = {}): Promise<MergePreviewResponse> {
|
|
977
|
+
return postJson<MergePreviewResponse>(SYNC_API.mergePreview, payload, this.t, SYNC_OPTS);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* T-ui-merge:按逐项裁决执行合并(写动作,宿主侧过 mutation gate)。
|
|
982
|
+
*
|
|
983
|
+
* 返回 pendingConflicts = 执行后**仍**待裁决的分区:界面据此告诉用户
|
|
984
|
+
* 「跳过 ≠ 已解决,下次同步仍会出现」,而不是把一次带 skip 的执行报成「已解决」。
|
|
985
|
+
*/
|
|
986
|
+
async applyMerge(payload: MergeApplyPayload): Promise<MergeApplyResponse> {
|
|
987
|
+
return postJson<MergeApplyResponse>(SYNC_API.applyMerge, payload, this.t, SYNC_OPTS);
|
|
988
|
+
}
|
|
989
|
+
|
|
780
990
|
/** Phase 7 迁移前咨询(只读健康评分 + 建议):对远端快照生成咨询报告。 */
|
|
781
991
|
async consult(input: { type: 'remote-snapshot'; id: string; snapshotId?: string }): Promise<ConsultReport> {
|
|
782
992
|
return postJson<ConsultReport>(CONFIG_MANAGER_API.consult, input, this.t, SYNC_OPTS);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 方案 A 入口编排单测(node:test,零依赖):只发**一次**预览请求 + fail-safe 回退。
|
|
3
|
+
* 重点不是「happy path」,而是三条**绝不挡老路径**的判据:无基准、请求抛错、宿主 ok=false。
|
|
4
|
+
*/
|
|
5
|
+
import test from 'node:test'
|
|
6
|
+
import assert from 'node:assert/strict'
|
|
7
|
+
|
|
8
|
+
import { resolveSmartSync } from './sync-entry.ts'
|
|
9
|
+
import type { MergePreviewResponse, SyncApi } from './sync-api.ts'
|
|
10
|
+
|
|
11
|
+
/** 假 api:只记录 mergePreview 的调用次数(其余成员本测试用不到)。 */
|
|
12
|
+
function fakeApi(result: MergePreviewResponse | Error): { api: SyncApi; calls: () => number } {
|
|
13
|
+
let calls = 0
|
|
14
|
+
const api = {
|
|
15
|
+
mergePreview: async (): Promise<MergePreviewResponse> => {
|
|
16
|
+
calls += 1
|
|
17
|
+
if (result instanceof Error) throw result
|
|
18
|
+
return result
|
|
19
|
+
},
|
|
20
|
+
} as unknown as SyncApi
|
|
21
|
+
return { api, calls: () => calls }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function preview(overrides: Partial<MergePreviewResponse> = {}): MergePreviewResponse {
|
|
25
|
+
return {
|
|
26
|
+
ok: true,
|
|
27
|
+
snapshotId: 'snap-1',
|
|
28
|
+
hasAncestor: true,
|
|
29
|
+
sections: [{ id: 'settings', decision: 'conflict', conflictCount: 1, conflicts: [] }],
|
|
30
|
+
summary: { auto: 0, review: 1, conflicts: 1, skipped: 0 },
|
|
31
|
+
...overrides,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const deps = (api: SyncApi, hasBaseline: unknown, decryptPassword?: string) => ({
|
|
36
|
+
api,
|
|
37
|
+
hasBaseline,
|
|
38
|
+
channelPayload: () => ({}),
|
|
39
|
+
...(decryptPassword === undefined ? {} : { decryptPassword }),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('sync-entry: 无基准(含未知/缺省)→ fallback,且**零请求**(行为与方案 A 之前逐字一致)', async () => {
|
|
43
|
+
for (const hasBaseline of [undefined, false, null, 'true', 1]) {
|
|
44
|
+
const { api, calls } = fakeApi(preview())
|
|
45
|
+
const out = await resolveSmartSync(deps(api, hasBaseline))
|
|
46
|
+
assert.deepEqual(out, { kind: 'fallback' }, 'hasBaseline=' + String(hasBaseline))
|
|
47
|
+
assert.equal(calls(), 0, '无基准时不得发预览请求')
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('sync-entry: 有基准 + 有变更 → merge,且**只请求一次**并原样复用该 preview', async () => {
|
|
52
|
+
const seeded = preview()
|
|
53
|
+
const { api, calls } = fakeApi(seeded)
|
|
54
|
+
const out = await resolveSmartSync(deps(api, true))
|
|
55
|
+
assert.equal(out.kind, 'merge')
|
|
56
|
+
assert.equal(calls(), 1, '预览只取一次(视图不得二次请求)')
|
|
57
|
+
assert.equal(out.kind === 'merge' ? out.preview : null, seeded, '复用同一次结果(同一引用)')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('sync-entry: 有基准 + 全 skip → upToDate(提示已是最新,不写任何东西)', async () => {
|
|
61
|
+
const { api, calls } = fakeApi(preview({ sections: [{ id: 'settings', decision: 'skip', conflictCount: 0, conflicts: [] }] }))
|
|
62
|
+
assert.deepEqual(await resolveSmartSync(deps(api, true)), { kind: 'upToDate' })
|
|
63
|
+
assert.equal(calls(), 1)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('sync-entry: 预览请求抛错 → fallback + 错误文本(新入口绝不挡住老路径)', async () => {
|
|
67
|
+
const { api, calls } = fakeApi(new Error('network down'))
|
|
68
|
+
const out = await resolveSmartSync(deps(api, true))
|
|
69
|
+
assert.equal(out.kind, 'fallback')
|
|
70
|
+
assert.equal(out.kind === 'fallback' ? out.error : null, 'network down')
|
|
71
|
+
assert.equal(calls(), 1)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('sync-entry: 宿主 ok=false → fallback + 宿主 message(如实带出原因,不回退成静默)', async () => {
|
|
75
|
+
const { api } = fakeApi(preview({ ok: false, sections: [], message: '远端没有快照' }))
|
|
76
|
+
const out = await resolveSmartSync(deps(api, true))
|
|
77
|
+
assert.deepEqual(out, { kind: 'fallback', error: '远端没有快照' })
|
|
78
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 方案 A 的「一键同步」入口编排(宿主调用 + 结果归类)。
|
|
3
|
+
*
|
|
4
|
+
* 分工:**决策**是纯函数(merge-view.ts 的 `decideSyncEntry()`,判据在那里冻结);
|
|
5
|
+
* 本文件只负责「按需发**一次** /sync/merge-preview + 把结果归成三类」,让 SyncPanel 保持装配层。
|
|
6
|
+
* 单独成文件的第二个理由:fail-safe 回退(无基准 / 请求失败 / ok=false 一律回老导入确认路径)
|
|
7
|
+
* 是最需要单测的地方,而组件层在本仓库没有测试框架(见 AGENTS.md)。
|
|
8
|
+
*/
|
|
9
|
+
import { decideSyncEntry, hasMergeBaseline } from './merge-view.ts'
|
|
10
|
+
import type { MergePreviewResponse, SyncApi, SyncPushPayload } from './sync-api.ts'
|
|
11
|
+
|
|
12
|
+
export interface SmartSyncDeps {
|
|
13
|
+
api: SyncApi
|
|
14
|
+
/** /sync/status 的 hasBaseline;unknown = 缺省/旧宿主(一律按无基准走老路径) */
|
|
15
|
+
hasBaseline: unknown
|
|
16
|
+
/** 通道请求体(含 transport / 地址 / 凭据引用;与 push/pull 同源) */
|
|
17
|
+
channelPayload: () => SyncPushPayload
|
|
18
|
+
/** 解密密码(可选,仅内存) */
|
|
19
|
+
decryptPassword?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 分派结果:
|
|
24
|
+
* - 'fallback':走老路径(`runSync()` 导入确认)。可能带 error(预览请求失败或宿主 ok=false);
|
|
25
|
+
* - 'merge' :打开裁决视图,**复用本次 preview**(调用方不得再请求一次);
|
|
26
|
+
* - 'upToDate':全部 skip / 无分区 → 提示「已是最新」,零写入。
|
|
27
|
+
*/
|
|
28
|
+
export type SmartSyncOutcome =
|
|
29
|
+
| { kind: 'merge'; preview: MergePreviewResponse }
|
|
30
|
+
| { kind: 'upToDate' }
|
|
31
|
+
| { kind: 'fallback'; error?: string }
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 解析本次一键同步该走哪条路。**无基准时不发任何请求**(行为与方案 A 之前逐字一致)。
|
|
35
|
+
* 预览请求失败绝不抛:转成 'fallback' + error 文本,由调用方 toast 后继续走老路径。
|
|
36
|
+
*/
|
|
37
|
+
export async function resolveSmartSync(deps: SmartSyncDeps): Promise<SmartSyncOutcome> {
|
|
38
|
+
// 判据与 decideSyncEntry 的第一条**同源**(hasMergeBaseline);这里提前返回只是为了不发请求
|
|
39
|
+
if (!hasMergeBaseline(deps.hasBaseline)) return { kind: 'fallback' }
|
|
40
|
+
const password = deps.decryptPassword !== undefined && deps.decryptPassword !== ''
|
|
41
|
+
? { decryptPassword: deps.decryptPassword }
|
|
42
|
+
: {}
|
|
43
|
+
try {
|
|
44
|
+
const preview = await deps.api.mergePreview({ ...deps.channelPayload(), ...password })
|
|
45
|
+
const decision = decideSyncEntry(deps.hasBaseline, preview)
|
|
46
|
+
if (decision === 'merge') return { kind: 'merge', preview }
|
|
47
|
+
if (decision === 'upToDate') return { kind: 'upToDate' }
|
|
48
|
+
// ok === false:宿主明确说预览没产出(例如远端没有快照)→ 老路径 + 如实带出原因
|
|
49
|
+
return { kind: 'fallback', ...(preview.message !== undefined ? { error: preview.message } : {}) }
|
|
50
|
+
} catch (err) {
|
|
51
|
+
return { kind: 'fallback', error: err instanceof Error ? err.message : String(err) }
|
|
52
|
+
}
|
|
53
|
+
}
|