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.
Files changed (302) hide show
  1. package/lib/adapters/credentials.d.ts +6 -2
  2. package/lib/adapters/credentials.js +19 -3
  3. package/lib/adapters/file-collection.js +2 -1
  4. package/lib/adapters/mcp.d.ts +48 -2
  5. package/lib/adapters/mcp.js +265 -3
  6. package/lib/adapters/messages.d.ts +3 -0
  7. package/lib/adapters/messages.js +7 -0
  8. package/lib/adapters/plugin-files.js +2 -1
  9. package/lib/adapters/plugins.js +105 -20
  10. package/lib/adapters/pnpm-workspace.d.ts +32 -0
  11. package/lib/adapters/pnpm-workspace.js +113 -2
  12. package/lib/adapters/prompts.d.ts +6 -2
  13. package/lib/adapters/prompts.js +17 -2
  14. package/lib/adapters/providers.d.ts +19 -2
  15. package/lib/adapters/providers.js +59 -3
  16. package/lib/adapters/settings.d.ts +6 -2
  17. package/lib/adapters/settings.js +13 -2
  18. package/lib/adapters/skills.d.ts +25 -1
  19. package/lib/adapters/skills.js +60 -0
  20. package/lib/adapters/ui.d.ts +6 -2
  21. package/lib/adapters/ui.js +13 -2
  22. package/lib/adapters/units.d.ts +18 -0
  23. package/lib/adapters/units.js +39 -0
  24. package/lib/adapters/workspaces.js +2 -1
  25. package/lib/client.d.ts +401 -112
  26. package/lib/client.js +2899 -1525
  27. package/lib/core/backup.js +20 -0
  28. package/lib/core/mcp-connector-store.d.ts +159 -0
  29. package/lib/core/mcp-connector-store.js +445 -0
  30. package/lib/core/messages-core.d.ts +2 -0
  31. package/lib/core/messages-core.js +4 -0
  32. package/lib/core/messages.d.ts +20 -0
  33. package/lib/core/messages.js +44 -0
  34. package/lib/core/phase3-host.js +18 -1
  35. package/lib/core/plugin-cli.d.ts +22 -2
  36. package/lib/core/plugin-cli.js +22 -3
  37. package/lib/core/recovery-orchestrator.d.ts +3 -1
  38. package/lib/core/recovery-orchestrator.js +75 -16
  39. package/lib/core/rollback-activity.d.ts +89 -0
  40. package/lib/core/rollback-activity.js +350 -0
  41. package/lib/core/rollback-ledger.d.ts +0 -9
  42. package/lib/core/rollback-ledger.js +37 -0
  43. package/lib/core/rollback-merge.d.ts +22 -2
  44. package/lib/core/rollback-merge.js +63 -13
  45. package/lib/core/rollback-point.d.ts +93 -0
  46. package/lib/core/rollback-point.js +420 -101
  47. package/lib/core/rollback-undo-journal.d.ts +123 -0
  48. package/lib/core/rollback-undo-journal.js +517 -0
  49. package/lib/core/self-update.js +2 -4
  50. package/lib/core/text-fidelity.d.ts +89 -0
  51. package/lib/core/text-fidelity.js +176 -0
  52. package/lib/core/types.d.ts +12 -3
  53. package/lib/core/update-check.js +2 -4
  54. package/lib/core/validator.d.ts +9 -0
  55. package/lib/core/validator.js +12 -0
  56. package/lib/foreign/bundle.js +42 -3
  57. package/lib/foreign/claude-code.js +358 -26
  58. package/lib/foreign/claude-sessions.d.ts +51 -8
  59. package/lib/foreign/claude-sessions.js +111 -13
  60. package/lib/foreign/read-claude-code.d.ts +33 -4
  61. package/lib/foreign/read-claude-code.js +446 -65
  62. package/lib/foreign/read-claude-plugins.d.ts +36 -0
  63. package/lib/foreign/read-claude-plugins.js +403 -0
  64. package/lib/foreign/registry.js +25 -2
  65. package/lib/foreign/truth-table.js +6 -4
  66. package/lib/foreign/types.d.ts +170 -2
  67. package/lib/index.d.ts +14 -5
  68. package/lib/index.js +60 -16
  69. package/lib/market/git-file-writer.js +7 -1
  70. package/lib/profiles/dsh-profile-launcher.d.ts +20 -0
  71. package/lib/profiles/dsh-profile-launcher.js +58 -1
  72. package/lib/profiles/dsh-profile-shared.d.ts +8 -2
  73. package/lib/profiles/dsh-profile-shared.js +8 -2
  74. package/lib/routes/foreign.js +8 -1
  75. package/lib/routes/index.d.ts +1 -1
  76. package/lib/routes/kit.d.ts +2 -2
  77. package/lib/routes/sync.js +173 -9
  78. package/lib/schema/manifest.d.ts +6 -1
  79. package/lib/schema/manifest.js +55 -0
  80. package/lib/schema/types.d.ts +47 -2
  81. package/lib/sync/ancestor.d.ts +22 -0
  82. package/lib/sync/ancestor.js +66 -2
  83. package/lib/sync/autosync-config.d.ts +73 -0
  84. package/lib/sync/autosync-config.js +89 -0
  85. package/lib/sync/autosync-scheduler.d.ts +80 -6
  86. package/lib/sync/autosync-scheduler.js +263 -40
  87. package/lib/sync/gist/gist-transport.d.ts +18 -1
  88. package/lib/sync/gist/gist-transport.js +42 -3
  89. package/lib/sync/gist/messages.d.ts +2 -0
  90. package/lib/sync/gist/messages.js +5 -0
  91. package/lib/sync/git/git-transport.d.ts +42 -3
  92. package/lib/sync/git/git-transport.js +146 -3
  93. package/lib/sync/merge.d.ts +47 -14
  94. package/lib/sync/merge.js +242 -222
  95. package/lib/sync/messages.d.ts +8 -0
  96. package/lib/sync/messages.js +19 -0
  97. package/lib/sync/risk.d.ts +19 -2
  98. package/lib/sync/risk.js +10 -3
  99. package/lib/sync/s3/messages.d.ts +6 -0
  100. package/lib/sync/s3/messages.js +13 -0
  101. package/lib/sync/s3/s3-transport.d.ts +9 -1
  102. package/lib/sync/s3/s3-transport.js +78 -6
  103. package/lib/sync/s3/sigv4.js +11 -2
  104. package/lib/sync/sync-engine.d.ts +212 -8
  105. package/lib/sync/sync-engine.js +514 -58
  106. package/lib/sync/sync-history.d.ts +11 -0
  107. package/lib/sync/sync-history.js +20 -0
  108. package/lib/sync/sync-state.d.ts +43 -0
  109. package/lib/sync/sync-state.js +44 -0
  110. package/lib/sync/sync-watcher.d.ts +76 -0
  111. package/lib/sync/sync-watcher.js +202 -0
  112. package/lib/sync/text-merge.d.ts +85 -0
  113. package/lib/sync/text-merge.js +513 -0
  114. package/lib/sync/transport.d.ts +48 -0
  115. package/lib/sync/transport.js +12 -0
  116. package/lib/sync/watch-scope.d.ts +38 -0
  117. package/lib/sync/watch-scope.js +95 -0
  118. package/lib/sync/webdav/webdav-transport.d.ts +16 -2
  119. package/lib/sync/webdav/webdav-transport.js +128 -11
  120. package/lib/ui/foreign-view.js +10 -0
  121. package/lib/ui/i18n.d.ts +9 -0
  122. package/lib/ui/i18n.js +22 -0
  123. package/lib/ui/rollback-view.d.ts +12 -0
  124. package/lib/ui/rollback-view.js +2 -0
  125. package/lib/ui/sync-settings-view.d.ts +47 -0
  126. package/lib/ui/sync-settings-view.js +24 -0
  127. package/lib/ui/types.d.ts +21 -0
  128. package/lib/utils/env-lock.d.ts +1 -0
  129. package/lib/utils/env-lock.js +7 -4
  130. package/package.json +6 -6
  131. package/src/adapters/credentials.ts +22 -4
  132. package/src/adapters/file-collection.ts +2 -1
  133. package/src/adapters/mcp.test.ts +235 -1
  134. package/src/adapters/mcp.ts +276 -5
  135. package/src/adapters/messages.ts +7 -0
  136. package/src/adapters/plugin-files.ts +2 -1
  137. package/src/adapters/plugins.test.ts +176 -2
  138. package/src/adapters/plugins.ts +107 -21
  139. package/src/adapters/pnpm-workspace.test.ts +89 -1
  140. package/src/adapters/pnpm-workspace.ts +127 -2
  141. package/src/adapters/prompts.ts +20 -3
  142. package/src/adapters/providers.ts +62 -4
  143. package/src/adapters/settings.ts +16 -3
  144. package/src/adapters/skills.test.ts +141 -0
  145. package/src/adapters/skills.ts +58 -2
  146. package/src/adapters/ui.ts +16 -3
  147. package/src/adapters/units-entries.test.ts +190 -0
  148. package/src/adapters/units.ts +50 -0
  149. package/src/adapters/workspaces.ts +2 -1
  150. package/src/client/about/PluginUpdateDialog.tsx +4 -2
  151. package/src/client/about/ReleaseNotesDialog.tsx +4 -2
  152. package/src/client/chat/RollbackReviewDialog.tsx +3 -2
  153. package/src/client/common/Skeleton.tsx +32 -0
  154. package/src/client/common/api-base.test.ts +36 -0
  155. package/src/client/common/http.test.ts +12 -1
  156. package/src/client/common/http.ts +8 -2
  157. package/src/client/common/routes.ts +46 -3
  158. package/src/client/config-manager.module.css +321 -11
  159. package/src/client/home/HomePanel.tsx +14 -6
  160. package/src/client/import/ForeignImportView.tsx +3 -1
  161. package/src/client/import/import-wizard-steps.tsx +2 -1
  162. package/src/client/library/ArtifactRow.tsx +12 -2
  163. package/src/client/library/LibraryActions.tsx +6 -3
  164. package/src/client/library/LibraryPanel.tsx +21 -3
  165. package/src/client/library/RestorePlanView.tsx +4 -2
  166. package/src/client/locales.ts +3 -0
  167. package/src/client/market/market-api.test.ts +3 -2
  168. package/src/client/recovery/RecoveryPanel.tsx +54 -7
  169. package/src/client/recovery/RollbackCard.tsx +7 -2
  170. package/src/client/recovery/RollbackDiffPane.tsx +3 -2
  171. package/src/client/recovery/recovery-locales.ts +20 -2
  172. package/src/client/recovery/recovery-view.test.ts +39 -1
  173. package/src/client/recovery/recovery-view.ts +23 -1
  174. package/src/client/run-store.test.ts +1 -1
  175. package/src/client/run-store.ts +4 -0
  176. package/src/client/sync/AutosyncCard.tsx +49 -6
  177. package/src/client/sync/MergeModal.tsx +220 -0
  178. package/src/client/sync/MergePreviewView.tsx +306 -0
  179. package/src/client/sync/SyncActivityBar.tsx +31 -0
  180. package/src/client/sync/SyncConfirmView.tsx +2 -1
  181. package/src/client/sync/SyncPanel.tsx +130 -87
  182. package/src/client/sync/SyncProbeRow.tsx +162 -0
  183. package/src/client/sync/SyncSettingsView.test.ts +27 -2
  184. package/src/client/sync/WorkDirCard.tsx +44 -6
  185. package/src/client/sync/history-model.ts +4 -1
  186. package/src/client/sync/merge-view.test.ts +248 -0
  187. package/src/client/sync/merge-view.ts +398 -0
  188. package/src/client/sync/probe-view.test.ts +90 -0
  189. package/src/client/sync/probe-view.ts +63 -0
  190. package/src/client/sync/remote-device-view.test.ts +35 -0
  191. package/src/client/sync/remote-device-view.ts +25 -0
  192. package/src/client/sync/sync-api.test.ts +5 -3
  193. package/src/client/sync/sync-api.ts +212 -2
  194. package/src/client/sync/sync-entry.test.ts +78 -0
  195. package/src/client/sync/sync-entry.ts +53 -0
  196. package/src/client/sync/sync-locales.ts +234 -2
  197. package/src/client/sync/sync-view.test.ts +18 -1
  198. package/src/client/sync/sync-view.ts +20 -0
  199. package/src/core/backup.test.ts +41 -0
  200. package/src/core/backup.ts +20 -0
  201. package/src/core/mcp-connector-store.test.ts +265 -0
  202. package/src/core/mcp-connector-store.ts +516 -0
  203. package/src/core/messages-core.ts +4 -0
  204. package/src/core/messages.ts +46 -0
  205. package/src/core/phase3-host.ts +18 -1
  206. package/src/core/phase3-production-integration.test.ts +4 -0
  207. package/src/core/plugin-cli.test.ts +26 -0
  208. package/src/core/plugin-cli.ts +30 -4
  209. package/src/core/recovery-orchestrator.test.ts +104 -5
  210. package/src/core/recovery-orchestrator.ts +74 -17
  211. package/src/core/rollback-activity.test.ts +311 -0
  212. package/src/core/rollback-activity.ts +367 -0
  213. package/src/core/rollback-cross-session.test.ts +694 -0
  214. package/src/core/rollback-interrupted.test.ts +137 -0
  215. package/src/core/rollback-ledger.ts +32 -0
  216. package/src/core/rollback-merge.test.ts +130 -0
  217. package/src/core/rollback-merge.ts +78 -14
  218. package/src/core/rollback-point.ts +468 -91
  219. package/src/core/rollback-undo-journal.test.ts +508 -0
  220. package/src/core/rollback-undo-journal.ts +571 -0
  221. package/src/core/self-update.ts +2 -5
  222. package/src/core/text-fidelity.test.ts +189 -0
  223. package/src/core/text-fidelity.ts +197 -0
  224. package/src/core/types.ts +11 -3
  225. package/src/core/update-check.ts +2 -5
  226. package/src/core/validator.ts +14 -0
  227. package/src/foreign/bundle-origin.test.ts +265 -0
  228. package/src/foreign/bundle.ts +45 -5
  229. package/src/foreign/claude-code.test.ts +268 -0
  230. package/src/foreign/claude-code.ts +406 -25
  231. package/src/foreign/claude-plugins.test.ts +234 -0
  232. package/src/foreign/claude-sessions.test.ts +141 -0
  233. package/src/foreign/claude-sessions.ts +179 -23
  234. package/src/foreign/file-budget.test.ts +4 -1
  235. package/src/foreign/read-claude-code.test.ts +320 -0
  236. package/src/foreign/read-claude-code.ts +490 -71
  237. package/src/foreign/read-claude-plugins.ts +465 -0
  238. package/src/foreign/registry.ts +25 -2
  239. package/src/foreign/session-ir.test.ts +65 -0
  240. package/src/foreign/truth-table.test.ts +81 -0
  241. package/src/foreign/truth-table.ts +6 -3
  242. package/src/foreign/types.ts +162 -2
  243. package/src/index.facade.test.ts +26 -4
  244. package/src/index.ts +76 -19
  245. package/src/market/git-file-writer.test.ts +4 -0
  246. package/src/market/git-file-writer.ts +7 -1
  247. package/src/profiles/dsh-profile-launcher.test.ts +68 -3
  248. package/src/profiles/dsh-profile-launcher.ts +56 -1
  249. package/src/profiles/dsh-profile-shared.ts +8 -2
  250. package/src/routes/foreign.ts +8 -1
  251. package/src/routes/index.ts +1 -1
  252. package/src/routes/kit.ts +2 -2
  253. package/src/routes/sync.ts +176 -9
  254. package/src/schema/manifest-schema.test.ts +87 -2
  255. package/src/schema/manifest.ts +57 -1
  256. package/src/schema/types.ts +45 -2
  257. package/src/sync/ancestor.ts +76 -2
  258. package/src/sync/autosync-config.test.ts +73 -1
  259. package/src/sync/autosync-config.ts +124 -0
  260. package/src/sync/autosync-risk-tier.test.ts +38 -2
  261. package/src/sync/autosync-scheduler.test.ts +264 -12
  262. package/src/sync/autosync-scheduler.ts +296 -44
  263. package/src/sync/gist/gist-conditional-write.test.ts +203 -0
  264. package/src/sync/gist/gist-transport.ts +53 -5
  265. package/src/sync/gist/messages.ts +5 -0
  266. package/src/sync/git/git-probe.test.ts +80 -0
  267. package/src/sync/git/git-transport.test.ts +185 -0
  268. package/src/sync/git/git-transport.ts +178 -5
  269. package/src/sync/merge.test.ts +188 -55
  270. package/src/sync/merge.ts +282 -224
  271. package/src/sync/messages.ts +19 -0
  272. package/src/sync/risk.ts +29 -5
  273. package/src/sync/s3/messages.ts +13 -0
  274. package/src/sync/s3/s3-conditional-write.test.ts +330 -0
  275. package/src/sync/s3/s3-transport.test.ts +2 -1
  276. package/src/sync/s3/s3-transport.ts +87 -9
  277. package/src/sync/s3/sigv4.test.ts +4 -1
  278. package/src/sync/s3/sigv4.ts +11 -2
  279. package/src/sync/sync-device-identity.test.ts +267 -0
  280. package/src/sync/sync-engine.test.ts +410 -19
  281. package/src/sync/sync-engine.ts +602 -57
  282. package/src/sync/sync-history.test.ts +16 -1
  283. package/src/sync/sync-history.ts +26 -0
  284. package/src/sync/sync-state.test.ts +64 -0
  285. package/src/sync/sync-state.ts +79 -0
  286. package/src/sync/sync-watcher.test.ts +197 -0
  287. package/src/sync/sync-watcher.ts +246 -0
  288. package/src/sync/text-merge.test.ts +358 -0
  289. package/src/sync/text-merge.ts +554 -0
  290. package/src/sync/transport.ts +57 -0
  291. package/src/sync/watch-scope.test.ts +122 -0
  292. package/src/sync/watch-scope.ts +120 -0
  293. package/src/sync/webdav/webdav-conditional-write.test.ts +360 -0
  294. package/src/sync/webdav/webdav-transport.ts +137 -14
  295. package/src/ui/foreign-view.test.ts +34 -0
  296. package/src/ui/foreign-view.ts +10 -0
  297. package/src/ui/i18n.ts +22 -0
  298. package/src/ui/rollback-view.ts +14 -0
  299. package/src/ui/sync-settings-view.test.ts +28 -1
  300. package/src/ui/sync-settings-view.ts +43 -0
  301. package/src/ui/types.ts +22 -0
  302. package/src/utils/env-lock.ts +8 -5
@@ -40,3 +40,14 @@ export interface SyncHistoryFile {
40
40
  export declare function readSyncHistory(dir: string): Promise<SyncHistoryFile>;
41
41
  /** 追加一条自动同步执行记录(升序),裁剪到 AUTOSYNC_HISTORY_KEEP 条,原子写。 */
42
42
  export declare function appendAutosyncEntry(dir: string, entry: AutosyncHistoryEntry, now?: () => Date): Promise<void>;
43
+ /**
44
+ * 给指定 createdAt 的自动同步记录补 notifiedAt 并落盘(issue #105)。
45
+ *
46
+ * 为什么不再借道 autosync-config:此前「连续失败 ≥ 3」的通知文案被写进 lastRunMessage,
47
+ * 与「本轮错误原文」抢同一个字段 —— 界面据此渲染出与真实状态无关的通知文案。
48
+ * 通知的持久痕迹只留历史 entry 的 notifiedAt;未命中记录返回 false(不写文件)。
49
+ */
50
+ export declare function markAutosyncNotified(dir: string, createdAt: string, notifiedAt: string, opts?: {
51
+ now?: () => Date;
52
+ readHistory?: (dir: string) => Promise<SyncHistoryFile>;
53
+ }): Promise<boolean>;
@@ -75,4 +75,24 @@ export async function appendAutosyncEntry(dir, entry, now = () => new Date()) {
75
75
  const data = stringifyJsonSafe(current, { space: 2 });
76
76
  await atomicWriteFile(target, data, { mode: 0o600 });
77
77
  }
78
+ /**
79
+ * 给指定 createdAt 的自动同步记录补 notifiedAt 并落盘(issue #105)。
80
+ *
81
+ * 为什么不再借道 autosync-config:此前「连续失败 ≥ 3」的通知文案被写进 lastRunMessage,
82
+ * 与「本轮错误原文」抢同一个字段 —— 界面据此渲染出与真实状态无关的通知文案。
83
+ * 通知的持久痕迹只留历史 entry 的 notifiedAt;未命中记录返回 false(不写文件)。
84
+ */
85
+ export async function markAutosyncNotified(dir, createdAt, notifiedAt, opts = {}) {
86
+ const readHistory = opts.readHistory ?? readSyncHistory;
87
+ const current = await readHistory(dir);
88
+ const entry = current.autosyncEntries.find((e) => e.createdAt === createdAt);
89
+ if (entry === undefined)
90
+ return false;
91
+ entry.notifiedAt = notifiedAt;
92
+ current.updatedAt = (opts.now ?? (() => new Date()))().toISOString();
93
+ await fs.mkdir(dir, { recursive: true });
94
+ const target = path.join(dir, SYNC_HISTORY_FILE);
95
+ await atomicWriteFile(target, stringifyJsonSafe(current, { space: 2 }), { mode: 0o600 });
96
+ return true;
97
+ }
78
98
  //# sourceMappingURL=sync-history.js.map
@@ -1,6 +1,7 @@
1
1
  import type { MsgFunc } from '../core/messages.ts';
2
2
  import type { SectionData, SectionId } from '../schema/types.ts';
3
3
  import type { SnapshotFs } from './fs.ts';
4
+ import './messages.ts';
4
5
  export declare const SYNC_STATE_SCHEMA_VERSION = 3;
5
6
  /**
6
7
  * 历史可读取版本(均在内存中迁移到当前版本,不就地改写磁盘):
@@ -16,6 +17,24 @@ export interface SyncSectionState {
16
17
  hash: string;
17
18
  updatedAt: string;
18
19
  }
20
+ /**
21
+ * 待裁决的合并状态(基线纪律,2026-10)。
22
+ *
23
+ * 非空 = 上一次三方合并**仍有未裁决的冲突**,因此 lastSnapshotId 刻意**不推进**:
24
+ * 远端的最新快照下一轮仍会被当作「比基线新」而重新合并,冲突因此重新出现,直到用户裁决完。
25
+ * 若在冲突未裁决时就推进基线,冲突内容会在下一轮静默消失(远端那一版改动永远不再被讨论)。
26
+ *
27
+ * 纯可选字段:**不提升 SYNC_STATE_SCHEMA_VERSION**(旧文件缺该字段 → 读取形状逐键不变;
28
+ * 旧版本代码读到新文件也只是忽略它)。
29
+ */
30
+ export interface PendingMerge {
31
+ /** 冲突所针对的**远端**快照 id(基线指针此刻仍停在更早的那一个)。 */
32
+ remoteSnapshotId: string;
33
+ /** 仍未裁决的分区 id(非空;空 = 没有待裁决内容,调用方应清除该字段)。 */
34
+ sections: SectionId[];
35
+ /** 该待裁决状态**首次**出现的时刻(ISO-8601 UTC;跨轮保留首次时间,便于界面讲「挂了多久」)。 */
36
+ since: string;
37
+ }
19
38
  /** sync-state.json 结构 */
20
39
  export interface SyncState {
21
40
  schemaVersion: number;
@@ -48,6 +67,19 @@ export interface SyncState {
48
67
  * 本机若又出现该会话的实体,对应墓碑会被撤销(有实体 = 没删)。
49
68
  */
50
69
  deletedSessions?: string[];
70
+ /**
71
+ * cloud-sync 借鉴项 ⑥:本设备的**稳定**同步身份(首次推送时生成并持久化)。
72
+ *
73
+ * deviceId = 随机 UUID(换主机名/改机器名都不变,历史列表据此认出「同一台设备」);
74
+ * deviceName = 人类可读名(缺省取主机名)。缺省 = 旧文件 / 从未推送(不猜)。
75
+ */
76
+ deviceId?: string;
77
+ deviceName?: string;
78
+ /**
79
+ * 基线纪律(2026-10):未裁决冲突记录。非空 = 基线指针**刻意停在旧值**,
80
+ * 让冲突在下一轮重新出现(详见 PendingMerge)。
81
+ */
82
+ pendingMerge?: PendingMerge;
51
83
  }
52
84
  /**
53
85
  * 分区内容 hash:
@@ -60,5 +92,16 @@ export declare function hashSection(data: SectionData): string;
60
92
  * 旧版本文件(v1/v2)→ 在内存中迁移到当前版本(见 SYNC_STATE_SUPPORTED_VERSIONS),
61
93
  * 不在磁盘上就地升级(首次 saveSyncState 时才写回新版本)。 */
62
94
  export declare function loadSyncState(dir: string, fsx?: SnapshotFs, msg?: MsgFunc): Promise<SyncState>;
95
+ /**
96
+ * 解析(必要时生成并持久化)本设备的同步身份(cloud-sync 借鉴项 ⑥)。
97
+ *
98
+ * - deviceId 一旦生成就持久化在 sync-state.json → 历史列表能认出「同一台设备」;
99
+ * - deviceName 缺省取主机名,取不到(容器/无主机名)时用字典里的兜底文案,绝不写空串;
100
+ * - **只在首次生成时写盘**(已有 id → 纯读,不产生额外写)。
101
+ */
102
+ export declare function resolveDeviceIdentity(dir: string, hostName: string, fsx?: SnapshotFs, msg?: MsgFunc): Promise<{
103
+ id: string;
104
+ name: string;
105
+ }>;
63
106
  /** 保存同步状态(自动创建目录) */
64
107
  export declare function saveSyncState(dir: string, state: SyncState, fsx?: SnapshotFs): Promise<void>;
@@ -3,10 +3,13 @@
3
3
  * 记录上次同步时间与各分区内容 hash,供变更检测(与远端快照对比决定是否重传)。
4
4
  * 纯逻辑 + 可注入 fs;不触碰真实 ~/.dsh。
5
5
  */
6
+ import { randomUUID } from 'node:crypto';
6
7
  import { sha256Hex } from '../utils/hashing.js';
7
8
  import { parseJsonSafe, stringifyJsonSafe } from '../utils/json.js';
8
9
  import { zhMsg } from '../core/messages.js';
9
10
  import { createSnapshotFs, joinFs } from './fs.js';
11
+ // 侧效应导入:注册同步域追加消息目录(sync.deviceNameFallback 等 ⑥ 新键必须能解析,绝不露裸键名)
12
+ import './messages.js';
10
13
  export const SYNC_STATE_SCHEMA_VERSION = 3;
11
14
  /**
12
15
  * 历史可读取版本(均在内存中迁移到当前版本,不就地改写磁盘):
@@ -72,6 +75,24 @@ function readStringArray(raw) {
72
75
  }
73
76
  return out;
74
77
  }
78
+ /**
79
+ * 读取可选 pendingMerge(基线纪律)。
80
+ *
81
+ * 形状不符(损坏/被篡改/分区清单为空)→ undefined = 按「无待裁决冲突」处理:
82
+ * 这里**绝不抛错**(一个诊断字段不该让 loadSyncState 整体失败),也绝不把空清单当成
83
+ * 「所有冲突都解决了」—— 空清单只是没有内容可记录。
84
+ */
85
+ function readPendingMerge(raw) {
86
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
87
+ return undefined;
88
+ const o = raw;
89
+ if (typeof o['remoteSnapshotId'] !== 'string' || typeof o['since'] !== 'string')
90
+ return undefined;
91
+ const sections = readStringArray(o['sections']);
92
+ if (sections.length === 0)
93
+ return undefined;
94
+ return { remoteSnapshotId: o['remoteSnapshotId'], sections: sections, since: o['since'] };
95
+ }
75
96
  /** 读取同步状态;文件不存在 → 返回缺省空状态(从未同步)。
76
97
  * 旧版本文件(v1/v2)→ 在内存中迁移到当前版本(见 SYNC_STATE_SUPPORTED_VERSIONS),
77
98
  * 不在磁盘上就地升级(首次 saveSyncState 时才写回新版本)。 */
@@ -122,6 +143,11 @@ export async function loadSyncState(dir, fsx = createSnapshotFs(), msg = zhMsg)
122
143
  // 绝不采用一个来路不明的目录名。
123
144
  ancestorId: rawAncestorId ?? (fileVersion < SYNC_STATE_SCHEMA_VERSION ? rawLastSnapshotId : ''),
124
145
  lastSnapshotId: rawLastSnapshotId,
146
+ // ⑥:只有文件里确实写过才回填(缺席 = 从未记录 → 下次推送才生成,绝不在这里凭空造一个 id)
147
+ ...(typeof obj['deviceId'] === 'string' && obj['deviceId'] !== '' ? { deviceId: obj['deviceId'] } : {}),
148
+ ...(typeof obj['deviceName'] === 'string' && obj['deviceName'] !== '' ? { deviceName: obj['deviceName'] } : {}),
149
+ // 基线纪律:同样「写过才回填」—— 缺席 = 无待裁决冲突(旧文件读回形状逐键不变)
150
+ ...(readPendingMerge(obj['pendingMerge']) !== undefined ? { pendingMerge: readPendingMerge(obj['pendingMerge']) } : {}),
125
151
  };
126
152
  const t = obj['transport'];
127
153
  if (t !== undefined) {
@@ -132,6 +158,24 @@ export async function loadSyncState(dir, fsx = createSnapshotFs(), msg = zhMsg)
132
158
  }
133
159
  return state;
134
160
  }
161
+ /**
162
+ * 解析(必要时生成并持久化)本设备的同步身份(cloud-sync 借鉴项 ⑥)。
163
+ *
164
+ * - deviceId 一旦生成就持久化在 sync-state.json → 历史列表能认出「同一台设备」;
165
+ * - deviceName 缺省取主机名,取不到(容器/无主机名)时用字典里的兜底文案,绝不写空串;
166
+ * - **只在首次生成时写盘**(已有 id → 纯读,不产生额外写)。
167
+ */
168
+ export async function resolveDeviceIdentity(dir, hostName, fsx = createSnapshotFs(), msg = zhMsg) {
169
+ const state = await loadSyncState(dir, fsx, msg);
170
+ const trimmedHost = typeof hostName === 'string' ? hostName.trim() : '';
171
+ const name = (typeof state.deviceName === 'string' && state.deviceName !== '' ? state.deviceName : '')
172
+ || (trimmedHost !== '' ? trimmedHost : msg('sync.deviceNameFallback'));
173
+ if (typeof state.deviceId === 'string' && state.deviceId !== '')
174
+ return { id: state.deviceId, name };
175
+ const id = randomUUID();
176
+ await saveSyncState(dir, { ...state, deviceId: id, deviceName: name }, fsx);
177
+ return { id, name };
178
+ }
135
179
  /** 保存同步状态(自动创建目录) */
136
180
  export async function saveSyncState(dir, state, fsx = createSnapshotFs()) {
137
181
  await fsx.mkdir(dir);
@@ -0,0 +1,76 @@
1
+ import type { Logger } from '../utils/logger.ts';
2
+ import type { WatchTarget } from './watch-scope.ts';
3
+ /** fs.watch 返回的句柄(只用到 close)。 */
4
+ export interface WatchHandle {
5
+ close(): void;
6
+ }
7
+ /** fs.watch 的最小签名(测试注入假实现)。 */
8
+ export type WatchFactory = (absPath: string, opts: {
9
+ recursive: boolean;
10
+ }, listener: (event: string, filename: string | null) => void) => WatchHandle;
11
+ /** 缺省去抖窗口:一次「保存配置」产生的连续事件合并为一次触发。 */
12
+ export declare const DEFAULT_WATCH_DEBOUNCE_MS = 1500;
13
+ /** 缺省自写回声窗口:resume 后这段时间内的事件一律忽略。 */
14
+ export declare const DEFAULT_WATCH_QUIET_MS = 2000;
15
+ export interface SyncWatcherOptions {
16
+ /** `$DSH_HOME` 绝对路径 */
17
+ homeDir: string;
18
+ /** 监听目标(`watch-scope.buildWatchTargets` 的产物) */
19
+ targets: readonly WatchTarget[];
20
+ log: Logger;
21
+ /** 去抖窗口到期:把这一批变更的 home 相对路径交给上层(已过滤噪声)。 */
22
+ onFlush: (changed: readonly string[]) => void;
23
+ debounceMs?: number;
24
+ quietAfterSuspendMs?: number;
25
+ watchFactory?: WatchFactory;
26
+ /** 时间源 / 计时器(测试注入) */
27
+ now?: () => number;
28
+ setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;
29
+ clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
30
+ }
31
+ export declare class SyncWatcher {
32
+ private readonly homeDir;
33
+ private readonly log;
34
+ private readonly onFlush;
35
+ private readonly debounceMs;
36
+ private readonly quietAfterSuspendMs;
37
+ private readonly watchFactory;
38
+ private readonly now;
39
+ private readonly setTimer;
40
+ private readonly clearTimer;
41
+ private targets;
42
+ private attachments;
43
+ private pending;
44
+ private timer;
45
+ private started;
46
+ private suspended;
47
+ private quietUntil;
48
+ constructor(opts: SyncWatcherOptions);
49
+ /** 是否已启动。 */
50
+ isStarted(): boolean;
51
+ /** 当前实际挂上的监听数(诊断/测试用)。 */
52
+ attachmentCount(): number;
53
+ /** 启动监听(重复调用是幂等的)。 */
54
+ start(): void;
55
+ /** 停止监听并丢弃未触发的变更(插件卸载 / 自动同步关闭时必须调)。 */
56
+ stop(): void;
57
+ /** 换监听目标(分区选择/配置变化);已启动则原子重启。 */
58
+ setTargets(targets: readonly WatchTarget[]): void;
59
+ /**
60
+ * 进入「自写中」:屏蔽事件并丢弃已积累的变更。
61
+ *
62
+ * 在 runOnce 开始时调用 —— 同步自家的 apply/push 会写 settings.yaml / patch / skills,
63
+ * 那些写入不是用户改动。
64
+ */
65
+ suspend(): void;
66
+ /** 退出「自写中」:随后进入静默窗口,避免收尾写入的回声立刻触发下一轮。 */
67
+ resume(): void;
68
+ /** 立即处理当前积累的变更(测试用;正常路径靠定时器)。 */
69
+ flushNow(): void;
70
+ private attachAll;
71
+ private attach;
72
+ private onRawEvent;
73
+ private scheduleFlush;
74
+ private flush;
75
+ private clearPendingTimer;
76
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * m-autosync-realtime:本地文件监听器(fs.watch + 去抖 + 自写回声屏蔽)。
3
+ *
4
+ * 存在的理由:改造前的自动同步**只靠定时器**(最短 5 分钟),本地改完配置得等下一轮才上行,
5
+ * 与「像 VS Code 那样改完就同步」的预期不符(issue: 自动同步不自动)。
6
+ *
7
+ * 设计要点:
8
+ * - **文件目标监听父目录**(非递归)+ 按 basename 过滤:原子写是 temp + rename,
9
+ * 直接监听文件本身会在 rename 后失去句柄(Linux inode 变化),监听父目录才稳;
10
+ * - **去抖**(缺省 1500ms):一次保存往往连写多个文件(settings.yaml + patch),
11
+ * 合并成一次触发,绝不为每个字节排一轮同步;
12
+ * - **自写回声屏蔽**:调度器在 runOnce 前后 suspend()/resume(),resume 后再静默
13
+ * `quietAfterSuspendMs`(缺省 2000ms)——否则「同步把远端内容写回本地」自身就会触发下一轮,
14
+ * 形成 push→apply→push 的死循环;
15
+ * - **静默窗口只「推迟」不「丢弃」**:窗口内到达的事件记进 pending 并排一枚窗口结束后的补跑定时器
16
+ * (真机实测:窗口内保存若直接丢弃,用户改完要等一整轮兜底间隔才上行 —— 与「改完就同步」不符)。
17
+ * 自家回声由引擎的分区 hash 比对判成「无变化」,多跑一轮不产生任何传输;
18
+ * - **纯监听、不做判定**:这里只说「有东西变了」(附带 home 相对路径),究竟哪些分区真的变了
19
+ * 由引擎的 `hasLocalChanges()`(分区 hash 比基线)回答 —— 监听器绝不猜。
20
+ *
21
+ * 可靠性取舍:监听失败(ENOENT / 平台不支持 recursive)只 `log.debug/warn`,绝不抛错 ——
22
+ * 自动同步的兜底始终是定时器,监听掉线只是退回「等下一轮」,不能让宿主启动失败。
23
+ */
24
+ import fs from 'node:fs';
25
+ import path from 'node:path';
26
+ import { isNoiseRelPath, targetAbsPath, toHomeRelPath } from './watch-scope.js';
27
+ /** 缺省去抖窗口:一次「保存配置」产生的连续事件合并为一次触发。 */
28
+ export const DEFAULT_WATCH_DEBOUNCE_MS = 1500;
29
+ /** 缺省自写回声窗口:resume 后这段时间内的事件一律忽略。 */
30
+ export const DEFAULT_WATCH_QUIET_MS = 2000;
31
+ export class SyncWatcher {
32
+ homeDir;
33
+ log;
34
+ onFlush;
35
+ debounceMs;
36
+ quietAfterSuspendMs;
37
+ watchFactory;
38
+ now;
39
+ setTimer;
40
+ clearTimer;
41
+ targets;
42
+ attachments = [];
43
+ pending = new Set();
44
+ timer = null;
45
+ started = false;
46
+ suspended = false;
47
+ quietUntil = 0;
48
+ constructor(opts) {
49
+ this.homeDir = opts.homeDir;
50
+ this.targets = opts.targets;
51
+ this.log = opts.log;
52
+ this.onFlush = opts.onFlush;
53
+ this.debounceMs = opts.debounceMs ?? DEFAULT_WATCH_DEBOUNCE_MS;
54
+ this.quietAfterSuspendMs = opts.quietAfterSuspendMs ?? DEFAULT_WATCH_QUIET_MS;
55
+ this.watchFactory = opts.watchFactory ?? defaultWatchFactory;
56
+ this.now = opts.now ?? (() => Date.now());
57
+ this.setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
58
+ this.clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
59
+ }
60
+ /** 是否已启动。 */
61
+ isStarted() {
62
+ return this.started;
63
+ }
64
+ /** 当前实际挂上的监听数(诊断/测试用)。 */
65
+ attachmentCount() {
66
+ return this.attachments.length;
67
+ }
68
+ /** 启动监听(重复调用是幂等的)。 */
69
+ start() {
70
+ if (this.started)
71
+ return;
72
+ this.started = true;
73
+ this.attachAll();
74
+ }
75
+ /** 停止监听并丢弃未触发的变更(插件卸载 / 自动同步关闭时必须调)。 */
76
+ stop() {
77
+ this.started = false;
78
+ this.suspended = false;
79
+ this.quietUntil = 0;
80
+ this.clearPendingTimer();
81
+ this.pending.clear();
82
+ for (const a of this.attachments) {
83
+ try {
84
+ a.handle.close();
85
+ }
86
+ catch {
87
+ /* 尽力而为:句柄可能已被平台回收 */
88
+ }
89
+ }
90
+ this.attachments = [];
91
+ }
92
+ /** 换监听目标(分区选择/配置变化);已启动则原子重启。 */
93
+ setTargets(targets) {
94
+ this.targets = targets;
95
+ if (!this.started)
96
+ return;
97
+ this.stop();
98
+ this.started = true;
99
+ this.attachAll();
100
+ }
101
+ /**
102
+ * 进入「自写中」:屏蔽事件并丢弃已积累的变更。
103
+ *
104
+ * 在 runOnce 开始时调用 —— 同步自家的 apply/push 会写 settings.yaml / patch / skills,
105
+ * 那些写入不是用户改动。
106
+ */
107
+ suspend() {
108
+ this.suspended = true;
109
+ this.clearPendingTimer();
110
+ this.pending.clear();
111
+ }
112
+ /** 退出「自写中」:随后进入静默窗口,避免收尾写入的回声立刻触发下一轮。 */
113
+ resume() {
114
+ if (!this.suspended)
115
+ return;
116
+ this.suspended = false;
117
+ this.quietUntil = this.now() + this.quietAfterSuspendMs;
118
+ }
119
+ /** 立即处理当前积累的变更(测试用;正常路径靠定时器)。 */
120
+ flushNow() {
121
+ this.flush();
122
+ }
123
+ attachAll() {
124
+ for (const target of this.targets)
125
+ this.attach(target);
126
+ }
127
+ attach(target) {
128
+ const abs = targetAbsPath(this.homeDir, target);
129
+ const isDir = target.kind === 'dir';
130
+ const watchedDir = isDir ? abs : path.dirname(abs);
131
+ const fileBase = isDir ? null : path.basename(abs);
132
+ try {
133
+ const handle = this.watchFactory(watchedDir, { recursive: isDir ? target.recursive : false }, (event, filename) => {
134
+ this.onRawEvent(target, watchedDir, fileBase, filename);
135
+ });
136
+ this.attachments.push({ target, watchedDir, fileBase, handle });
137
+ this.log.debug(`自动同步监听已挂载:${target.relPath}`, { watchedDir });
138
+ }
139
+ catch (err) {
140
+ // 路径不存在 / 平台不支持:不阻断启动,兜底是定时器
141
+ this.log.debug(`自动同步监听未挂载:${target.relPath}`, { error: err instanceof Error ? err.message : String(err) });
142
+ }
143
+ }
144
+ onRawEvent(target, watchedDir, fileBase, filename) {
145
+ if (!this.started || this.suspended)
146
+ return;
147
+ // 静默窗口内到达的事件:先记账,窗口一结束就补跑一次判定(丢弃会让用户改动漏掉一整轮兜底间隔)
148
+ const quietRemainingMs = this.quietUntil - this.now();
149
+ let abs;
150
+ if (fileBase !== null) {
151
+ // 文件目标:只关心父目录里同名文件的事件(原子写的 temp 名字被噪声规则挡掉)
152
+ if (filename !== null && path.basename(filename) !== fileBase)
153
+ return;
154
+ abs = path.join(watchedDir, fileBase);
155
+ }
156
+ else {
157
+ if (filename === null)
158
+ return;
159
+ abs = path.join(watchedDir, filename);
160
+ }
161
+ const rel = toHomeRelPath(this.homeDir, abs);
162
+ if (rel === null || isNoiseRelPath(rel))
163
+ return;
164
+ void target; // sections 仅用于诊断;判定交给引擎的分区 hash
165
+ this.pending.add(rel);
166
+ this.scheduleFlush(quietRemainingMs > 0 ? quietRemainingMs + this.debounceMs : this.debounceMs);
167
+ }
168
+ scheduleFlush(delayMs = this.debounceMs) {
169
+ if (this.timer !== null)
170
+ return;
171
+ this.timer = this.setTimer(() => {
172
+ this.timer = null;
173
+ this.flush();
174
+ }, delayMs);
175
+ }
176
+ flush() {
177
+ if (this.pending.size === 0)
178
+ return;
179
+ const quietRemainingMs = this.quietUntil - this.now();
180
+ if (quietRemainingMs > 0) {
181
+ // 去抖定时器提前到期:推迟到窗口结束,既不丢用户改动、也不把自家收尾写入当触发
182
+ this.scheduleFlush(quietRemainingMs + this.debounceMs);
183
+ return;
184
+ }
185
+ const changed = [...this.pending];
186
+ this.pending.clear();
187
+ try {
188
+ this.onFlush(changed);
189
+ }
190
+ catch (err) {
191
+ this.log.warn('自动同步监听回调失败', { error: err instanceof Error ? err.message : String(err) });
192
+ }
193
+ }
194
+ clearPendingTimer() {
195
+ if (this.timer === null)
196
+ return;
197
+ this.clearTimer(this.timer);
198
+ this.timer = null;
199
+ }
200
+ }
201
+ const defaultWatchFactory = (absPath, opts, listener) => fs.watch(absPath, { recursive: opts.recursive }, listener);
202
+ //# sourceMappingURL=sync-watcher.js.map
@@ -0,0 +1,85 @@
1
+ /**
2
+ * m-sync-flow:行级文本三方合并 + 递归 JSON 结构化合并(零依赖纯函数)。
3
+ *
4
+ * 职责边界(本文件只做纯计算:不读配置、不落盘、不打日志):
5
+ * - looksLikeText / tryParseJson:内容形态判定(文本 / JSON);
6
+ * - mergeJsonValues:JSON 值级递归三方合并(对象按 key 递归;数组绝不按索引合并);
7
+ * - mergeTextThreeWay:文本级三方合并(三段都是合法 JSON 时优先走结构化合并);
8
+ * - mergeBytesThreeWay:字节级入口,非文本 / 超限 / 缺祖先一律如实拒绝(绝不猜)。
9
+ *
10
+ * 安全口径(改这里前先读):
11
+ * - 冲突优先于猜测:语义不明的合并(数组双侧改动、同一区域双侧改动、类型互换)一律报冲突,
12
+ * 绝不静默丢弃任一侧的键 / 插件配置 / 用户数据。
13
+ * - 行对齐只走 LCS(base→local、base→remote),同一行内容重复时也不会错位;单侧变更区间
14
+ * 超过 DP 上限时退化为「整段替换」——宁可粗粒度报冲突,也不做定位猜测。
15
+ * - 文本比较按「去行尾」后的内容进行:CRLF/LF 混用不得误报冲突;输出保留输入的行尾风格。
16
+ * - 缺共同祖先(ancestor === undefined)→ ok:false:由上层按整文件冲突处理,本模块不猜。
17
+ */
18
+ /** 文本判定与文本合并的缺省字节上限:1 MiB。超限一律按「太大不合并」处理。 */
19
+ export declare const TEXT_MERGE_MAX_BYTES: number;
20
+ /** 单条冲突。local/remote/ancestor 是原始值:JSON 合并给原始 JSON 值,文本合并给字符串片段。 */
21
+ export interface TextMergeConflict {
22
+ /** 冲突位置:JSON 合并是点号路径('a.b.c',根为 '');文本合并是 'line 12-14'(或单点 'line 7')。 */
23
+ path: string;
24
+ reason: 'overlap' | 'array' | 'format' | 'binary' | 'too-large';
25
+ local?: unknown;
26
+ remote?: unknown;
27
+ ancestor?: unknown;
28
+ }
29
+ /** JSON 值级合并结果;ok 为 false 时 merged 缺省(绝不把半成品当结果)。 */
30
+ export interface JsonMergeResult {
31
+ ok: boolean;
32
+ merged?: unknown;
33
+ conflicts: TextMergeConflict[];
34
+ }
35
+ /** 合并内容形态:合法 JSON 文本 / 行级文本 / 二进制 / 超限。 */
36
+ export type TextMergeKind = 'text' | 'json' | 'binary' | 'too-large';
37
+ /** 文本(或字节)三方合并结果。 */
38
+ export interface TextMergeResult {
39
+ ok: boolean;
40
+ merged?: string;
41
+ conflicts: TextMergeConflict[];
42
+ kind: TextMergeKind;
43
+ }
44
+ /** 字节三方合并结果。 */
45
+ export interface TextBytesMergeResult {
46
+ ok: boolean;
47
+ merged?: Uint8Array;
48
+ conflicts: TextMergeConflict[];
49
+ kind: TextMergeKind;
50
+ }
51
+ /**
52
+ * 是否像文本:含 NUL 字节 / 非法 UTF-8 / 超过 maxBytes(缺省 1 MiB)→ false。
53
+ * 只做形态判定,不做解码;调用方拿到 true 后可用 UTF-8 解码。
54
+ */
55
+ export declare function looksLikeText(bytes: Uint8Array, maxBytes?: number): boolean;
56
+ /** 严格 JSON 解析(不做 JSON5 / 注释 / 尾逗号宽容):解析失败返回 ok:false。 */
57
+ export declare function tryParseJson(text: string): {
58
+ ok: true;
59
+ value: unknown;
60
+ } | {
61
+ ok: false;
62
+ };
63
+ /**
64
+ * 三方合并 JSON 值(递归,任意深度)。
65
+ * - 双侧相同 → 该值(收敛);
66
+ * - 仅一侧相对祖先改动 → 采纳该侧;
67
+ * - 对象(祖先缺省或同为对象)→ 按键递归,**缺失键 = 删除语义**:
68
+ * 一侧删除 + 另一侧未改 → 删除胜出;一侧删除 + 另一侧改了 → 冲突;
69
+ * 单侧新增(祖先没有)→ 采纳;双侧都新增且不同 → 冲突;
70
+ * - 数组绝不按索引合并:双侧都改且不 deepEqual → 冲突(reason='array');
71
+ * - 其余(标量双向改动、类型互换)→ 冲突(reason='overlap')。
72
+ */
73
+ export declare function mergeJsonValues(ancestor: unknown, local: unknown, remote: unknown, path?: string): JsonMergeResult;
74
+ /**
75
+ * 行级三方合并:base→local、base→remote 各自算变更区间,不重叠的全部应用;
76
+ * 重叠区间两侧结果逐字相同 → 收敛为一份,否则冲突(path='line X-Y')。
77
+ */
78
+ export declare function mergeTextThreeWay(ancestor: string, local: string, remote: string): TextMergeResult;
79
+ /**
80
+ * 字节三方合并:
81
+ * - ancestor === undefined → ok:false(无共同祖先,由上层按整文件冲突处理,绝不猜);
82
+ * - 任一侧二进制 / 超限(含 ancestor)→ ok:false,kind 如实('binary' / 'too-large');
83
+ * - 否则解码为文本走 mergeTextThreeWay,成功后按 UTF-8 编码回字节。
84
+ */
85
+ export declare function mergeBytesThreeWay(ancestor: Uint8Array | undefined, local: Uint8Array, remote: Uint8Array): TextBytesMergeResult;