@logictan/dsh-config-manager 0.1.60 → 0.1.62

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 (77) hide show
  1. package/lib/adapters/credentials.d.ts +1 -1
  2. package/lib/adapters/index.d.ts +1 -1
  3. package/lib/adapters/index.js +1 -1
  4. package/lib/adapters/mcp.js +4 -4
  5. package/lib/adapters/plugins.d.ts +0 -1
  6. package/lib/adapters/plugins.js +35 -19
  7. package/lib/adapters/prompts.js +6 -6
  8. package/lib/adapters/test-helpers.d.ts +27 -0
  9. package/lib/adapters/test-helpers.js +40 -0
  10. package/lib/client.d.ts +1 -13
  11. package/lib/client.js +297 -304
  12. package/lib/core/analyzer.js +5 -4
  13. package/lib/core/backup.js +5 -3
  14. package/lib/core/exporter.d.ts +1 -4
  15. package/lib/core/exporter.js +14 -45
  16. package/lib/core/importer.d.ts +1 -1
  17. package/lib/core/index.d.ts +1 -1
  18. package/lib/core/messages.d.ts +1 -5
  19. package/lib/core/messages.js +2 -10
  20. package/lib/core/patch-layers.d.ts +32 -0
  21. package/lib/core/patch-layers.js +39 -0
  22. package/lib/core/rollback.js +4 -4
  23. package/lib/core/types.d.ts +8 -12
  24. package/lib/index.d.ts +23 -14
  25. package/lib/index.js +31 -92
  26. package/lib/security/index.d.ts +1 -3
  27. package/lib/security/index.js +1 -3
  28. package/lib/sync/transport.d.ts +2 -2
  29. package/lib/sync/transport.js +1 -1
  30. package/lib/ui/i18n.d.ts +0 -3
  31. package/lib/ui/i18n.js +0 -6
  32. package/lib/ui/report.js +1 -2
  33. package/lib/ui/test-helpers.d.ts +0 -6
  34. package/lib/ui/test-helpers.js +1 -5
  35. package/lib/ui/types.d.ts +0 -13
  36. package/lib/utils/env-lock.d.ts +16 -0
  37. package/lib/utils/env-lock.js +27 -6
  38. package/package.json +2 -2
  39. package/src/adapters/credentials.ts +1 -1
  40. package/src/adapters/index.ts +1 -1
  41. package/src/adapters/mcp.ts +4 -4
  42. package/src/adapters/patch-layers.test.ts +201 -0
  43. package/src/adapters/plugins.test.ts +13 -5
  44. package/src/adapters/plugins.ts +33 -17
  45. package/src/adapters/prompts.ts +6 -6
  46. package/src/adapters/test-helpers.ts +48 -0
  47. package/src/client/api.ts +1 -10
  48. package/src/client/config-manager.module.css +1 -1
  49. package/src/client/run-store.test.ts +1 -1
  50. package/src/client/sync/SyncSettingsView.tsx +5 -5
  51. package/src/client/sync/sync-api.ts +0 -1
  52. package/src/client/sync/sync-locales.ts +0 -1
  53. package/src/core/analyzer.ts +5 -4
  54. package/src/core/backup.ts +5 -3
  55. package/src/core/config-lifecycle.test.ts +4 -1
  56. package/src/core/exporter.ts +15 -47
  57. package/src/core/importer.ts +1 -1
  58. package/src/core/index.ts +1 -1
  59. package/src/core/messages.ts +2 -10
  60. package/src/core/patch-layers.ts +42 -0
  61. package/src/core/rollback.ts +4 -4
  62. package/src/core/smoke.test.ts +5 -5
  63. package/src/core/types.ts +8 -12
  64. package/src/index.facade.test.ts +25 -2
  65. package/src/index.ts +30 -104
  66. package/src/security/index.ts +1 -3
  67. package/src/security/security.test.ts +0 -361
  68. package/src/sync/transport.ts +3 -3
  69. package/src/ui/i18n.ts +0 -6
  70. package/src/ui/report.ts +1 -2
  71. package/src/ui/test-helpers.ts +3 -7
  72. package/src/ui/types.ts +0 -10
  73. package/src/utils/env-lock.test.ts +29 -0
  74. package/src/utils/env-lock.ts +27 -6
  75. package/lib/security/encryption.d.ts +0 -85
  76. package/lib/security/encryption.js +0 -279
  77. package/src/security/encryption.ts +0 -335
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * 安全不变量:永不导出值(hasValue 恒 false);导入生成 MissingSecret 清单,
7
7
  * 用户补录值经 ctx.secretInputs / decryptedCredentials(仅内存)→ credentials.set()。
8
- * .credentials.yaml 文件字节交由 m4 加密层处理,本 adapter 不触碰。
8
+ * .credentials.yaml 文件字节由 Exporter 的文件级 vault 处理,本 adapter 不触碰。
9
9
  */
10
10
  import type { CredentialsSection } from '../schema/types.ts';
11
11
  import type { MsgFunc } from '../core/messages.ts';
@@ -42,7 +42,7 @@ export type { CredentialRefsProvider } from './credentials.ts';
42
42
  export { SettingsAdapter } from './settings.ts';
43
43
  export { UiAdapter, isUiNamespace, KNOWN_UI_NAMESPACE_PREFIXES, UI_MIGRATION_NOTES } from './ui.ts';
44
44
  export { ProvidersAdapter, DEFAULT_PROVIDER_NAMESPACES, type ProviderExportEntry, type ProviderExportSection } from './providers.ts';
45
- export { PluginsAdapter, USER_PATCH_FILE } from './plugins.ts';
45
+ export { PluginsAdapter } from './plugins.ts';
46
46
  export type { LocalPluginPackHook } from './plugins.ts';
47
47
  export { McpAdapter, extractMcpServers, buildMcpPatchLine, type McpExportEntry, type McpExportSection } from './mcp.ts';
48
48
  export { PromptsAdapter, extractPrompts, mergePromptIntoLine, buildPromptLine, type PromptExportEntry, type PromptsExportSection } from './prompts.ts';
@@ -40,7 +40,7 @@ export function createAdapters(options = {}) {
40
40
  export { SettingsAdapter } from './settings.js';
41
41
  export { UiAdapter, isUiNamespace, KNOWN_UI_NAMESPACE_PREFIXES, UI_MIGRATION_NOTES } from './ui.js';
42
42
  export { ProvidersAdapter, DEFAULT_PROVIDER_NAMESPACES } from './providers.js';
43
- export { PluginsAdapter, USER_PATCH_FILE } from './plugins.js';
43
+ export { PluginsAdapter } from './plugins.js';
44
44
  export { McpAdapter, extractMcpServers, buildMcpPatchLine } from './mcp.js';
45
45
  export { PromptsAdapter, extractPrompts, mergePromptIntoLine, buildPromptLine } from './prompts.js';
46
46
  export { SkillsAdapter } from './skills.js';
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { isDeepStrictEqual } from 'node:util';
10
10
  import { msgOf, zhMsg } from '../core/messages.js';
11
- import { USER_PATCH_FILE } from './plugins.js';
11
+ import { HOME_PATCH_FILE } from '../core/patch-layers.js';
12
12
  /** 从 patch 行数组中提取 MCP server 条目(以 config.serverName 存在为判定) */
13
13
  export function extractMcpServers(lines) {
14
14
  const servers = [];
@@ -84,7 +84,7 @@ export class McpAdapter {
84
84
  const warnings = [];
85
85
  let lines = [];
86
86
  try {
87
- lines = await ctx.patchFile.readPatchLines(USER_PATCH_FILE);
87
+ lines = await ctx.patchFile.readPatchLines(HOME_PATCH_FILE);
88
88
  }
89
89
  catch (err) {
90
90
  warnings.push(msgOf(ctx)('adapter.patchReadFailedMCP', { reason: err instanceof Error ? err.message : String(err) }));
@@ -100,7 +100,7 @@ export class McpAdapter {
100
100
  async analyzeImport(data, ctx) {
101
101
  const msg = ctx.msg;
102
102
  const items = [];
103
- const targetLines = await ctx.target.patchFile.readPatchLines(USER_PATCH_FILE);
103
+ const targetLines = await ctx.target.patchFile.readPatchLines(HOME_PATCH_FILE);
104
104
  const targetServers = extractMcpServers(targetLines);
105
105
  for (const server of data.servers) {
106
106
  const id = `mcp:${server.serverName}`;
@@ -149,7 +149,7 @@ export class McpAdapter {
149
149
  if (!ref)
150
150
  return { ok: false, message: msg('adapter.missingTargetRef') };
151
151
  const raw = buildMcpPatchLine(ref, server);
152
- await ctx.target.patchFile.applyPatchChanges(USER_PATCH_FILE, [
152
+ await ctx.target.patchFile.applyPatchChanges(HOME_PATCH_FILE, [
153
153
  { lineId: ref, raw, action: item.kind === 'Create' ? 'insert' : 'update' },
154
154
  ]);
155
155
  return { ok: true, needsRestart: true, message: msg('adapter.mcpWritten', { serverName }) };
@@ -2,7 +2,6 @@ import type { PackLocalPluginsResult } from '../core/local-plugin-pack.ts';
2
2
  import type { MsgFunc } from '../core/messages.ts';
3
3
  import type { PluginEntry, PluginsSection } from '../schema/types.ts';
4
4
  import type { ApplyResult, ConfigAdapter, ExportOptions, ExportSection, HostContext, ImportContext, PlanItem, ValidationResult } from '../core/types.ts';
5
- export declare const USER_PATCH_FILE = "cordis.patch.yml";
6
5
  /**
7
6
  * 本地源插件打包钩子(由宿主注入,见 src/index.ts createAdapters)。
8
7
  * 返回打包结果(含字节与重写后的 spec);不注入 = 不做本地源打包(保持旧行为)。
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * plugins 分区 adapter(设计 §3.3/§8):
3
- * 数据源 = ctx.plugins.listInstalled()(插件清单)+ 用户 patch 层(profile cordis.patch.yml)。
3
+ * 数据源 = ctx.plugins.listInstalled()(插件清单)+ 两个 patch 层(home 与 profile cordis.patch.yml)。
4
4
  *
5
5
  * 安全不变量:绝不打包插件二进制;导入走 DSH 官方机制(dsh plugin CLI → needsRestart 提示)。
6
- * patch 行导入(用户自定义行:启用/禁用/插入)写回 cordis.patch.yml,同样 needsRestart。
6
+ * patch 行导入(用户自定义行:启用/禁用/插入)写回该行自带的层(home profile),同样 needsRestart。
7
7
  *
8
8
  * T1(本地源插件迁移):`link:` / `file:` 来源的插件指向本机路径,换机后必然不可达
9
9
  * (曾导致插件被静默丢失)。导出时经注入的 `localPack` 执行 `npm pack`,把 tarball 作为
@@ -17,8 +17,8 @@ import { isLocalPluginSpec, isPackedLocalSpec, LOCAL_PLUGIN_DIR } from '../core/
17
17
  import { msgOf, zhMsg } from '../core/messages.js';
18
18
  import { isPathSafe, normalizePath } from '../utils/paths.js';
19
19
  import { PLUGIN_PATCH_REF_PREFIX } from '../core/backup.js';
20
+ import { HOME_PATCH_FILE, PROFILE_PATCH_FILE, patchLayerKey, parsePatchLayerKey } from '../core/patch-layers.js';
20
21
  import { parsePnpmPatchedDependencies, sanitizePnpmWorkspacePatches } from './pnpm-workspace.js';
21
- export const USER_PATCH_FILE = 'cordis.patch.yml';
22
22
  /** tarball 字节 → base64(零依赖,避免 Buffer 在浏览器侧类型问题) */
23
23
  function bytesToBase64(data) {
24
24
  if (typeof Buffer !== 'undefined')
@@ -142,14 +142,18 @@ export class PluginsAdapter {
142
142
  catch (err) {
143
143
  warnings.push(msgOf(ctx)('adapter.pluginListReadFailed', { reason: err instanceof Error ? err.message : String(err) }));
144
144
  }
145
+ // 两层都读:home 层(全机偏好)与 profile 层(该 profile 专属)。缺 profile 层文件不是故障
146
+ // (readPatchLines 对不存在的文件返回空数组),只有真读失败才告警。
145
147
  const patch = [];
146
- try {
147
- const lines = await ctx.patchFile.readPatchLines(USER_PATCH_FILE);
148
- for (const l of lines)
149
- patch.push({ file: USER_PATCH_FILE, lineId: l.lineId, raw: l.raw });
150
- }
151
- catch (err) {
152
- warnings.push(msgOf(ctx)('adapter.patchReadFailed', { reason: err instanceof Error ? err.message : String(err) }));
148
+ for (const file of [HOME_PATCH_FILE, PROFILE_PATCH_FILE]) {
149
+ try {
150
+ const lines = await ctx.patchFile.readPatchLines(file);
151
+ for (const l of lines)
152
+ patch.push({ file, lineId: l.lineId, raw: l.raw });
153
+ }
154
+ catch (err) {
155
+ warnings.push(msgOf(ctx)('adapter.patchReadFailed', { reason: err instanceof Error ? err.message : String(err) }));
156
+ }
153
157
  }
154
158
  // pnpm-workspace.yaml(allowBuilds / minimumReleaseAgeExclude 等):随插件分区迁移,
155
159
  // 否则目标 profile 的 pnpm 可能因构建白名单/冷静期拒绝安装插件(§34.17 同款语义)。
@@ -391,17 +395,26 @@ export class PluginsAdapter {
391
395
  }
392
396
  // 用户 patch 行:lineId 唯一键;存在且同 → Skip;存在不同 → Conflict;不存在 → Create。
393
397
  // mcp-client 行与 systemPrompt/planMode 行由 mcp/prompts adapter 管理,此处跳过(避免重复写入覆盖)。
394
- const targetLines = await ctx.target.patchFile.readPatchLines(USER_PATCH_FILE);
398
+ // 逐层读目标端:行自带的 file 决定比对哪一层(只读 home 层会让 profile 层行在目标机
399
+ // 已有同值时被误判成 Create,也会让目标机 profile 层的不同值判不出 Conflict)。
400
+ const targetLines = new Map();
401
+ for (const pl of data.patch) {
402
+ if (!targetLines.has(pl.file)) {
403
+ targetLines.set(pl.file, await ctx.target.patchFile.readPatchLines(pl.file));
404
+ }
405
+ }
395
406
  for (const pl of data.patch) {
396
407
  if (isManagedElsewhere(pl.raw))
397
408
  continue;
398
- const id = `patch:${pl.lineId}`;
399
- const tl = targetLines.find((l) => l.lineId === pl.lineId);
409
+ // 层限定复合键:两层可存在同名 lineId,裸 lineId 无法指向「哪一层的哪一行」。
410
+ const ref = patchLayerKey(pl.file, pl.lineId);
411
+ const id = `patch:${ref}`;
412
+ const tl = targetLines.get(pl.file)?.find((l) => l.lineId === pl.lineId);
400
413
  if (!tl) {
401
414
  items.push({
402
415
  id, kind: 'Create', adapter: 'plugins',
403
416
  description: msg('adapter.patchLineCreate', { lineId: pl.lineId }), severity: 'info',
404
- target: { adapter: 'plugins', ref: pl.lineId },
417
+ target: { adapter: 'plugins', ref },
405
418
  });
406
419
  }
407
420
  else if (isDeepStrictEqual(tl.raw, pl.raw)) {
@@ -411,7 +424,7 @@ export class PluginsAdapter {
411
424
  items.push({
412
425
  id, kind: 'Conflict', adapter: 'plugins',
413
426
  description: msg('adapter.patchLineDiff', { lineId: pl.lineId }), severity: 'warning',
414
- target: { adapter: 'plugins', ref: pl.lineId },
427
+ target: { adapter: 'plugins', ref },
415
428
  });
416
429
  }
417
430
  }
@@ -547,14 +560,17 @@ export class PluginsAdapter {
547
560
  const ref = item.target?.ref;
548
561
  if (!ref)
549
562
  return { ok: false, message: msg('adapter.missingTargetRef') };
563
+ // 复合键解析出层与 lineId:只按 lineId 查找会命中数组里先出现的 home 层行,
564
+ // 把 profile 层的行写进 home 层(回滚同理)。
565
+ const { file, lineId } = parsePatchLayerKey(ref);
550
566
  const data = ctx.sections.get('plugins');
551
- const pl = data?.patch.find((p) => p.lineId === ref);
567
+ const pl = data?.patch.find((p) => p.file === file && p.lineId === lineId);
552
568
  if (!pl)
553
569
  return { ok: false, message: msg('adapter.patchMissing', { ref }) };
554
- await ctx.target.patchFile.applyPatchChanges(pl.file, [
555
- { lineId: ref, raw: pl.raw, action: item.kind === 'Create' ? 'insert' : 'update' },
570
+ await ctx.target.patchFile.applyPatchChanges(file, [
571
+ { lineId, raw: pl.raw, action: item.kind === 'Create' ? 'insert' : 'update' },
556
572
  ]);
557
- return { ok: true, needsRestart: true, message: msg('adapter.patchWritten', { ref }) };
573
+ return { ok: true, needsRestart: true, message: msg('adapter.patchWritten', { ref: lineId }) };
558
574
  }
559
575
  async validate(data, msg = zhMsg) {
560
576
  const issues = [];
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { isDeepStrictEqual } from 'node:util';
13
13
  import { msgOf, zhMsg } from '../core/messages.js';
14
- import { USER_PATCH_FILE } from './plugins.js';
14
+ import { HOME_PATCH_FILE } from '../core/patch-layers.js';
15
15
  /** 从 patch 行 config 中提取 prompt 条目(systemPrompt.persona / planMode.sections[].text) */
16
16
  export function extractPrompts(lines) {
17
17
  const prompts = [];
@@ -108,7 +108,7 @@ export class PromptsAdapter {
108
108
  const warnings = [];
109
109
  let lines = [];
110
110
  try {
111
- lines = await ctx.patchFile.readPatchLines(USER_PATCH_FILE);
111
+ lines = await ctx.patchFile.readPatchLines(HOME_PATCH_FILE);
112
112
  }
113
113
  catch (err) {
114
114
  warnings.push(msgOf(ctx)('adapter.patchReadFailedPrompts', { reason: err instanceof Error ? err.message : String(err) }));
@@ -124,7 +124,7 @@ export class PromptsAdapter {
124
124
  async analyzeImport(data, ctx) {
125
125
  const msg = ctx.msg;
126
126
  const items = [];
127
- const targetLines = await ctx.target.patchFile.readPatchLines(USER_PATCH_FILE);
127
+ const targetLines = await ctx.target.patchFile.readPatchLines(HOME_PATCH_FILE);
128
128
  const targetPrompts = extractPrompts(targetLines);
129
129
  for (const p of data.prompts) {
130
130
  const id = p.id;
@@ -191,18 +191,18 @@ export class PromptsAdapter {
191
191
  // Create:目标无来源行 → 用记录的行名重建 patch 行(insert)
192
192
  if (item.kind === 'Create') {
193
193
  const raw = buildPromptLine(ref, prompt);
194
- await ctx.target.patchFile.applyPatchChanges(USER_PATCH_FILE, [
194
+ await ctx.target.patchFile.applyPatchChanges(HOME_PATCH_FILE, [
195
195
  { lineId: ref, raw, action: 'insert' },
196
196
  ]);
197
197
  return { ok: true, needsRestart: true, message: msg('adapter.promptCreated', { name: prompt.name, ref }) };
198
198
  }
199
199
  // Update / Conflict(useImported):合并进目标行 config
200
- const lines = await ctx.target.patchFile.readPatchLines(USER_PATCH_FILE);
200
+ const lines = await ctx.target.patchFile.readPatchLines(HOME_PATCH_FILE);
201
201
  const line = lines.find((l) => l.lineId === ref);
202
202
  if (!line)
203
203
  return { ok: false, message: msg('adapter.patchLineMissing', { ref }) };
204
204
  const newRaw = mergePromptIntoLine(line.raw, prompt);
205
- await ctx.target.patchFile.applyPatchChanges(USER_PATCH_FILE, [
205
+ await ctx.target.patchFile.applyPatchChanges(HOME_PATCH_FILE, [
206
206
  { lineId: ref, raw: newRaw, action: 'update' },
207
207
  ]);
208
208
  return { ok: true, needsRestart: true, message: msg('adapter.promptWritten', { name: prompt.name, ref }) };
@@ -94,6 +94,30 @@ export declare class MemPatch implements PatchFileFacade {
94
94
  action: 'insert' | 'update' | 'remove';
95
95
  }[]): Promise<void>;
96
96
  }
97
+ /**
98
+ * 分层 patch 门面 mock:patch 行按 `file`(层 token)分桶。
99
+ * 单层的 `MemPatch` 无法表达「同名 lineId 分布在两层」——而那正是层寻址契约的核心,
100
+ * 故另立一个按层键控的实现,供 plugins 适配器与快照/回滚的契约测试使用。
101
+ */
102
+ export declare class MemLayeredPatch implements PatchFileFacade {
103
+ files: Map<string, Map<string, {
104
+ lineId: string;
105
+ raw: unknown;
106
+ }>>;
107
+ /** 直接落一行(测试夹具;等价于目标机该层已存在该行)。 */
108
+ set(file: string, lineId: string, raw: unknown): void;
109
+ has(file: string, lineId: string): boolean;
110
+ rawOf(file: string, lineId: string): unknown;
111
+ readPatchLines(file: string): Promise<{
112
+ lineId: string;
113
+ raw: unknown;
114
+ }[]>;
115
+ applyPatchChanges(file: string, changes: {
116
+ lineId: string;
117
+ raw: unknown;
118
+ action: 'insert' | 'update' | 'remove';
119
+ }[]): Promise<void>;
120
+ }
97
121
  export declare class MemSnapshotStore implements SnapshotStore {
98
122
  snapshots: Map<string, Snapshot>;
99
123
  blobs: Map<string, Uint8Array<ArrayBufferLike>>;
@@ -116,6 +140,9 @@ export declare class MockHostContext implements HostContext {
116
140
  patchFile: MemPatch;
117
141
  fs: MemFs;
118
142
  constructor(platform: string, homeDir: string, profile?: string);
143
+ /** 换成按层键控的 patch 门面(层寻址契约测试用)。
144
+ * 字段的声明类型仍是单层的 MemPatch,以免既有夹具的 `.lines` 用法失型。 */
145
+ useLayeredPatch(): MemLayeredPatch;
119
146
  }
120
147
  export declare function makeContext(platform: string, homeDir: string, profile?: string): MockHostContext;
121
148
  /** 构造最小合法 manifest(adapter 单测用;真实 manifest 由 exporter 生成) */
@@ -150,6 +150,39 @@ export class MemPatch {
150
150
  }
151
151
  }
152
152
  }
153
+ /**
154
+ * 分层 patch 门面 mock:patch 行按 `file`(层 token)分桶。
155
+ * 单层的 `MemPatch` 无法表达「同名 lineId 分布在两层」——而那正是层寻址契约的核心,
156
+ * 故另立一个按层键控的实现,供 plugins 适配器与快照/回滚的契约测试使用。
157
+ */
158
+ export class MemLayeredPatch {
159
+ files = new Map();
160
+ /** 直接落一行(测试夹具;等价于目标机该层已存在该行)。 */
161
+ set(file, lineId, raw) {
162
+ const layer = this.files.get(file) ?? new Map();
163
+ layer.set(lineId, { lineId, raw });
164
+ this.files.set(file, layer);
165
+ }
166
+ has(file, lineId) {
167
+ return this.files.get(file)?.has(lineId) ?? false;
168
+ }
169
+ rawOf(file, lineId) {
170
+ return this.files.get(file)?.get(lineId)?.raw;
171
+ }
172
+ async readPatchLines(file) {
173
+ return [...(this.files.get(file)?.values() ?? [])];
174
+ }
175
+ async applyPatchChanges(file, changes) {
176
+ const layer = this.files.get(file) ?? new Map();
177
+ this.files.set(file, layer);
178
+ for (const c of changes) {
179
+ if (c.action === 'remove')
180
+ layer.delete(c.lineId);
181
+ else
182
+ layer.set(c.lineId, { lineId: c.lineId, raw: c.raw });
183
+ }
184
+ }
185
+ }
153
186
  export class MemSnapshotStore {
154
187
  snapshots = new Map();
155
188
  blobs = new Map();
@@ -198,6 +231,13 @@ export class MockHostContext {
198
231
  this.fs = new MemFs(homeDir);
199
232
  this.log = createLogger({ level: 'error', sink: () => { } });
200
233
  }
234
+ /** 换成按层键控的 patch 门面(层寻址契约测试用)。
235
+ * 字段的声明类型仍是单层的 MemPatch,以免既有夹具的 `.lines` 用法失型。 */
236
+ useLayeredPatch() {
237
+ const layered = new MemLayeredPatch();
238
+ this.patchFile = layered;
239
+ return layered;
240
+ }
201
241
  }
202
242
  export function makeContext(platform, homeDir, profile) {
203
243
  return new MockHostContext(platform, homeDir, profile);
package/lib/client.d.ts CHANGED
@@ -44,11 +44,9 @@ declare const uiZh: {
44
44
  readonly 'report.security': '安全:';
45
45
  readonly 'report.apiKeysExcluded': 'API 密钥已排除:';
46
46
  readonly 'report.containsSecrets': '包含密钥:';
47
- readonly 'report.encrypted': '已加密:';
48
47
  readonly 'report.redacted': '{count} 个敏感字段已脱敏';
49
48
  readonly 'report.file': '文件:';
50
49
  readonly 'report.yes': '是';
51
- readonly 'report.yesEncrypted': '是(加密)';
52
50
  readonly 'report.no': '否';
53
51
  readonly 'report.importedRestored': '已导入/恢复';
54
52
  readonly 'report.skipped': '跳过';
@@ -160,7 +158,6 @@ declare const uiZh: {
160
158
  readonly 'sync.pushOk': '推送成功(快照 {id})';
161
159
  readonly 'sync.pushPreviewHeadline': '将推送 {total} 个分区({changed} 个有变化)';
162
160
  readonly 'sync.pushPreviewHint': '以上为只读预览,不会写入远端。确认后点击「推送」才真正上传。';
163
- readonly 'sync.pushPreviewEncrypted': '加密快照:载荷将整体加密,各分区相对基线的变化不可比对。';
164
161
  readonly 'sync.pullFailed': '拉取失败';
165
162
  readonly 'sync.pullOk': '远端快照 {id} 差异预览:共 {count} 项变更';
166
163
  readonly 'sync.pullEmpty': '远端快照与本地一致(无变更)';
@@ -293,19 +290,10 @@ type UiTextKey = keyof typeof uiZh;
293
290
  type UiT = (key: UiTextKey, params?: Record<string, string | number>) => string;
294
291
  //#endregion
295
292
  //#region src/client/api.d.ts
296
- /** Host 半健康检查响应(plugin 版本 / DSH 版本 / 平台,用于主页横幅与兼容性说明) */
293
+ /** Host 半状态响应:设置页页脚版本行的两个字段(消费方只有 ConfigManagerSection) */
297
294
  interface ServiceStatus {
298
- ready: boolean;
299
295
  pluginVersion: string;
300
296
  dshVersion: string;
301
- platform: string;
302
- arch: string;
303
- homeDir?: string;
304
- profile?: string;
305
- profileManifestReadable?: boolean;
306
- installedPluginCount?: number;
307
- installedPluginNames?: string[];
308
- bundles?: string[];
309
297
  }
310
298
  /** 携带路由 JSON error 消息的错误类型 */
311
299
  declare class ConfigManagerApiError extends Error {