dsh-config-manager 0.1.19 → 0.1.21

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.
Files changed (82) hide show
  1. package/lib/client.d.ts +319 -2
  2. package/lib/client.js +1271 -109
  3. package/lib/client.js.map +1 -1
  4. package/lib/core/run-registry.d.ts +2 -2
  5. package/lib/core/run-registry.js +5 -1
  6. package/lib/core/run-registry.js.map +1 -1
  7. package/lib/index.js +408 -2
  8. package/lib/index.js.map +1 -1
  9. package/lib/sync/ancestor.d.ts +17 -0
  10. package/lib/sync/ancestor.js +64 -0
  11. package/lib/sync/ancestor.js.map +1 -0
  12. package/lib/sync/autosync-config.d.ts +34 -0
  13. package/lib/sync/autosync-config.js +116 -0
  14. package/lib/sync/autosync-config.js.map +1 -0
  15. package/lib/sync/autosync-scheduler.d.ts +95 -0
  16. package/lib/sync/autosync-scheduler.js +379 -0
  17. package/lib/sync/autosync-scheduler.js.map +1 -0
  18. package/lib/sync/merge.d.ts +42 -0
  19. package/lib/sync/merge.js +325 -0
  20. package/lib/sync/merge.js.map +1 -0
  21. package/lib/sync/review-queue.d.ts +51 -0
  22. package/lib/sync/review-queue.js +119 -0
  23. package/lib/sync/review-queue.js.map +1 -0
  24. package/lib/sync/risk.d.ts +59 -0
  25. package/lib/sync/risk.js +76 -0
  26. package/lib/sync/risk.js.map +1 -0
  27. package/lib/sync/sync-config.d.ts +7 -2
  28. package/lib/sync/sync-config.js +24 -4
  29. package/lib/sync/sync-config.js.map +1 -1
  30. package/lib/sync/sync-engine.d.ts +88 -2
  31. package/lib/sync/sync-engine.js +333 -8
  32. package/lib/sync/sync-engine.js.map +1 -1
  33. package/lib/sync/sync-history.d.ts +39 -0
  34. package/lib/sync/sync-history.js +88 -0
  35. package/lib/sync/sync-history.js.map +1 -0
  36. package/lib/sync/sync-session.d.ts +39 -0
  37. package/lib/sync/sync-session.js +54 -0
  38. package/lib/sync/sync-session.js.map +1 -0
  39. package/lib/sync/sync-state.d.ts +7 -2
  40. package/lib/sync/sync-state.js +9 -5
  41. package/lib/sync/sync-state.js.map +1 -1
  42. package/lib/ui/i18n.d.ts +63 -0
  43. package/lib/ui/i18n.js +130 -1
  44. package/lib/ui/i18n.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/client/config-manager.module.css +18 -0
  47. package/src/client/sync/SyncConfirmView.tsx +301 -0
  48. package/src/client/sync/SyncHistoryView.test.ts +40 -0
  49. package/src/client/sync/SyncHistoryView.tsx +155 -0
  50. package/src/client/sync/SyncSettingsView.tsx +241 -12
  51. package/src/client/sync/history-model.test.ts +82 -0
  52. package/src/client/sync/history-model.ts +112 -0
  53. package/src/client/sync/sync-api.test.ts +155 -0
  54. package/src/client/sync/sync-api.ts +219 -0
  55. package/src/client/sync/sync-locales.ts +162 -1
  56. package/src/client/sync/sync-view-v2.test.ts +131 -0
  57. package/src/client/sync/sync-view.ts +151 -4
  58. package/src/core/run-registry.ts +7 -3
  59. package/src/index.ts +420 -3
  60. package/src/sync/ancestor.test.ts +152 -0
  61. package/src/sync/ancestor.ts +81 -0
  62. package/src/sync/autosync-config.test.ts +93 -0
  63. package/src/sync/autosync-config.ts +137 -0
  64. package/src/sync/autosync-scheduler.test.ts +191 -0
  65. package/src/sync/autosync-scheduler.ts +443 -0
  66. package/src/sync/merge.test.ts +204 -0
  67. package/src/sync/merge.ts +360 -0
  68. package/src/sync/review-queue.test.ts +120 -0
  69. package/src/sync/review-queue.ts +167 -0
  70. package/src/sync/risk.test.ts +131 -0
  71. package/src/sync/risk.ts +122 -0
  72. package/src/sync/sync-config.test.ts +106 -0
  73. package/src/sync/sync-config.ts +23 -4
  74. package/src/sync/sync-engine.test.ts +392 -3
  75. package/src/sync/sync-engine.ts +383 -10
  76. package/src/sync/sync-history.test.ts +85 -0
  77. package/src/sync/sync-history.ts +126 -0
  78. package/src/sync/sync-session.test.ts +137 -0
  79. package/src/sync/sync-session.ts +76 -0
  80. package/src/sync/sync-state.test.ts +48 -2
  81. package/src/sync/sync-state.ts +11 -5
  82. package/src/ui/i18n.ts +132 -3
@@ -1,6 +1,6 @@
1
1
  import type { MsgFunc } from './messages.ts';
2
- /** run 类型:导出 / 导入。 */
3
- export type RunKind = 'export' | 'import';
2
+ /** run 类型:导出 / 导入 / 自动同步 / 一键同步逐项应用。 */
3
+ export type RunKind = 'export' | 'import' | 'autosync' | 'sync-apply';
4
4
  /** run 状态:进行中 / 完成 / 失败。 */
5
5
  export type RunStatus = 'running' | 'done' | 'failed';
6
6
  /** 单个 run 的实时状态(/progress 返回体;纯 JSON 可序列化)。 */
@@ -26,7 +26,11 @@ export class RunConflictError extends Error {
26
26
  constructor(runId, kind, msg = zhMsg) {
27
27
  super(kind === 'export'
28
28
  ? msg('run.conflict.export', { runId })
29
- : msg('run.conflict.import', { runId }));
29
+ : kind === 'autosync'
30
+ ? '自动同步已在进行中'
31
+ : kind === 'sync-apply'
32
+ ? '一键同步已在进行中'
33
+ : msg('run.conflict.import', { runId }));
30
34
  this.name = 'RunConflictError';
31
35
  this.runId = runId;
32
36
  }
@@ -1 +1 @@
1
- {"version":3,"file":"run-registry.js","sourceRoot":"","sources":["../../src/core/run-registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run-registry.js","sourceRoot":"","sources":["../../src/core/run-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAiCrC,0CAA0C;AAC1C,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzC,iCAAiC;IACxB,KAAK,CAAQ;IACtB,YAAY,KAAa,EAAE,IAAa,EAAE,MAAe,KAAK;QAC5D,KAAK,CACH,IAAI,KAAK,QAAQ;YACf,CAAC,CAAC,GAAG,CAAC,qBAAqB,EAAE,EAAE,KAAK,EAAE,CAAC;YACvC,CAAC,CAAC,IAAI,KAAK,UAAU;gBACnB,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,IAAI,KAAK,YAAY;oBACrB,CAAC,CAAC,WAAW;oBACb,CAAC,CAAC,GAAG,CAAC,qBAAqB,EAAE,EAAE,KAAK,EAAE,CAAC,CAC9C,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAA;QAC9B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;CACF;AAED,4BAA4B;AAC5B,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAWtD;;;GAGG;AACH,MAAM,OAAO,WAAW;IACL,IAAI,GAAG,IAAI,GAAG,EAAoB,CAAA;IAClC,WAAW,CAAQ;IACnB,GAAG,CAAc;IACjB,GAAG,CAAS;IAE7B,YAAY,OAA2B,EAAE;QACvC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,wBAAwB,CAAA;QAC/D,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;QACzC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,KAAK,CAAA;IAC9B,CAAC;IAED,qDAAqD;IACrD,QAAQ,CAAC,IAAa;QACpB,IAAI,CAAC,KAAK,EAAE,CAAA;QACZ,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YACrC,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;gBAAE,MAAM,IAAI,gBAAgB,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;QAC1G,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,KAAK,GAAa;YACtB,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;YACtC,IAAI;YACJ,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,IAAI;YACb,YAAY,EAAE,IAAI;YAClB,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG;SACf,CAAA;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QACjC,OAAO,EAAE,GAAG,KAAK,EAAE,CAAA;IACrB,CAAC;IAED,0DAA0D;IAC1D,MAAM,CACJ,KAAa,EACb,KAA4F;QAE5F,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAChC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QACvC,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,EAAE,GAAG,GAAG,EAAE,CAAA;QAC/C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;QACpD,OAAO,EAAE,GAAG,GAAG,EAAE,CAAA;IACnB,CAAC;IAED,qCAAqC;IACrC,GAAG,CAAC,KAAa;QACf,IAAI,CAAC,KAAK,EAAE,CAAA;QACZ,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAChC,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAA;IACnD,CAAC;IAED,2CAA2C;IAC3C,UAAU;QACR,IAAI,CAAC,KAAK,EAAE,CAAA;QACZ,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IAC7F,CAAC;IAED,uCAAuC;IACvC,MAAM,CAAC,KAAa,EAAE,MAAe;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAChC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QACvC,GAAG,CAAC,MAAM,GAAG,MAAM,CAAA;QACnB,GAAG,CAAC,MAAM,GAAG,MAAM,CAAA;QACnB,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAC1B,OAAO,EAAE,GAAG,GAAG,EAAE,CAAA;IACnB,CAAC;IAED,uCAAuC;IACvC,IAAI,CAAC,KAAa,EAAE,KAAa;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAChC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QACvC,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAA;QACrB,GAAG,CAAC,KAAK,GAAG,KAAK,CAAA;QACjB,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAC1B,OAAO,EAAE,GAAG,GAAG,EAAE,CAAA;IACnB,CAAC;IAED,8CAA8C;IACtC,KAAK;QACX,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,CAAA;QAC5C,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAClC,IAAI,GAAG,CAAC,SAAS,GAAG,MAAM;gBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAClD,CAAC;IACH,CAAC;CACF"}
package/lib/index.js CHANGED
@@ -45,6 +45,7 @@ import { dshHomePath, resolveDshHome } from '@deepseek-ai/dsh-home-paths';
45
45
  import * as yaml from 'js-yaml';
46
46
  import { Exporter, FileSnapshotStore, Importer } from "./core/index.js";
47
47
  import { listSnapshots, planRestore } from "./core/restore.js";
48
+ import { rollback as performRollback } from "./core/rollback.js";
48
49
  import { RunRegistry } from "./core/run-registry.js";
49
50
  import { makeMsg, msgOf, zhMsg } from "./core/messages.js";
50
51
  import { hasDshBundlePatch, installErrorFor, installSpecFor, listInstalledPlugins, resolveProfileDir, resolveProfileNameFromArgv, runDshPlugin, validateProfileName, } from "./core/plugin-cli.js";
@@ -54,7 +55,11 @@ import { createHardenedZipParser } from "./security/zip-security.js";
54
55
  import { GitTransport } from "./sync/git/git-transport.js";
55
56
  import { DeviceFlowStore, GitHubAuthClient } from "./sync/github-auth.js";
56
57
  import { SyncEngine } from "./sync/sync-engine.js";
57
- import { loadSyncState } from "./sync/sync-state.js";
58
+ import { SyncSessionStore } from "./sync/sync-session.js";
59
+ import { AutoSyncScheduler } from "./sync/autosync-scheduler.js";
60
+ import { defaultAutosyncConfig, readAutosyncConfig, writeAutosyncConfig } from "./sync/autosync-config.js";
61
+ import { appendAutosyncEntry, readSyncHistory } from "./sync/sync-history.js";
62
+ import { loadSyncState, saveSyncState } from "./sync/sync-state.js";
58
63
  import { readSyncConfig, writeSyncConfig, validateRepoUrl } from "./sync/sync-config.js";
59
64
  import { MANIFEST_FILE, parseManifest } from "./schema/manifest.js";
60
65
  import { SECTION_IDS } from "./schema/config.js";
@@ -67,7 +72,7 @@ export const name = 'config-manager';
67
72
  /** Services required before the engine can mount (present in every profile). */
68
73
  export const inject = ['settings', 'credentials'];
69
74
  /** Plugin version, kept in sync with package.json ("version"). */
70
- const PLUGIN_VERSION = '0.1.19';
75
+ const PLUGIN_VERSION = '0.1.20';
71
76
  /**
72
77
  * 内置 GitHub OAuth App 的 client_id(「使用 GitHub 登录」device flow 缺省值)。
73
78
  * Client ID 是公开标识(GitHub 官方明确非机密):内置后所有安装者开箱即用,
@@ -97,6 +102,15 @@ const API = {
97
102
  syncGithubStart: '/api/dsh-config-manager/sync/github/start',
98
103
  syncGithubPoll: '/api/dsh-config-manager/sync/github/poll',
99
104
  syncGithubCancel: '/api/dsh-config-manager/sync/github/cancel',
105
+ // P2:同步历史 / 自动应用 / 一键回滚
106
+ syncHistory: '/api/dsh-config-manager/sync/history',
107
+ syncRollback: '/api/dsh-config-manager/sync/rollback',
108
+ // m-sync-v2:一键同步(差异确认会话)+ 自动同步 + 历史快照
109
+ syncSnapshotsList: '/api/dsh-config-manager/sync/snapshots-list',
110
+ syncSync: '/api/dsh-config-manager/sync/sync',
111
+ syncApplyItems: '/api/dsh-config-manager/sync/apply-items',
112
+ syncCancel: '/api/dsh-config-manager/sync/cancel',
113
+ syncAutosync: '/api/dsh-config-manager/sync/autosync',
100
114
  };
101
115
  /**
102
116
  * 同步 token 的 DSH credentials 引用名(POSIX env-var 形态,满足 CredentialRef 品牌要求)。
@@ -722,6 +736,59 @@ export function writeSyncRouteError(res, error) {
722
736
  const message = error instanceof Error ? error.message : String(error);
723
737
  writeJson(res, 500, { error: message });
724
738
  }
739
+ /** 需要人工决策的 PlanItemKind(一键同步 needsReview 判定 + 逐项确认标记) */
740
+ const REVIEW_KINDS = new Set([
741
+ 'Conflict', 'MissingSecret', 'MissingDependency', 'Install', 'Error', 'PathMapping',
742
+ ]);
743
+ /** 把 ImportPlan 投影为逐项可确认的差异项(默认采用 Create/Update/Install;人工项默认不采用)。 */
744
+ function planToConfirmItems(plan) {
745
+ return plan.items.map((item) => {
746
+ const manual = REVIEW_KINDS.has(item.kind);
747
+ let conflict;
748
+ if (item.kind === 'Conflict') {
749
+ const c = item.conflict;
750
+ conflict = {
751
+ path: c?.path ?? '$',
752
+ kind: c?.kind === 'file' ? 'file' : c?.kind === 'section' ? 'section' : 'key',
753
+ ...(c?.local !== undefined ? { local: c.local } : {}),
754
+ ...(c?.remote !== undefined ? { remote: c.remote } : {}),
755
+ ...(c?.ancestor !== undefined ? { ancestor: c.ancestor } : {}),
756
+ };
757
+ }
758
+ return {
759
+ itemId: item.id,
760
+ adapter: item.adapter,
761
+ kind: item.kind,
762
+ description: item.description,
763
+ severity: item.severity,
764
+ defaultAdopt: !manual,
765
+ adopt: !manual,
766
+ ...(conflict !== undefined ? { conflict } : {}),
767
+ ...(item.target !== undefined ? { target: item.target } : {}),
768
+ };
769
+ });
770
+ }
771
+ /** autosync interval 类型守卫 */
772
+ function isAutosyncInterval(v) {
773
+ return v === '5m' || v === '15m' || v === '30m' || v === '60m' || v === '6h' || v === '12h' || v === '24h';
774
+ }
775
+ /** 自动同步状态响应(GET /sync/autosync 与 POST 回填;读盘计算 elapsedMs)。 */
776
+ async function buildAutosyncStatus(dir) {
777
+ const cfg = await readAutosyncConfig(dir);
778
+ const elapsedMs = cfg.lastRunAt === undefined || cfg.lastRunAt === ''
779
+ ? -1
780
+ : Math.max(0, Date.now() - Date.parse(cfg.lastRunAt));
781
+ return {
782
+ enabled: cfg.enabled,
783
+ interval: cfg.interval,
784
+ ...(cfg.lastRunAt !== undefined ? { lastRunAt: cfg.lastRunAt } : {}),
785
+ ...(cfg.lastRunStatus !== undefined ? { lastRunStatus: cfg.lastRunStatus } : {}),
786
+ ...(cfg.lastRunMessage !== undefined ? { lastRunMessage: cfg.lastRunMessage } : {}),
787
+ consecutiveFailures: cfg.consecutiveFailures,
788
+ elapsedMs,
789
+ ...(cfg.lastRunHistoryId !== undefined ? { lastRunHistoryId: cfg.lastRunHistoryId } : {}),
790
+ };
791
+ }
725
792
  export function buildRestoreBody(body) {
726
793
  if (body === null || typeof body !== 'object' || Array.isArray(body)) {
727
794
  return { ok: false, error: 'invalid JSON body' };
@@ -914,6 +981,19 @@ function makeRoutes(deps) {
914
981
  msg,
915
982
  });
916
983
  };
984
+ /** 一键同步差异确认会话存储(进程内存;/sync/sync 预览 → /sync/apply-items 逐项执行解耦) */
985
+ const syncSessions = new SyncSessionStore();
986
+ /** 自动同步后台调度器(宿主进程生命周期,不依赖浏览器) */
987
+ let scheduler;
988
+ scheduler = new AutoSyncScheduler({
989
+ syncDir,
990
+ host,
991
+ makeSyncEngine,
992
+ msg,
993
+ runs,
994
+ });
995
+ // 启动:读 autosync-config;若 enabled 启动定时器;无条件执行一次「启动触发下载合并」(受阈值约束)
996
+ scheduler.start();
917
997
  return [
918
998
  // ------------------------------------------------------------- status
919
999
  {
@@ -1289,6 +1369,7 @@ function makeRoutes(deps) {
1289
1369
  lastSyncAt: state.lastSyncAt === '' ? undefined : state.lastSyncAt,
1290
1370
  sectionCount: Object.keys(state.sections).length,
1291
1371
  transport: state.transport,
1372
+ autosync: await buildAutosyncStatus(syncDir),
1292
1373
  });
1293
1374
  }
1294
1375
  catch (error) {
@@ -1461,6 +1542,331 @@ function makeRoutes(deps) {
1461
1542
  writeJson(res, 200, { ok: true });
1462
1543
  },
1463
1544
  },
1545
+ // ------------------------------------------------------ sync/history
1546
+ // P2:列出本地祖先快照目录的 manifest.json(id/createdAt/sectionHashes),
1547
+ // 同时统计 review-queue 中关联到该 snapshotId 的项数。
1548
+ {
1549
+ kind: 'exact',
1550
+ path: API.syncHistory,
1551
+ handler: async (req, res) => {
1552
+ if (!guard(req, res, 'GET'))
1553
+ return;
1554
+ try {
1555
+ const localDir = join(syncDir, 'snapshots');
1556
+ const entries = await fs.readdir(localDir).catch(() => []);
1557
+ const rows = [];
1558
+ for (const name of entries) {
1559
+ const dir = join(localDir, name);
1560
+ const stat = await fs.stat(dir).catch(() => null);
1561
+ if (!stat?.isDirectory())
1562
+ continue;
1563
+ const manifestPath = join(dir, 'manifest.json');
1564
+ const raw = await fs.readFile(manifestPath, 'utf8').catch(() => null);
1565
+ if (raw === null)
1566
+ continue;
1567
+ try {
1568
+ const m = JSON.parse(raw);
1569
+ if (typeof m.id !== 'string' || typeof m.createdAt !== 'string')
1570
+ continue;
1571
+ const sectionCount = m.sectionHashes && typeof m.sectionHashes === 'object'
1572
+ ? Object.keys(m.sectionHashes).length
1573
+ : 0;
1574
+ rows.push({ id: m.id, createdAt: m.createdAt, sectionCount, reviewCount: 0 });
1575
+ }
1576
+ catch { /* skip malformed */ }
1577
+ }
1578
+ // 关联 review-queue 计数
1579
+ const rqPath = join(syncDir, 'sync-review-queue.json');
1580
+ const rqRaw = await fs.readFile(rqPath, 'utf8').catch(() => null);
1581
+ if (rqRaw !== null) {
1582
+ try {
1583
+ const rq = JSON.parse(rqRaw);
1584
+ const byId = new Map();
1585
+ for (const it of rq.items ?? []) {
1586
+ if (typeof it.snapshotId === 'string') {
1587
+ byId.set(it.snapshotId, (byId.get(it.snapshotId) ?? 0) + 1);
1588
+ }
1589
+ }
1590
+ for (const r of rows) {
1591
+ const c = byId.get(r.id);
1592
+ if (c !== undefined)
1593
+ r.reviewCount = c;
1594
+ }
1595
+ }
1596
+ catch { /* skip */ }
1597
+ }
1598
+ rows.sort((a, b) => (a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0));
1599
+ // 合并自动同步执行记录(sync-history.json)
1600
+ const hist = await readSyncHistory(syncDir);
1601
+ const merged = [
1602
+ ...rows.map((r) => ({ ...r, kind: 'apply' })),
1603
+ ...hist.autosyncEntries.map((e) => ({
1604
+ id: e.createdAt,
1605
+ createdAt: e.createdAt,
1606
+ kind: 'autosync',
1607
+ autosync: e,
1608
+ })),
1609
+ ].sort((a, b) => (a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0));
1610
+ writeJson(res, 200, { entries: merged });
1611
+ }
1612
+ catch (error) {
1613
+ writeJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
1614
+ }
1615
+ },
1616
+ },
1617
+ // ------------------------------------------------------ sync/snapshots-list
1618
+ // m-sync-v2:远端历史快照列表(供「选择历史快照」下拉)。
1619
+ {
1620
+ kind: 'exact',
1621
+ path: API.syncSnapshotsList,
1622
+ handler: async (req, res) => {
1623
+ if (!guard(req, res, 'POST'))
1624
+ return;
1625
+ const body = await readJsonBody(req);
1626
+ if (body === undefined) {
1627
+ writeJson(res, 400, { error: 'invalid JSON body' });
1628
+ return;
1629
+ }
1630
+ try {
1631
+ const { repoUrl, gitBin } = await prepareSync(body);
1632
+ const engine = makeSyncEngine(repoUrl, gitBin);
1633
+ const metas = await withTimeout(engine.listSnapshots(), ROUTE_TIMEOUT_MS, msg('host.syncPullTimeout'));
1634
+ const snapshots = [...metas]
1635
+ .sort((a, b) => (a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0))
1636
+ .map((m) => ({
1637
+ id: m.id,
1638
+ createdAt: m.createdAt,
1639
+ sectionCount: m.manifest.sectionIds.length,
1640
+ platform: m.manifest.platform,
1641
+ dshVersion: m.manifest.dshVersion,
1642
+ }));
1643
+ const state = await loadSyncState(syncDir);
1644
+ writeJson(res, 200, { ok: true, snapshots, currentSnapshotId: state.lastSnapshotId === '' ? undefined : state.lastSnapshotId });
1645
+ }
1646
+ catch (error) {
1647
+ writeSyncRouteError(res, error);
1648
+ }
1649
+ },
1650
+ },
1651
+ // ------------------------------------------------------ sync/sync
1652
+ // m-sync-v2:一键同步第一步 —— 拉取 → 差异确认会话(内存登记临时 ZIP + ImportPlan)。
1653
+ {
1654
+ kind: 'exact',
1655
+ path: API.syncSync,
1656
+ handler: async (req, res) => {
1657
+ if (!guard(req, res, 'POST'))
1658
+ return;
1659
+ const body = await readJsonBody(req);
1660
+ if (body === undefined) {
1661
+ writeJson(res, 400, { error: 'invalid JSON body' });
1662
+ return;
1663
+ }
1664
+ try {
1665
+ const { repoUrl, gitBin } = await prepareSync(body);
1666
+ const engine = makeSyncEngine(repoUrl, gitBin);
1667
+ const snapshotId = typeof body['snapshotId'] === 'string' && body['snapshotId'] !== '' ? body['snapshotId'] : undefined;
1668
+ const preview = await withTimeout(engine.preview(snapshotId === undefined ? {} : { snapshotId }), ROUTE_TIMEOUT_MS, msg('host.syncPullTimeout'));
1669
+ if (!preview.ok || preview.plan === null || preview.analysis === null) {
1670
+ writeJson(res, 200, { ok: false, syncSessionId: '', snapshotId: preview.snapshotId, items: [], needsReview: false, compatibility: 'unsupported', message: preview.message ?? '同步预览失败' });
1671
+ return;
1672
+ }
1673
+ const syncSessionId = syncSessions.set({
1674
+ zipPath: preview.zipPath,
1675
+ plan: preview.plan,
1676
+ analysis: preview.analysis,
1677
+ snapshotId: preview.snapshotId,
1678
+ repoUrl,
1679
+ gitBin,
1680
+ });
1681
+ const items = planToConfirmItems(preview.plan);
1682
+ const needsReview = items.some((i) => REVIEW_KINDS.has(i.kind)) || preview.analysis.pathIssues.length > 0;
1683
+ writeJson(res, 200, {
1684
+ ok: true,
1685
+ syncSessionId,
1686
+ snapshotId: preview.snapshotId,
1687
+ items,
1688
+ needsReview,
1689
+ compatibility: preview.analysis.compatibility,
1690
+ });
1691
+ }
1692
+ catch (error) {
1693
+ writeSyncRouteError(res, error);
1694
+ }
1695
+ },
1696
+ },
1697
+ // ------------------------------------------------------ sync/apply-items
1698
+ // m-sync-v2:一键同步第二步 —— 按用户对差异项的逐项决策执行导入。
1699
+ {
1700
+ kind: 'exact',
1701
+ path: API.syncApplyItems,
1702
+ handler: async (req, res) => {
1703
+ if (!guard(req, res, 'POST'))
1704
+ return;
1705
+ const body = await readJsonBody(req);
1706
+ if (body === undefined) {
1707
+ writeJson(res, 400, { error: 'invalid JSON body' });
1708
+ return;
1709
+ }
1710
+ try {
1711
+ const syncSessionId = typeof body['syncSessionId'] === 'string' ? body['syncSessionId'] : '';
1712
+ const session = syncSessions.get(syncSessionId);
1713
+ if (session === undefined) {
1714
+ writeJson(res, 400, { error: '同步会话不存在或已过期,请重新拉取预览' });
1715
+ return;
1716
+ }
1717
+ const adoptions = Array.isArray(body['adoptions']) ? body['adoptions'] : [];
1718
+ // 构造子计划(仅含采纳项)
1719
+ const byId = new Map();
1720
+ for (const a of adoptions) {
1721
+ if (typeof a?.['itemId'] !== 'string')
1722
+ continue;
1723
+ byId.set(a['itemId'], { adopt: a['adopt'] === true, resolution: typeof a['resolution'] === 'string' ? a['resolution'] : undefined });
1724
+ }
1725
+ const subItems = session.plan.items.filter((item) => {
1726
+ const d = byId.get(item.id);
1727
+ if (d === undefined || !d.adopt)
1728
+ return false;
1729
+ // Conflict 项必须有 resolution,且 keepLocal/skip 从子计划剔除
1730
+ if (item.kind === 'Conflict') {
1731
+ if (d.resolution === undefined)
1732
+ throw new SyncRouteError(`冲突项 ${item.id} 必须提供 resolution(useRemote/keepLocal/skip)`);
1733
+ if (d.resolution === 'keepLocal' || d.resolution === 'skip')
1734
+ return false;
1735
+ }
1736
+ return true;
1737
+ });
1738
+ const subPlan = {
1739
+ ...session.plan,
1740
+ items: subItems,
1741
+ };
1742
+ // 消费会话(同一 session 只允许一次 apply-items)
1743
+ syncSessions.delete(syncSessionId);
1744
+ await fs.rm(dirname(session.zipPath), { recursive: true, force: true }).catch(() => { });
1745
+ const engine = makeSyncEngine(session.repoUrl, session.gitBin);
1746
+ const report = await engine.applyItems(session.zipPath, subPlan, {
1747
+ onItem: (info) => { },
1748
+ });
1749
+ writeJson(res, 200, {
1750
+ ok: report.ok,
1751
+ applied: report.applied,
1752
+ skipped: subItems.map((i) => i.id),
1753
+ needsRestart: report.needsRestart === true,
1754
+ warnings: report.warnings,
1755
+ restoreId: report.restoreId,
1756
+ rolledBack: report.rolledBack,
1757
+ failed: report.failed,
1758
+ result: report.result,
1759
+ });
1760
+ }
1761
+ catch (error) {
1762
+ writeSyncRouteError(res, error);
1763
+ }
1764
+ },
1765
+ },
1766
+ // ------------------------------------------------------ sync/cancel
1767
+ // m-sync-v2:取消 / 清理差异确认会话(丢弃临时 ZIP,零副作用)。
1768
+ {
1769
+ kind: 'exact',
1770
+ path: API.syncCancel,
1771
+ handler: async (req, res) => {
1772
+ if (!guard(req, res, 'POST'))
1773
+ return;
1774
+ const body = await readJsonBody(req);
1775
+ if (body === undefined) {
1776
+ writeJson(res, 400, { error: 'invalid JSON body' });
1777
+ return;
1778
+ }
1779
+ try {
1780
+ const syncSessionId = typeof body['syncSessionId'] === 'string' ? body['syncSessionId'] : '';
1781
+ if (syncSessionId !== '') {
1782
+ const session = syncSessions.get(syncSessionId);
1783
+ if (session !== undefined) {
1784
+ await fs.rm(dirname(session.zipPath), { recursive: true, force: true }).catch(() => { });
1785
+ }
1786
+ syncSessions.delete(syncSessionId);
1787
+ }
1788
+ writeJson(res, 200, { ok: true });
1789
+ }
1790
+ catch (error) {
1791
+ writeSyncRouteError(res, error);
1792
+ }
1793
+ },
1794
+ },
1795
+ // ------------------------------------------------------ sync/autosync
1796
+ // m-sync-v2:自动同步配置读写(总开关 + 间隔 + 启动阈值 + 状态)。
1797
+ {
1798
+ kind: 'exact',
1799
+ path: API.syncAutosync,
1800
+ handler: async (req, res) => {
1801
+ if (!guard(req, res, 'GET'))
1802
+ return;
1803
+ try {
1804
+ writeJson(res, 200, await buildAutosyncStatus(syncDir));
1805
+ }
1806
+ catch (error) {
1807
+ writeSyncRouteError(res, error);
1808
+ }
1809
+ },
1810
+ },
1811
+ {
1812
+ kind: 'exact',
1813
+ path: API.syncAutosync,
1814
+ handler: async (req, res) => {
1815
+ if (!guard(req, res, 'POST'))
1816
+ return;
1817
+ const body = await readJsonBody(req);
1818
+ if (body === undefined) {
1819
+ writeJson(res, 400, { error: 'invalid JSON body' });
1820
+ return;
1821
+ }
1822
+ try {
1823
+ const cfg = await readAutosyncConfig(syncDir);
1824
+ if (typeof body['enabled'] === 'boolean')
1825
+ cfg.enabled = body['enabled'];
1826
+ if (typeof body['interval'] === 'string' && isAutosyncInterval(body['interval']))
1827
+ cfg.interval = body['interval'];
1828
+ if (typeof body['startupMinIntervalMs'] === 'number' && Number.isFinite(body['startupMinIntervalMs']) && body['startupMinIntervalMs'] > 0) {
1829
+ cfg.startupMinIntervalMs = body['startupMinIntervalMs'];
1830
+ }
1831
+ await writeAutosyncConfig(syncDir, cfg);
1832
+ if (scheduler)
1833
+ scheduler.reload().catch(() => { });
1834
+ writeJson(res, 200, await buildAutosyncStatus(syncDir));
1835
+ }
1836
+ catch (error) {
1837
+ writeSyncRouteError(res, error);
1838
+ }
1839
+ },
1840
+ },
1841
+ // ------------------------------------------------------ sync/rollback
1842
+ // P2:UI 一键回滚入口(按 apply 返回的 restoreId 调用 backup→rollback)。
1843
+ {
1844
+ kind: 'exact',
1845
+ path: API.syncRollback,
1846
+ handler: async (req, res) => {
1847
+ if (!guard(req, res, 'POST'))
1848
+ return;
1849
+ const body = await readJsonBody(req);
1850
+ if (body === undefined) {
1851
+ writeJson(res, 400, { error: 'invalid JSON body' });
1852
+ return;
1853
+ }
1854
+ try {
1855
+ const restoreId = typeof body['restoreId'] === 'string' ? body['restoreId'] : '';
1856
+ if (restoreId === '') {
1857
+ writeJson(res, 400, { error: 'restoreId required' });
1858
+ return;
1859
+ }
1860
+ const store = new FileSnapshotStore({ dir: join(syncDir, 'snapshots') });
1861
+ const snap = await store.load(restoreId);
1862
+ const report = await performRollback({ ctx: host, snapshot: snap, store, adapters });
1863
+ writeJson(res, 200, { ok: true, full: report.full });
1864
+ }
1865
+ catch (error) {
1866
+ writeJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
1867
+ }
1868
+ },
1869
+ },
1464
1870
  ];
1465
1871
  }
1466
1872
  /* ------------------------------------------------------------------ apply */