dsh-config-manager 0.1.79 → 0.1.81

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 (51) hide show
  1. package/lib/cli/actions.d.ts +5 -0
  2. package/lib/cli/actions.js +5 -0
  3. package/lib/cli/index.js +21 -7
  4. package/lib/client.d.ts +1 -0
  5. package/lib/client.js +589 -534
  6. package/lib/index.d.ts +1 -1
  7. package/lib/index.js +1 -1
  8. package/lib/routes/sync.js +16 -0
  9. package/lib/sync/ancestor.d.ts +22 -0
  10. package/lib/sync/ancestor.js +66 -2
  11. package/lib/sync/autosync-scheduler.d.ts +8 -3
  12. package/lib/sync/autosync-scheduler.js +22 -9
  13. package/lib/sync/gist/gist-transport.d.ts +8 -5
  14. package/lib/sync/gist/gist-transport.js +8 -30
  15. package/lib/sync/gist/messages.d.ts +0 -1
  16. package/lib/sync/gist/messages.js +1 -3
  17. package/lib/sync/s3/sigv4.js +11 -2
  18. package/lib/sync/sync-engine.d.ts +32 -4
  19. package/lib/sync/sync-engine.js +73 -22
  20. package/lib/sync/sync-session.d.ts +5 -0
  21. package/lib/ui/i18n.d.ts +1 -0
  22. package/lib/ui/i18n.js +2 -0
  23. package/package.json +1 -1
  24. package/src/cli/actions.ts +6 -0
  25. package/src/cli/index-guards.test.ts +62 -0
  26. package/src/cli/index.ts +19 -9
  27. package/src/client/common/api-base.test.ts +36 -0
  28. package/src/client/common/http.test.ts +12 -1
  29. package/src/client/common/http.ts +8 -2
  30. package/src/client/common/routes.ts +37 -3
  31. package/src/client/market/market-api.test.ts +3 -2
  32. package/src/client/sync/MergeModal.tsx +2 -1
  33. package/src/client/sync/MergePreviewView.tsx +1 -1
  34. package/src/client/sync/merge-view.test.ts +19 -5
  35. package/src/client/sync/merge-view.ts +36 -7
  36. package/src/client/sync/sync-api.test.ts +5 -3
  37. package/src/index.ts +1 -1
  38. package/src/routes/sync.ts +16 -0
  39. package/src/sync/ancestor.ts +76 -2
  40. package/src/sync/autosync-scheduler.test.ts +62 -0
  41. package/src/sync/autosync-scheduler.ts +21 -8
  42. package/src/sync/gist/gist-conditional-write.test.ts +30 -50
  43. package/src/sync/gist/gist-transport.ts +13 -31
  44. package/src/sync/gist/messages.ts +1 -3
  45. package/src/sync/s3/s3-transport.test.ts +2 -1
  46. package/src/sync/s3/sigv4.test.ts +4 -1
  47. package/src/sync/s3/sigv4.ts +11 -2
  48. package/src/sync/sync-engine.test.ts +95 -19
  49. package/src/sync/sync-engine.ts +89 -22
  50. package/src/sync/sync-session.ts +5 -0
  51. package/src/ui/i18n.ts +2 -0
@@ -142,6 +142,11 @@ export interface RescueStatus {
142
142
  /** 首页聚合:每一项各自 try/catch,缺一项不影响其余(但失败必须在 errors 里可见)。 */
143
143
  export declare function readRescueStatus(paths: RescuePaths, now?: () => Date): Promise<RescueStatus>;
144
144
  export type WriteRefusalCode = 'safe-mode' | 'stale-lock' | 'dsh-running' | 'nothing-to-do' | 'io-error';
145
+ /**
146
+ * SAFE MODE 拒因码的**单点常量**:`src/cli/index.ts` 的源码守卫(P0-11-07)禁止
147
+ * CLI 自带 `'safe-mode'` 字面量,判据必须复用本常量(否则改一处漏一处)。
148
+ */
149
+ export declare const SAFE_MODE_REFUSAL_CODE: WriteRefusalCode;
145
150
  export interface WriteGateRefusal {
146
151
  ok: false;
147
152
  code: WriteRefusalCode;
@@ -279,6 +279,11 @@ export async function readRescueStatus(paths, now = () => new Date()) {
279
279
  function errorText(error) {
280
280
  return error instanceof Error ? error.message : String(error);
281
281
  }
282
+ /**
283
+ * SAFE MODE 拒因码的**单点常量**:`src/cli/index.ts` 的源码守卫(P0-11-07)禁止
284
+ * CLI 自带 `'safe-mode'` 字面量,判据必须复用本常量(否则改一处漏一处)。
285
+ */
286
+ export const SAFE_MODE_REFUSAL_CODE = 'safe-mode';
282
287
  /**
283
288
  * 写动作的前置门(与 CLI `sessions repair --fix` 同一判据)。
284
289
  *
package/lib/cli/index.js CHANGED
@@ -30,7 +30,7 @@ import { runSessionsRepair } from './sessions-repair.js';
30
30
  import { runSessionsInspect } from './sessions-inspect.js';
31
31
  import { runImportSource } from './import-source.js';
32
32
  import { renderCommandHelp, renderOverview } from './help.js';
33
- import { collectVerifyResults, readRunningInstances } from './actions.js';
33
+ import { checkWriteGates, collectVerifyResults, readRunningInstances, SAFE_MODE_REFUSAL_CODE } from './actions.js';
34
34
  import { Phase3Recovery, readSafeModeMarkerSync, safeModeMarkerPath } from '../core/phase3-host.js';
35
35
  import { verifyBackupZip, } from '../core/backup-verify.js';
36
36
  import { collectBackupEntries, buildSectionFlags, parseSectionsArg, OFFLINE_UNAVAILABLE_SECTIONS, } from '../core/backup-plan.js';
@@ -853,19 +853,33 @@ export async function runCli(argv, io = defaultIo, env = process.env, deps = {})
853
853
  }, io, env);
854
854
  }
855
855
  if (options.command === 'sessions') {
856
- // 离线修复:不碰 $DSH_HOME 的其它部分、不需要环境锁(只读写会话目录,且默认 dry-run)
856
+ // 离线修复:只读写会话目录,默认 dry-run(零写入)。list/doctor 纯只读,连「DSH 在跑」都不用管。
857
+ // **--fix 走与救急台完全相同的写入门**(actions.ts 的 checkWriteGates,顺序 SAFE MODE → 残留环境锁 → DSH 未运行):
858
+ // 此前这里只查「DSH 在跑」,于是 SAFE MODE 激活 / 残留环境锁时 CLI 仍能改写会话字节 —— 与救急台判据分叉(审计 P1)。
857
859
  const home = options.home ?? resolveDshHome(env);
858
860
  const action = options.positionals[0] ?? 'repair';
859
861
  // T6:list / doctor 是**只读**动作 —— 不检查「DSH 是否在跑」,因为读永远不会伤害运行中的实例。
860
862
  if (action === 'list' || action === 'doctor') {
861
863
  return runSessionsInspect({ home, action: action === 'doctor' ? 'doctor' : 'list', json: options.json }, io);
862
864
  }
863
- // T6:repair(含 --fix/--apply)先跑安全修复计划;**写操作前必须确认 DSH 没在跑**。
865
+ // T6:repair(含 --fix/--apply)先跑安全修复计划;写操作前三道门与救急台逐条同源。
864
866
  if (options.fix === true) {
865
- const running = (await readRunningInstances(resolveControlRoots({ dataRoot: options.dataRoot, dataDir: options.dataDir }, env))).map((instance) => instance.name);
866
- if (running.length > 0) {
867
- io.error('检测到 DSH 正在运行(' + running.join(', ') + '):请先关闭 DSH 再执行修复。');
868
- io.error('DSH running (' + running.join(', ') + '): close DSH before repairing session bytes.');
867
+ const controlOpts = { dataRoot: options.dataRoot, dataDir: options.dataDir };
868
+ const gate = await checkWriteGates(resolveControlRoots(controlOpts, env), {
869
+ needsDshStopped: true,
870
+ locksDir: resolveRecoverLocksDir(controlOpts, env),
871
+ });
872
+ if (!gate.ok) {
873
+ io.error('拒绝执行:' + gate.reason);
874
+ if (gate.code === 'dsh-running') {
875
+ io.error('DSH running: close DSH before repairing session bytes.');
876
+ }
877
+ else if (gate.code === SAFE_MODE_REFUSAL_CODE) {
878
+ io.error('SAFE MODE is active: resolve the pending transaction before writing session bytes.');
879
+ }
880
+ else if (gate.code === 'stale-lock') {
881
+ io.error('A stale environment lock is present: run "dsh-config-manager recover-stale-lock" first.');
882
+ }
869
883
  return 1;
870
884
  }
871
885
  }
package/lib/client.d.ts CHANGED
@@ -1546,6 +1546,7 @@ declare const uiZh: {
1546
1546
  readonly 'report.unit.notes': '条说明';
1547
1547
  readonly 'report.action.done': '完成';
1548
1548
  readonly 'error.notMounted': 'config-manager 服务未挂载(插件未加载):请确认 profile 中已安装 dsh-config-manager 并重启 DSH';
1549
+ readonly 'error.apiPathMissed': '接口路径未命中(HTTP {status}):请求前缀为 {base},但该路径没有返回 JSON —— 若你经反向代理挂在子路径下,请检查代理是否转发了这个前缀(issue #108)';
1549
1550
  readonly 'error.httpInvalidJson': 'HTTP {status}: 无效的 JSON 响应';
1550
1551
  readonly 'error.fallback': '操作失败';
1551
1552
  readonly 'error.requestTimeout': '请求超时({seconds} 秒):未在时限内收到宿主响应,请重试';