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
@@ -7,6 +7,30 @@ export type AutosyncInterval = '5m' | '15m' | '30m' | '60m' | '6h' | '12h' | '24
7
7
  export declare const DEFAULT_AUTOSYNC_INTERVAL: AutosyncInterval;
8
8
  /** 缺省启动触发最小间隔阈值(5 分钟,防频繁重启反复同步) */
9
9
  export declare const DEFAULT_STARTUP_MIN_INTERVAL_MS: number;
10
+ /** 缺省开启「本地改动即时触发」(真·自动同步;关闭后退回纯定时器) */
11
+ export declare const DEFAULT_AUTOSYNC_REALTIME = true;
12
+ /** 缺省远端轮询间隔(realtime 开启时与本地监听并行:本地改动即时上行,远端改动最迟 1 分钟内下行) */
13
+ export declare const DEFAULT_AUTOSYNC_REMOTE_CHECK_MS: number;
14
+ /** 远端轮询间隔下限(15 s):再短只是空转 fetch,收益为零 */
15
+ export declare const MIN_AUTOSYNC_REMOTE_CHECK_MS: number;
16
+ /** 远端轮询间隔上限(60 min):比这更慢就不该叫「实时」了 */
17
+ export declare const MAX_AUTOSYNC_REMOTE_CHECK_MS: number;
18
+ /** 自动应用的最高风险等级(low = 只有低风险分区自动落盘;medium = 中风险 plugins/mcp 也自动落盘) */
19
+ export type AutosyncAutoApplyTier = 'low' | 'medium';
20
+ /** 缺省自动应用等级:medium(用户拍板:plugins/mcp 的中风险改动也要真自动,否则「同步所有改动」是假的) */
21
+ export declare const DEFAULT_AUTOSYNC_AUTO_APPLY_TIER: AutosyncAutoApplyTier;
22
+ /**
23
+ * 双侧都改(真冲突)时的自动裁决策略:
24
+ * - 'ask' :保留冲突待人工裁决(改造前的唯一行为);
25
+ * - 'local' :缺省 —— **保留本机并推上去**(VS Code 风格):本机版本成为新的远端最新,
26
+ * 被覆盖的远端版本仍留在远端历史快照里,随时可回滚。
27
+ *
28
+ * 刻意**不提供** 'remote':采用远端需要走 applyMergeResolutions,那条路径会整体应用所有
29
+ * 可决断分区、绕过 risk.ts 的风险分级(audit-sync sync-F5),与「分区逐档放行」冲突。
30
+ */
31
+ export type AutosyncConflictPolicy = 'ask' | 'local';
32
+ /** 缺省冲突策略:local(保留本机并推上去) */
33
+ export declare const DEFAULT_AUTOSYNC_CONFLICT_POLICY: AutosyncConflictPolicy;
10
34
  /** 最近一次自动同步执行状态 */
11
35
  export type AutosyncRunStatus = 'success' | 'skipped' | 'failed' | 'partial';
12
36
  /** 自动同步配置(持久化面;单通道) */
@@ -17,6 +41,21 @@ export interface AutosyncConfig {
17
41
  interval: AutosyncInterval;
18
42
  /** 重启触发的「自动下载合并」最小间隔阈值(ms) */
19
43
  startupMinIntervalMs: number;
44
+ /**
45
+ * 真·自动同步:本地文件改动后即时触发一次(缺省 true)。
46
+ *
47
+ * false = 退回改造前的纯定时器语义(等 interval 到点);保留这个开关是因为
48
+ * 「监听整棵配置目录」在某些环境(网络盘、超大 skills 树)可能带来额外 I/O。
49
+ *
50
+ * 可选:**读取层(parseV1Channel)恒补缺省值**,旧文件/旧夹具不需要带这个字段。
51
+ */
52
+ realtime?: boolean;
53
+ /** realtime 开启时的远端轮询间隔(ms;下限 15 s、上限 60 min,缺省 60 s) */
54
+ remoteCheckMs?: number;
55
+ /** 自动应用的最高风险等级(缺省 medium) */
56
+ autoApplyTier?: AutosyncAutoApplyTier;
57
+ /** 双侧都改时的自动裁决策略(缺省 local:保留本机并推上去) */
58
+ conflictPolicy?: AutosyncConflictPolicy;
20
59
  /** 连续失败计数(用于通知判定) */
21
60
  consecutiveFailures: number;
22
61
  /** 最近一次自动同步执行时间(ISO-8601 UTC);''/undefined = 从未执行 */
@@ -42,9 +81,43 @@ export interface AutosyncConfig {
42
81
  export type AutosyncConfigByChannel = Record<SyncTransportType, AutosyncConfig>;
43
82
  /** 缺省配置(首次无文件 / 损坏 / 不支持 schema 时回退) */
44
83
  export declare function defaultAutosyncConfig(): AutosyncConfig;
84
+ /** 远端轮询间隔夹取(非数字 / 越界一律回落到边界值,绝不写入 NaN)。 */
85
+ export declare function clampRemoteCheckMs(value: number): number;
45
86
  /** 读取全部通道的自动同步配置(v2 文件;v1 迁移为 git;webdav 缺省)。 */
46
87
  export declare function readAllAutosyncConfigs(dir: string): Promise<AutosyncConfigByChannel>;
47
88
  /** 读取指定通道的自动同步配置;文件不存在 / 损坏 / 不支持 schema → 缺省值(不抛错)。 */
48
89
  export declare function readAutosyncConfig(dir: string, channel: SyncTransportType): Promise<AutosyncConfig>;
49
90
  /** 写入指定通道的自动同步配置(原子写:临时文件 + rename;保留另一通道;自动创建目录)。 */
50
91
  export declare function writeAutosyncConfig(dir: string, channel: SyncTransportType, cfg: AutosyncConfig): Promise<void>;
92
+ /**
93
+ * 可展示的「本轮失败原因」(issue #105)。
94
+ *
95
+ * 判据只有一条:**本轮状态真的是 failed**,才允许把 lastRunMessage 当成错误文案回传。
96
+ * 此前状态路由无条件回传 lastRunMessage,而 POST /sync/autosync 又会把读到的旧配置原样
97
+ * 写回 —— 于是「十天前那次失败的错误原文」会被一次纯粹改间隔的保存坐实,卡片上继续挂着
98
+ * 早已不成立的错误,用户据此去查一个不存在的故障(真机现场:认证已修好,界面仍报认证失败)。
99
+ *
100
+ * 返回 undefined = 界面不得渲染任何 lastRunMessage 单元格。
101
+ */
102
+ export declare function lastRunFailureMessage(cfg: Pick<AutosyncConfig, 'lastRunStatus' | 'lastRunMessage'>): string | undefined;
103
+ /**
104
+ * 清空「上一轮运行结果」三字段(issue #105:配置变更 ≠ 一轮运行结果)。
105
+ *
106
+ * POST /sync/autosync 是**配置**写入而不是运行,写完必须把上一轮的结论清掉,否则旧结论
107
+ * 会被当成当前结论继续展示。保留 lastRunAt/lastRunStatus 与连续失败计数:它们描述的是
108
+ * 「上一次真的跑过什么」,不会因为改了间隔就变成假话。
109
+ */
110
+ export declare function clearLastRunResult(cfg: AutosyncConfig): AutosyncConfig;
111
+ /**
112
+ * 勾选「自动同步」后是否该立刻跑一轮(issue #105 建议 4)。
113
+ *
114
+ * 三种情况返回 false:
115
+ * - 本来就已经开着:这次 POST 只是改间隔 / 调阈值,不是「打开」动作;
116
+ * - 打开后仍在 startupMinIntervalMs 冷却窗口内:刚跑过,别连点把通道打爆;
117
+ * - 这次是把开关关掉:关掉当然不跑。
118
+ * `lastRunAt` 缺失或读不出(NaN)→ 当作「很久没跑过」,该跑。
119
+ *
120
+ * 判据与 `autosync-scheduler.ts` 的 `shouldTriggerStartupRun` 一致;这里不能直接 import 它
121
+ * —— 调度器依赖本模块,反向引用会成环。
122
+ */
123
+ export declare function shouldRunOnEnable(prev: Pick<AutosyncConfig, 'enabled'>, next: Pick<AutosyncConfig, 'enabled' | 'lastRunAt' | 'startupMinIntervalMs'>, nowMs?: number): boolean;
@@ -25,6 +25,18 @@ export const AUTOSYNC_CONFIG_SCHEMA_VERSION = 2;
25
25
  export const DEFAULT_AUTOSYNC_INTERVAL = '30m';
26
26
  /** 缺省启动触发最小间隔阈值(5 分钟,防频繁重启反复同步) */
27
27
  export const DEFAULT_STARTUP_MIN_INTERVAL_MS = 5 * 60 * 1000;
28
+ /** 缺省开启「本地改动即时触发」(真·自动同步;关闭后退回纯定时器) */
29
+ export const DEFAULT_AUTOSYNC_REALTIME = true;
30
+ /** 缺省远端轮询间隔(realtime 开启时与本地监听并行:本地改动即时上行,远端改动最迟 1 分钟内下行) */
31
+ export const DEFAULT_AUTOSYNC_REMOTE_CHECK_MS = 60 * 1000;
32
+ /** 远端轮询间隔下限(15 s):再短只是空转 fetch,收益为零 */
33
+ export const MIN_AUTOSYNC_REMOTE_CHECK_MS = 15 * 1000;
34
+ /** 远端轮询间隔上限(60 min):比这更慢就不该叫「实时」了 */
35
+ export const MAX_AUTOSYNC_REMOTE_CHECK_MS = 60 * 60 * 1000;
36
+ /** 缺省自动应用等级:medium(用户拍板:plugins/mcp 的中风险改动也要真自动,否则「同步所有改动」是假的) */
37
+ export const DEFAULT_AUTOSYNC_AUTO_APPLY_TIER = 'medium';
38
+ /** 缺省冲突策略:local(保留本机并推上去) */
39
+ export const DEFAULT_AUTOSYNC_CONFLICT_POLICY = 'local';
28
40
  /** 缺省配置(首次无文件 / 损坏 / 不支持 schema 时回退) */
29
41
  export function defaultAutosyncConfig() {
30
42
  return {
@@ -32,8 +44,18 @@ export function defaultAutosyncConfig() {
32
44
  interval: DEFAULT_AUTOSYNC_INTERVAL,
33
45
  startupMinIntervalMs: DEFAULT_STARTUP_MIN_INTERVAL_MS,
34
46
  consecutiveFailures: 0,
47
+ realtime: DEFAULT_AUTOSYNC_REALTIME,
48
+ remoteCheckMs: DEFAULT_AUTOSYNC_REMOTE_CHECK_MS,
49
+ autoApplyTier: DEFAULT_AUTOSYNC_AUTO_APPLY_TIER,
50
+ conflictPolicy: DEFAULT_AUTOSYNC_CONFLICT_POLICY,
35
51
  };
36
52
  }
53
+ /** 远端轮询间隔夹取(非数字 / 越界一律回落到边界值,绝不写入 NaN)。 */
54
+ export function clampRemoteCheckMs(value) {
55
+ if (!Number.isFinite(value))
56
+ return DEFAULT_AUTOSYNC_REMOTE_CHECK_MS;
57
+ return Math.min(MAX_AUTOSYNC_REMOTE_CHECK_MS, Math.max(MIN_AUTOSYNC_REMOTE_CHECK_MS, Math.round(value)));
58
+ }
37
59
  /** 从 v1 顶层字段(缺 schemaVersion 或 schemaVersion=1)解析单通道配置;非法字段回退缺省。 */
38
60
  function parseV1Channel(obj) {
39
61
  const cfg = defaultAutosyncConfig();
@@ -47,6 +69,16 @@ function parseV1Channel(obj) {
47
69
  if (typeof obj['consecutiveFailures'] === 'number' && Number.isFinite(obj['consecutiveFailures']) && obj['consecutiveFailures'] >= 0) {
48
70
  cfg.consecutiveFailures = obj['consecutiveFailures'];
49
71
  }
72
+ // 真·自动同步的三个新字段:缺失(旧文件)→ 缺省值,等于升级后立刻自动生效(用户已拍板)
73
+ if (typeof obj['realtime'] === 'boolean')
74
+ cfg.realtime = obj['realtime'];
75
+ if (typeof obj['remoteCheckMs'] === 'number')
76
+ cfg.remoteCheckMs = clampRemoteCheckMs(obj['remoteCheckMs']);
77
+ if (obj['autoApplyTier'] === 'low' || obj['autoApplyTier'] === 'medium')
78
+ cfg.autoApplyTier = obj['autoApplyTier'];
79
+ if (obj['conflictPolicy'] === 'ask' || obj['conflictPolicy'] === 'local') {
80
+ cfg.conflictPolicy = obj['conflictPolicy'];
81
+ }
50
82
  if (typeof obj['lastRunAt'] === 'string' && obj['lastRunAt'] !== '')
51
83
  cfg.lastRunAt = obj['lastRunAt'];
52
84
  if (typeof obj['lastRunStatus'] === 'string' && (obj['lastRunStatus'] === 'success' || obj['lastRunStatus'] === 'skipped' || obj['lastRunStatus'] === 'failed' || obj['lastRunStatus'] === 'partial')) {
@@ -125,6 +157,59 @@ export async function writeAutosyncConfig(dir, channel, cfg) {
125
157
  const data = stringifyJsonSafe(payload, { space: 2 });
126
158
  await atomicWriteFile(target, data, { mode: 0o600 });
127
159
  }
160
+ /**
161
+ * 可展示的「本轮失败原因」(issue #105)。
162
+ *
163
+ * 判据只有一条:**本轮状态真的是 failed**,才允许把 lastRunMessage 当成错误文案回传。
164
+ * 此前状态路由无条件回传 lastRunMessage,而 POST /sync/autosync 又会把读到的旧配置原样
165
+ * 写回 —— 于是「十天前那次失败的错误原文」会被一次纯粹改间隔的保存坐实,卡片上继续挂着
166
+ * 早已不成立的错误,用户据此去查一个不存在的故障(真机现场:认证已修好,界面仍报认证失败)。
167
+ *
168
+ * 返回 undefined = 界面不得渲染任何 lastRunMessage 单元格。
169
+ */
170
+ export function lastRunFailureMessage(cfg) {
171
+ if (cfg.lastRunStatus !== 'failed')
172
+ return undefined;
173
+ const message = cfg.lastRunMessage;
174
+ return message === undefined || message === '' ? undefined : message;
175
+ }
176
+ /**
177
+ * 清空「上一轮运行结果」三字段(issue #105:配置变更 ≠ 一轮运行结果)。
178
+ *
179
+ * POST /sync/autosync 是**配置**写入而不是运行,写完必须把上一轮的结论清掉,否则旧结论
180
+ * 会被当成当前结论继续展示。保留 lastRunAt/lastRunStatus 与连续失败计数:它们描述的是
181
+ * 「上一次真的跑过什么」,不会因为改了间隔就变成假话。
182
+ */
183
+ export function clearLastRunResult(cfg) {
184
+ const next = { ...cfg };
185
+ delete next.lastRunMessage;
186
+ delete next.lastRunSkipReason;
187
+ delete next.lastRunSkipCount;
188
+ return next;
189
+ }
190
+ /**
191
+ * 勾选「自动同步」后是否该立刻跑一轮(issue #105 建议 4)。
192
+ *
193
+ * 三种情况返回 false:
194
+ * - 本来就已经开着:这次 POST 只是改间隔 / 调阈值,不是「打开」动作;
195
+ * - 打开后仍在 startupMinIntervalMs 冷却窗口内:刚跑过,别连点把通道打爆;
196
+ * - 这次是把开关关掉:关掉当然不跑。
197
+ * `lastRunAt` 缺失或读不出(NaN)→ 当作「很久没跑过」,该跑。
198
+ *
199
+ * 判据与 `autosync-scheduler.ts` 的 `shouldTriggerStartupRun` 一致;这里不能直接 import 它
200
+ * —— 调度器依赖本模块,反向引用会成环。
201
+ */
202
+ export function shouldRunOnEnable(prev, next, nowMs = Date.now()) {
203
+ if (!next.enabled || prev.enabled)
204
+ return false;
205
+ const lastRunAt = next.lastRunAt;
206
+ if (lastRunAt === undefined || lastRunAt === '')
207
+ return true;
208
+ const lastMs = Date.parse(lastRunAt);
209
+ if (Number.isNaN(lastMs))
210
+ return true;
211
+ return nowMs - lastMs >= next.startupMinIntervalMs;
212
+ }
128
213
  /** AutosyncConfig → 文件载荷(可选运行字段仅在非空时写入,保持文件干净)。 */
129
214
  function toChannelPayload(cfg) {
130
215
  const payload = {
@@ -132,6 +217,10 @@ function toChannelPayload(cfg) {
132
217
  interval: cfg.interval,
133
218
  startupMinIntervalMs: cfg.startupMinIntervalMs,
134
219
  consecutiveFailures: cfg.consecutiveFailures,
220
+ realtime: cfg.realtime ?? DEFAULT_AUTOSYNC_REALTIME,
221
+ remoteCheckMs: clampRemoteCheckMs(cfg.remoteCheckMs ?? DEFAULT_AUTOSYNC_REMOTE_CHECK_MS),
222
+ autoApplyTier: cfg.autoApplyTier ?? DEFAULT_AUTOSYNC_AUTO_APPLY_TIER,
223
+ conflictPolicy: cfg.conflictPolicy ?? DEFAULT_AUTOSYNC_CONFLICT_POLICY,
135
224
  };
136
225
  if (cfg.lastRunAt !== undefined && cfg.lastRunAt !== '')
137
226
  payload['lastRunAt'] = cfg.lastRunAt;
@@ -4,6 +4,7 @@ import type { SectionId } from '../schema/types.ts';
4
4
  import type { RunRegistry } from '../core/run-registry.ts';
5
5
  import type { JournalRunContext } from '../core/phase3-host.ts';
6
6
  import type { SyncEngine } from './sync-engine.ts';
7
+ import type { SyncSnapshotMeta } from './transport.ts';
7
8
  import type { MutationLockPort, MutationLockContext } from '../utils/env-lock.ts';
8
9
  import type { AutosyncConfig, AutosyncInterval } from './autosync-config.ts';
9
10
  import type { SyncConfig, SyncTransportType } from './sync-config.ts';
@@ -11,6 +12,7 @@ import { readSyncHistory } from './sync-history.ts';
11
12
  import type { AutosyncHistoryEntry } from './sync-history.ts';
12
13
  import type { MergePlan } from './merge.ts';
13
14
  import type { SyncApplyPlan } from './risk.ts';
15
+ import { SyncWatcher } from './sync-watcher.ts';
14
16
  /** 间隔 → ms 换算(§4.3) */
15
17
  export declare function intervalToMs(interval: AutosyncInterval): number;
16
18
  /**
@@ -31,6 +33,24 @@ export interface AutosyncRunResult {
31
33
  historyId: string;
32
34
  consecutiveFailures: number;
33
35
  }
36
+ /**
37
+ * runOnce 的触发语义(只影响「做什么」的两个分支,不改变同步流程本身)。
38
+ * reason 只进日志,用来说明这一轮是被谁叫醒的(排障时能区分「定时兜底」与「真有事发生」)。
39
+ */
40
+ export interface AutosyncRunOptions {
41
+ /** true = 启动触发变体:只做下行合并,不上传。 */
42
+ startup?: boolean;
43
+ /** 触发来源:interval / remote-poll / local-change / manual / startup / enable-toggle。 */
44
+ reason?: string;
45
+ }
46
+ /** watcher 工厂入参(缺省实现 = 真实 SyncWatcher;测试注入内存实现)。 */
47
+ export interface WatcherFactoryOptions {
48
+ homeDir: string;
49
+ profile: string | undefined;
50
+ log: Logger;
51
+ /** 一批本地改动落地(已去抖)→ 交给调度器触发一轮同步。 */
52
+ onFlush: (changed: readonly string[]) => void;
53
+ }
34
54
  export interface AutoSyncSchedulerOptions {
35
55
  syncDir: string;
36
56
  host: {
@@ -57,8 +77,9 @@ export interface AutoSyncSchedulerOptions {
57
77
  /**
58
78
  * 远端新快照检测(§3.2「下载=检测到远端新快照才拉取」)。
59
79
  * 缺省:若 engine 实现了 hasNewRemoteSnapshot() 则调用;否则视为 true(保持旧行为=每次拉取)。
80
+ * `listed` = 本轮已取到的远端列表(查加密快照时顺手拿到)—— 传下去就不必再 list 一次。
60
81
  */
61
- detectRemoteNew?: (engine: SyncEngine) => Promise<boolean>;
82
+ detectRemoteNew?: (engine: SyncEngine, listed?: readonly SyncSnapshotMeta[]) => Promise<boolean>;
62
83
  /**
63
84
  * 本地配置变化检测(§3.1「上传=本地改动才推」)。
64
85
  * 缺省:若 engine 实现了 hasLocalChanges() 则调用;否则视为 true(保持旧行为=每次都推)。
@@ -89,6 +110,17 @@ export interface AutoSyncSchedulerOptions {
89
110
  result: unknown;
90
111
  }>;
91
112
  };
113
+ /**
114
+ * 家目录(= DSH 的 $DSH_HOME)。提供时才启用「本地改动即时上行」的文件监听;
115
+ * 缺省(测试/未接线)→ 只靠定时器与远端轮询,行为退化为改造前。
116
+ */
117
+ homeDir?: string;
118
+ /** 当前 profile(决定监听 profiles/<profile>/ 下的组合 patch 与插件清单)。 */
119
+ profile?: string;
120
+ /** 注入 watcher 工厂(缺省 = 真实 SyncWatcher;测试可传内存实现)。 */
121
+ makeWatcher?: (opts: WatcherFactoryOptions) => SyncWatcher;
122
+ /** 触发合并窗口(ms):连续保存/连续远端探测只跑一轮。缺省 3000。 */
123
+ minTriggerGapMs?: number;
92
124
  }
93
125
  export declare class AutoSyncScheduler {
94
126
  private readonly syncDir;
@@ -112,6 +144,22 @@ export declare class AutoSyncScheduler {
112
144
  private lockCtxForJournal;
113
145
  /** 每通道一个定时器(git/webdav 各自 enabled 时独立排期)。 */
114
146
  private readonly timers;
147
+ /** realtime 通道的远端轮询定时器:与 interval 定时器**分开**,否则每次轮询都会重置兜底间隔。 */
148
+ private readonly remoteTimers;
149
+ /** 本地改动监听(只有 enabled && realtime 的通道才挂;全关 → null)。 */
150
+ private watcher;
151
+ /** 当前需要监听的通道(setupWatchers 计算)。 */
152
+ private realtimeChannels;
153
+ /** 运行期到达的触发:本轮收尾后立刻补跑(绝不并发写本地配置,也绝不丢)。 */
154
+ private pendingTrigger;
155
+ /** 上次开始运行的时刻(trigger 的最小间隔合并用)。 */
156
+ private readonly lastRunAtMs;
157
+ /** 因最小间隔被推迟的触发(每通道至多一个)。 */
158
+ private readonly gapTimers;
159
+ private readonly homeDir;
160
+ private readonly profile;
161
+ private readonly makeWatcher;
162
+ private readonly minTriggerGapMs;
115
163
  private stopped;
116
164
  private running;
117
165
  constructor(opts: AutoSyncSchedulerOptions);
@@ -119,11 +167,30 @@ export declare class AutoSyncScheduler {
119
167
  private appendHistory;
120
168
  /** 启动:读两个通道配置 → enabled 通道各自排定时器 → 对每个 enabled 通道执行一次启动触发。 */
121
169
  start(): void;
122
- /** 停止:清全部定时器、标记不再调度;正在执行的任务允许自然结束。 */
170
+ /** 停止:清全部定时器、拆掉文件监听、标记不再调度;正在执行的任务允许自然结束。 */
123
171
  stop(): void;
124
172
  /** 重新加载配置(路由 POST /sync/autosync 后调用;重排所有通道定时器)。 */
125
173
  reload(): Promise<void>;
126
174
  private refreshTimers;
175
+ /**
176
+ * 外部触发一次运行(本地改动 / 远端探测 / 另一通道让位)。
177
+ *
178
+ * - 正在跑 → 记 pendingTrigger,本轮收尾后**立刻补跑**(不并发写本地配置,也绝不丢);
179
+ * - 距上次开始不足 minTriggerGapMs → 合并成一次(连续保存只跑一轮,每通道至多一个定时器)。
180
+ */
181
+ trigger(channel: SyncTransportType, reason?: string): void;
182
+ /** 兜底间隔定时器:每个 enabled 通道按各自 interval 排一次性定时器,触发后只重排自己。 */
183
+ private scheduleIntervalTimers;
184
+ /**
185
+ * realtime 通道的远端轮询:按 remoteCheckMs(缺省 60s)排一次性定时器。
186
+ * 轮询只是「叫醒一轮 runOnce」—— 是否真的下载仍由 detectRemoteNew 判定,
187
+ * 所以哪怕每 60s 叫醒一次,无变化时也只会记一条 upToDate 而不会反复传输。
188
+ */
189
+ private scheduleRemoteTimers;
190
+ /** 按「enabled && realtime」重算监听集合:需要就挂、全关就拆(绝不留下空转句柄)。 */
191
+ private setupWatchers;
192
+ /** watcher 回调:任一批改动 → 让所有 realtime 通道各跑一轮(各自有变化检测,不会空转传输)。 */
193
+ private onLocalChange;
127
194
  /** 启动触发下载合并(每个 enabled 通道;受各自 startupMinIntervalMs 阈值约束)。 */
128
195
  private startupRuns;
129
196
  /**
@@ -131,9 +198,8 @@ export declare class AutoSyncScheduler {
131
198
  * @param channel - 同步通道(git / webdav;各自独立的配置与运行状态)
132
199
  * @param opts.startup - true 表示启动触发变体(只做 Phase A+B,不做 Phase C push)
133
200
  */
134
- runOnce(channel: SyncTransportType, opts?: {
135
- startup?: boolean;
136
- }): Promise<AutosyncRunResult>;
201
+ runOnce(channel: SyncTransportType, opts?: AutosyncRunOptions): Promise<AutosyncRunResult>;
202
+ private runOnceInternal;
137
203
  /**
138
204
  * 收尾:写该通道 autosync-config(lastRunAt / lastRunStatus / consecutiveFailures /
139
205
  * lastRunHistoryId / 本轮消息与跳过原因)。
@@ -146,7 +212,15 @@ export declare class AutoSyncScheduler {
146
212
  * 成功清空两者(空串/undefined 不进文件,见 autosync-config.toChannelPayload)。
147
213
  */
148
214
  private writeFinalConfig;
149
- /** 连续失败 ≥ 3 → 通知(host.log.warn)。 */
215
+ /**
216
+ * 连续失败 ≥ 3 → 通知(host.log.warn)。
217
+ *
218
+ * issue #105:通知的持久痕迹只留历史 entry 的 notifiedAt。
219
+ * 此前这里把通知文案写进 autosync-config.lastRunMessage —— 那是「本轮错误原文」的字段,
220
+ * 一个字段两种语义,界面就无法判断该显示什么;而且 entry.notifiedAt 只在内存对象上改、
221
+ * 从未落盘(sync-history 当时只有 append),通知事实其实没留下。现在两者都走一处:
222
+ * 通知文案只进日志,notifiedAt 落盘到历史记录。
223
+ */
150
224
  private maybeNotify;
151
225
  }
152
226
  /**