dsh-config-manager 0.1.73 → 0.1.77
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.
- package/lib/adapters/plugin-data-declared.d.ts +12 -0
- package/lib/adapters/plugin-data-declared.js +57 -0
- package/lib/adapters/plugin-data.d.ts +4 -6
- package/lib/adapters/plugin-data.js +8 -36
- package/lib/adapters/plugins.js +92 -12
- package/lib/client.d.ts +112 -13
- package/lib/client.js +2102 -888
- package/lib/core/messages.d.ts +1 -0
- package/lib/core/messages.js +2 -0
- package/lib/core/rollback-attribution.d.ts +81 -0
- package/lib/core/rollback-attribution.js +236 -0
- package/lib/core/rollback-ledger.d.ts +29 -3
- package/lib/core/rollback-ledger.js +125 -7
- package/lib/core/rollback-merge.d.ts +22 -0
- package/lib/core/rollback-merge.js +203 -0
- package/lib/core/rollback-point.d.ts +335 -3
- package/lib/core/rollback-point.js +1323 -169
- package/lib/core/types.d.ts +18 -0
- package/lib/index.d.ts +26 -3
- package/lib/index.js +129 -16
- package/lib/routes/recovery.js +11 -0
- package/lib/schema/section-registry.js +5 -5
- package/lib/sync/messages.d.ts +1 -0
- package/lib/sync/messages.js +3 -0
- package/lib/sync/sync-engine.js +56 -7
- package/lib/ui/badge-tone.d.ts +16 -0
- package/lib/ui/badge-tone.js +20 -0
- package/lib/ui/i18n.d.ts +5 -5
- package/lib/ui/i18n.js +10 -10
- package/lib/ui/rollback-view.d.ts +95 -0
- package/lib/ui/rollback-view.js +55 -0
- package/lib/ui/selection-model.d.ts +140 -1
- package/lib/ui/selection-model.js +241 -9
- package/lib/utils/bundle-scan.d.ts +5 -3
- package/lib/utils/bundle-scan.js +5 -3
- package/lib/utils/line-diff.d.ts +7 -1
- package/lib/utils/line-diff.js +8 -2
- package/package.json +1 -1
- package/src/adapters/plugin-data-declared.ts +60 -0
- package/src/adapters/plugin-data.ts +7 -33
- package/src/adapters/plugins.ts +98 -13
- package/src/adapters/units.test.ts +26 -0
- package/src/client/chat/ChatEntryView.tsx +179 -49
- package/src/client/chat/RollbackFileIcon.tsx +7 -45
- package/src/client/chat/chat-entry-view.test.ts +440 -1
- package/src/client/chat/chat-entry-view.ts +222 -11
- package/src/client/chat/chat-node-registration.ts +111 -6
- package/src/client/chat/chat-slots.ts +30 -12
- package/src/client/common/ContentPicker.tsx +581 -175
- package/src/client/common/DiffTable.tsx +12 -23
- package/src/client/common/host-primitives.ts +141 -0
- package/src/client/common/ui.tsx +17 -3
- package/src/client/config-manager.module.css +658 -404
- package/src/client/css-scale-guard.test.ts +41 -3
- package/src/client/css-token-guard.test.ts +10 -4
- package/src/client/environment/maintenance/DiskUsageCard.tsx +20 -3
- package/src/client/export/ExportView.tsx +2 -0
- package/src/client/index.ts +22 -4
- package/src/client/library/RestorePlanView.tsx +0 -1
- package/src/client/locales.ts +54 -12
- package/src/client/recovery/RollbackCard.tsx +56 -2
- package/src/client/recovery/RollbackDiffPane.tsx +18 -6
- package/src/client/recovery/RollbackFileChanges.tsx +46 -2
- package/src/client/recovery/recovery-locales.ts +24 -4
- package/src/client/recovery/rollback-file-changes.test.ts +37 -1
- package/src/client/sync/PushPreviewCard.tsx +57 -0
- package/src/client/sync/SyncPanel.tsx +2 -37
- package/src/client/sync/SyncSettingsView.test.ts +3 -0
- package/src/client/sync/sync-push-preview.test.ts +14 -0
- package/src/client/sync/sync-view.ts +9 -1
- package/src/core/messages.ts +2 -0
- package/src/core/rollback-attribution.test.ts +120 -0
- package/src/core/rollback-attribution.ts +267 -0
- package/src/core/rollback-ledger.ts +123 -7
- package/src/core/rollback-merge.test.ts +133 -0
- package/src/core/rollback-merge.ts +213 -0
- package/src/core/rollback-point.test.ts +1004 -17
- package/src/core/rollback-point.ts +1421 -175
- package/src/core/types.ts +19 -0
- package/src/index.facade.test.ts +60 -0
- package/src/index.ts +133 -19
- package/src/routes/recovery.ts +11 -0
- package/src/schema/section-registry.ts +5 -5
- package/src/sync/messages.ts +3 -0
- package/src/sync/sync-engine.ts +55 -7
- package/src/sync/sync-preview-push.test.ts +158 -0
- package/src/ui/badge-tone.test.ts +23 -0
- package/src/ui/badge-tone.ts +27 -0
- package/src/ui/i18n.ts +10 -10
- package/src/ui/rollback-view.test.ts +27 -0
- package/src/ui/rollback-view.ts +139 -0
- package/src/ui/selection-model.test.ts +240 -2
- package/src/ui/selection-model.ts +349 -1
- package/src/utils/bundle-scan.ts +5 -3
- package/src/utils/line-diff.test.ts +12 -0
- package/src/utils/line-diff.ts +9 -3
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PluginDataSource } from '../core/plugin-data-registry.ts';
|
|
2
|
+
import type { HostContext } from '../core/types.ts';
|
|
3
|
+
/** 单次扫描最多读取多少个已安装插件的 manifest(防御性上限:profile 依赖数量异常时不无限读) */
|
|
4
|
+
export declare const MAX_DECLARED_PACKAGES = 300;
|
|
5
|
+
/**
|
|
6
|
+
* 解析声明时用的 profile 目录名。
|
|
7
|
+
*
|
|
8
|
+
* 与 DSH 的 profile 解析同口径:空 / undefined = \`web\`(与改造前的私有实现一致)。
|
|
9
|
+
*/
|
|
10
|
+
export declare function declaredProfileName(profile: string | undefined): string;
|
|
11
|
+
/** 扫描本机已安装插件自述的数据目录声明(零副作用;读不到的一律跳过)。 */
|
|
12
|
+
export declare function scanDeclaredDataSources(ctx: HostContext, maxPackages?: number): Promise<PluginDataSource[]>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「已安装插件自述数据目录(dsh.dataDirs)」的**单一扫描实现**。
|
|
3
|
+
*
|
|
4
|
+
* 为什么单独一个模块:这份扫描有两个消费者 ——
|
|
5
|
+
* - \`pluginData\` / \`pluginAssets\` adapter(决定要收集哪些区域);
|
|
6
|
+
* - \`plugins\` adapter(导出选择器「按插件看」需要知道**每个插件自己声明的数据区域**,
|
|
7
|
+
* 才能在不读任何数据目录的前提下画出树骨架)。
|
|
8
|
+
* 两处各写一份必然漂移(同一条铁律:同一事实只有一份实现)。
|
|
9
|
+
*
|
|
10
|
+
* 语义(与改造前 plugin-data.ts 的私有实现逐字一致):
|
|
11
|
+
* - 读的是 **profile 实际安装**的那份 manifest(\`profiles/<profile>/node_modules/<pkg>/package.json\`),
|
|
12
|
+
* 不是全局目录 —— 同一个包在不同 profile 可以是不同版本,声明必须取本 profile 装的;
|
|
13
|
+
* - 读不到(未安装 / 无声明 / 形状非法)一律跳过:本机制是**附加**信息,绝不因为一个包读不出来
|
|
14
|
+
* 就影响内置来源或整次导出;
|
|
15
|
+
* - 本函数**不缓存**(缓存策略由调用方决定:pluginData 有实例级 TTL 缓存,plugins 有模块级缓存)。
|
|
16
|
+
*/
|
|
17
|
+
import { parseDeclaredDataDirs } from '../core/plugin-data-registry.js';
|
|
18
|
+
/** 单次扫描最多读取多少个已安装插件的 manifest(防御性上限:profile 依赖数量异常时不无限读) */
|
|
19
|
+
export const MAX_DECLARED_PACKAGES = 300;
|
|
20
|
+
/**
|
|
21
|
+
* 解析声明时用的 profile 目录名。
|
|
22
|
+
*
|
|
23
|
+
* 与 DSH 的 profile 解析同口径:空 / undefined = \`web\`(与改造前的私有实现一致)。
|
|
24
|
+
*/
|
|
25
|
+
export function declaredProfileName(profile) {
|
|
26
|
+
return profile !== undefined && profile !== '' ? profile : 'web';
|
|
27
|
+
}
|
|
28
|
+
/** 扫描本机已安装插件自述的数据目录声明(零副作用;读不到的一律跳过)。 */
|
|
29
|
+
export async function scanDeclaredDataSources(ctx, maxPackages = MAX_DECLARED_PACKAGES) {
|
|
30
|
+
const base = 'profiles/' + declaredProfileName(ctx.profile);
|
|
31
|
+
const out = [];
|
|
32
|
+
let deps = [];
|
|
33
|
+
try {
|
|
34
|
+
const raw = await ctx.fs.readFile(base + '/package.json');
|
|
35
|
+
const parsed = JSON.parse(new TextDecoder().decode(raw));
|
|
36
|
+
const dep = parsed?.dependencies;
|
|
37
|
+
if (dep !== null && typeof dep === 'object')
|
|
38
|
+
deps = Object.keys(dep).slice(0, maxPackages);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
deps = [];
|
|
42
|
+
}
|
|
43
|
+
for (const name of deps) {
|
|
44
|
+
try {
|
|
45
|
+
const raw = await ctx.fs.readFile(base + '/node_modules/' + name + '/package.json');
|
|
46
|
+
const manifest = JSON.parse(new TextDecoder().decode(raw));
|
|
47
|
+
const source = parseDeclaredDataDirs(name, manifest);
|
|
48
|
+
if (source !== null)
|
|
49
|
+
out.push(source);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// 未安装 / 无 manifest → 无声明,跳过
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=plugin-data-declared.js.map
|
|
@@ -47,13 +47,11 @@ export declare class PluginDataAdapter extends FileCollectionAdapter {
|
|
|
47
47
|
private allowedEntries;
|
|
48
48
|
private allSources;
|
|
49
49
|
/**
|
|
50
|
-
* 已安装插件的自述声明(dsh.dataDirs
|
|
50
|
+
* 已安装插件的自述声明(dsh.dataDirs)—— 本方法只做**缓存**,扫描实现见 plugin-data-declared.ts。
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
* 下(pnpm
|
|
54
|
-
*
|
|
55
|
-
* 读不到(未安装 / 无声明 / 形状非法)一律跳过:本机制是**附加**信息,绝不因为一个包读不出来
|
|
56
|
-
* 就影响内置来源或整次导出。
|
|
52
|
+
* 为什么缓存必须带 ctx 键(homeDir + profile):DSH 的插件装在 \`profiles/<profile>/node_modules\`
|
|
53
|
+
* 下(pnpm 隔离布局),而「这台机器装了某插件」不代表另一台装了 —— 缓存若按实例共享,
|
|
54
|
+
* 导入到「没装该插件」的目标机时会拿源机的声明去放行写入,白名单就变成「谁先导入谁说了算」。
|
|
57
55
|
*/
|
|
58
56
|
private declaredSources;
|
|
59
57
|
/**
|
|
@@ -30,13 +30,12 @@ import { normalizePath } from '../utils/paths.js';
|
|
|
30
30
|
import { engineMsg } from './messages.js';
|
|
31
31
|
import { listFilesDetailed } from './link-report.js';
|
|
32
32
|
import { sectionMeta } from '../schema/section-registry.js';
|
|
33
|
-
import { BUILTIN_PLUGIN_DATA_SOURCES, areaOfPath, areasForSection, isExcludedRel, normalizeHomeRel,
|
|
33
|
+
import { BUILTIN_PLUGIN_DATA_SOURCES, areaOfPath, areasForSection, isExcludedRel, normalizeHomeRel, } from '../core/plugin-data-registry.js';
|
|
34
|
+
import { declaredProfileName, scanDeclaredDataSources } from './plugin-data-declared.js';
|
|
34
35
|
import { osHomeDirOf } from '../core/backup.js';
|
|
35
36
|
import { FileCollectionAdapter } from './file-collection.js';
|
|
36
37
|
/** 声明来源缓存的 TTL(毫秒):一次导出/导入/执行里会多次解析,逐次读 15+ 个 package.json 是净开销 */
|
|
37
38
|
const DECLARED_TTL_MS = 5_000;
|
|
38
|
-
/** 单次解析里最多读取多少个已安装插件的 manifest(防御性上限:profile 依赖数量异常时不无限读) */
|
|
39
|
-
const MAX_DECLARED_PACKAGES = 300;
|
|
40
39
|
export class PluginDataAdapter extends FileCollectionAdapter {
|
|
41
40
|
id;
|
|
42
41
|
displayName;
|
|
@@ -75,48 +74,21 @@ export class PluginDataAdapter extends FileCollectionAdapter {
|
|
|
75
74
|
return [...this.builtin, ...declared];
|
|
76
75
|
}
|
|
77
76
|
/**
|
|
78
|
-
* 已安装插件的自述声明(dsh.dataDirs
|
|
77
|
+
* 已安装插件的自述声明(dsh.dataDirs)—— 本方法只做**缓存**,扫描实现见 plugin-data-declared.ts。
|
|
79
78
|
*
|
|
80
|
-
*
|
|
81
|
-
* 下(pnpm
|
|
82
|
-
*
|
|
83
|
-
* 读不到(未安装 / 无声明 / 形状非法)一律跳过:本机制是**附加**信息,绝不因为一个包读不出来
|
|
84
|
-
* 就影响内置来源或整次导出。
|
|
79
|
+
* 为什么缓存必须带 ctx 键(homeDir + profile):DSH 的插件装在 \`profiles/<profile>/node_modules\`
|
|
80
|
+
* 下(pnpm 隔离布局),而「这台机器装了某插件」不代表另一台装了 —— 缓存若按实例共享,
|
|
81
|
+
* 导入到「没装该插件」的目标机时会拿源机的声明去放行写入,白名单就变成「谁先导入谁说了算」。
|
|
85
82
|
*/
|
|
86
83
|
async declaredSources(ctx) {
|
|
87
84
|
const now = Date.now();
|
|
88
|
-
const
|
|
89
|
-
const memoKey = ctx.homeDir + '|' + profile;
|
|
85
|
+
const memoKey = ctx.homeDir + '|' + declaredProfileName(ctx.profile);
|
|
90
86
|
if (this.declaredMemo !== undefined
|
|
91
87
|
&& this.declaredMemo.key === memoKey
|
|
92
88
|
&& now - this.declaredMemo.at < this.declaredTtlMs) {
|
|
93
89
|
return this.declaredMemo.sources;
|
|
94
90
|
}
|
|
95
|
-
const
|
|
96
|
-
const out = [];
|
|
97
|
-
let deps = [];
|
|
98
|
-
try {
|
|
99
|
-
const raw = await ctx.fs.readFile(base + '/package.json');
|
|
100
|
-
const parsed = JSON.parse(new TextDecoder().decode(raw));
|
|
101
|
-
const dep = parsed?.dependencies;
|
|
102
|
-
if (dep !== null && typeof dep === 'object')
|
|
103
|
-
deps = Object.keys(dep).slice(0, MAX_DECLARED_PACKAGES);
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
deps = [];
|
|
107
|
-
}
|
|
108
|
-
for (const name of deps) {
|
|
109
|
-
try {
|
|
110
|
-
const raw = await ctx.fs.readFile(base + '/node_modules/' + name + '/package.json');
|
|
111
|
-
const manifest = JSON.parse(new TextDecoder().decode(raw));
|
|
112
|
-
const source = parseDeclaredDataDirs(name, manifest);
|
|
113
|
-
if (source !== null)
|
|
114
|
-
out.push(source);
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
// 未安装 / 无 manifest → 无声明,跳过
|
|
118
|
-
}
|
|
119
|
-
}
|
|
91
|
+
const out = await scanDeclaredDataSources(ctx);
|
|
120
92
|
this.declaredMemo = { key: memoKey, at: now, sources: out };
|
|
121
93
|
return out;
|
|
122
94
|
}
|
package/lib/adapters/plugins.js
CHANGED
|
@@ -23,6 +23,8 @@ import { isENOENT } from '../utils/guards.js';
|
|
|
23
23
|
import { PLUGIN_PATCH_REF_PREFIX } from '../core/backup.js';
|
|
24
24
|
import { readEffectivePatchLines, resolveWriteLayer, USER_PATCH_FILE } from '../core/patch-layers.js';
|
|
25
25
|
import { parsePnpmPatchedDependencies, sanitizePnpmWorkspacePatches } from './pnpm-workspace.js';
|
|
26
|
+
import { BUILTIN_PLUGIN_DATA_SOURCES, areasForSection } from '../core/plugin-data-registry.js';
|
|
27
|
+
import { declaredProfileName, scanDeclaredDataSources } from './plugin-data-declared.js';
|
|
26
28
|
import { sectionMeta } from '../schema/section-registry.js';
|
|
27
29
|
import { validateJsonSection } from './json-section.js';
|
|
28
30
|
/**
|
|
@@ -126,6 +128,66 @@ function entriesOfRaw(raw) {
|
|
|
126
128
|
* 导出产物(真实 base64)走另一条分支,行为逐字不变。
|
|
127
129
|
*/
|
|
128
130
|
const previewTarballBytes = new WeakMap();
|
|
131
|
+
/**
|
|
132
|
+
* 「插件包名 → 它声明的数据区域」映射(只有 \`plugin:<包名>\` 单元用;导出选择器「按插件看」的树骨架)。
|
|
133
|
+
*
|
|
134
|
+
* 为什么挂 WeakMap 而不是给 PluginsSection 加字段:这是**展示用旁路信息** —— 既不是备份格式的
|
|
135
|
+
* 一部分(schema 校验与市场防线都不该看见它),也与「导出真的带什么」无关;挂在载荷对象上随对象
|
|
136
|
+
* 一起回收,载荷本身逐字节不变(与 previewTarballBytes 同一套理由)。
|
|
137
|
+
*/
|
|
138
|
+
const relatedAreasByData = new WeakMap();
|
|
139
|
+
/** 内置来源 + 自述声明的扫描缓存(TTL 内的同一 home/profile 只扫一次;/export-preview 一趟会问两次) */
|
|
140
|
+
const declaredSourcesMemo = { key: '', at: 0, sources: [] };
|
|
141
|
+
const DECLARED_MEMO_TTL_MS = 5_000;
|
|
142
|
+
/**
|
|
143
|
+
* 本机可归属的插件数据来源 = 内置来源表 + 已安装插件自述声明。
|
|
144
|
+
*
|
|
145
|
+
* 缓存键必须带 homeDir + profile:同一台机器换 profile 就是另一批已安装插件,拿旧声明画树
|
|
146
|
+
* 会让用户看到不属于当前 profile 的数据区域。
|
|
147
|
+
*/
|
|
148
|
+
async function pluginDataSources(ctx) {
|
|
149
|
+
const key = ctx.homeDir + '|' + declaredProfileName(ctx.profile);
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
if (declaredSourcesMemo.key === key && now - declaredSourcesMemo.at < DECLARED_MEMO_TTL_MS) {
|
|
152
|
+
return declaredSourcesMemo.sources;
|
|
153
|
+
}
|
|
154
|
+
const sources = [...BUILTIN_PLUGIN_DATA_SOURCES, ...await scanDeclaredDataSources(ctx)];
|
|
155
|
+
declaredSourcesMemo.key = key;
|
|
156
|
+
declaredSourcesMemo.at = now;
|
|
157
|
+
declaredSourcesMemo.sources = sources;
|
|
158
|
+
return sources;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 包名 → 该插件的数据区域引用。
|
|
162
|
+
*
|
|
163
|
+
* 口径与 pluginData / pluginAssets 的收集面**同源**(同一个 areasForSection):role=config 进
|
|
164
|
+
* pluginData、role=assets 进 pluginAssets;role=cache 的区域不属于任何分区,因此**绝不进树**
|
|
165
|
+
* (缓存是可再生产物,列出来只会诱导用户把它带走)。
|
|
166
|
+
*
|
|
167
|
+
* 只产出「注册表说它应该在哪儿」,不代表磁盘上真有 —— 界面必须把两者分开渲染。
|
|
168
|
+
*/
|
|
169
|
+
async function relatedAreasOf(ctx) {
|
|
170
|
+
const sources = await pluginDataSources(ctx);
|
|
171
|
+
const map = new Map();
|
|
172
|
+
for (const source of sources) {
|
|
173
|
+
const refs = [];
|
|
174
|
+
for (const section of ['pluginData', 'pluginAssets']) {
|
|
175
|
+
for (const entry of areasForSection([source], section)) {
|
|
176
|
+
refs.push({ section, relPath: entry.area.relPath, role: entry.area.role === 'assets' ? 'assets' : 'config' });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (refs.length === 0)
|
|
180
|
+
continue;
|
|
181
|
+
for (const pkg of source.packages) {
|
|
182
|
+
const cur = map.get(pkg);
|
|
183
|
+
if (cur === undefined)
|
|
184
|
+
map.set(pkg, [...refs]);
|
|
185
|
+
else
|
|
186
|
+
cur.push(...refs);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return map;
|
|
190
|
+
}
|
|
129
191
|
/**
|
|
130
192
|
* 零 spawn 推导本地源插件的体积(预览专用)。
|
|
131
193
|
*
|
|
@@ -179,7 +241,7 @@ async function measureLocalSources(plugins, ctx) {
|
|
|
179
241
|
* `tarballBytes` = 预览路径登记到的本地源体积(见 measureLocalSources,0 = 体积未知但仍随包携带);
|
|
180
242
|
* 缺省时体积与文件数完全按导出产物里的 base64 计算 —— 与改造前逐字一致。
|
|
181
243
|
*/
|
|
182
|
-
function unitsOf(data, tarballBytes) {
|
|
244
|
+
function unitsOf(data, tarballBytes, related) {
|
|
183
245
|
const wsId = 'plugins:pnpm-workspace';
|
|
184
246
|
const patchFileIds = (data.patchFiles ?? []).map((pf) => `plugins:patch:${normalizePath(pf.relativePath)}`);
|
|
185
247
|
const group = [wsId, ...patchFileIds];
|
|
@@ -187,6 +249,7 @@ function unitsOf(data, tarballBytes) {
|
|
|
187
249
|
for (const pl of data.plugins) {
|
|
188
250
|
const tarball = tarballOf(data, pl.name);
|
|
189
251
|
const measured = tarballBytes?.get(pl.name);
|
|
252
|
+
const areas = related?.get(pl.name);
|
|
190
253
|
units.push({
|
|
191
254
|
id: `plugin:${pl.name}`,
|
|
192
255
|
label: pl.name,
|
|
@@ -194,6 +257,8 @@ function unitsOf(data, tarballBytes) {
|
|
|
194
257
|
// base64 长度 → 原始字节数(仅用于展示体积,不求精确);预览路径没有字节,用 stat 量到的源体积
|
|
195
258
|
sizeBytes: tarball !== undefined ? Math.floor((tarball.base64.length * 3) / 4) : measured ?? 0,
|
|
196
259
|
...(tarball !== undefined || measured !== undefined ? { fileCount: 1 } : {}),
|
|
260
|
+
// 「按插件看」的树骨架:该插件声明的数据区域(零数据目录 I/O)
|
|
261
|
+
...(areas !== undefined && areas.length > 0 ? { relatedAreas: areas } : {}),
|
|
197
262
|
});
|
|
198
263
|
}
|
|
199
264
|
for (const line of data.patch) {
|
|
@@ -260,7 +325,7 @@ export class PluginsAdapter {
|
|
|
260
325
|
section: collected,
|
|
261
326
|
// 与 src/index.ts 对 JSON 分区现算体积的口径一致(utf8 字节数)
|
|
262
327
|
sizeBytes: new TextEncoder().encode(stringifyJsonSafe(data)).length,
|
|
263
|
-
items: unitsOf(data, tarballBytes),
|
|
328
|
+
items: unitsOf(data, tarballBytes, relatedAreasByData.get(data)),
|
|
264
329
|
};
|
|
265
330
|
}
|
|
266
331
|
/**
|
|
@@ -401,16 +466,30 @@ export class PluginsAdapter {
|
|
|
401
466
|
const alive = new Set(keptPlugins.map((pl) => pl.name));
|
|
402
467
|
keptTarballs = localTarballs?.filter((t) => alive.has(t.packageName));
|
|
403
468
|
}
|
|
469
|
+
const data = {
|
|
470
|
+
version: 1,
|
|
471
|
+
plugins: keptPlugins,
|
|
472
|
+
patch: keptPatch,
|
|
473
|
+
pnpmWorkspace: keptWorkspace,
|
|
474
|
+
...(keptTarballs !== undefined ? { localTarballs: keptTarballs } : {}),
|
|
475
|
+
...(keptPatchFiles !== undefined ? { patchFiles: keptPatchFiles } : {}),
|
|
476
|
+
};
|
|
477
|
+
/**
|
|
478
|
+
* 「按插件看」的树骨架(导出选择器):登记每个插件声明的数据区域。
|
|
479
|
+
* 只登记「至少有一个区域」的插件 —— 其余插件单元保持无该字段,界面据此不显示空壳节点。
|
|
480
|
+
* 归属信息是**附加**信息:扫描失败不得影响导出/预览本身(与 pluginData 同一纪律)。
|
|
481
|
+
*/
|
|
482
|
+
try {
|
|
483
|
+
const related = await relatedAreasOf(ctx);
|
|
484
|
+
if (related.size > 0)
|
|
485
|
+
relatedAreasByData.set(data, related);
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
// 读不到声明就不登记(界面按「没有已知关联」渲染,绝不猜)
|
|
489
|
+
}
|
|
404
490
|
return {
|
|
405
491
|
sectionId: 'plugins',
|
|
406
|
-
data
|
|
407
|
-
version: 1,
|
|
408
|
-
plugins: keptPlugins,
|
|
409
|
-
patch: keptPatch,
|
|
410
|
-
pnpmWorkspace: keptWorkspace,
|
|
411
|
-
...(keptTarballs !== undefined ? { localTarballs: keptTarballs } : {}),
|
|
412
|
-
...(keptPatchFiles !== undefined ? { patchFiles: keptPatchFiles } : {}),
|
|
413
|
-
},
|
|
492
|
+
data,
|
|
414
493
|
counts: {
|
|
415
494
|
plugins: keptPlugins.length,
|
|
416
495
|
patchLines: keptPatch.length,
|
|
@@ -429,8 +508,9 @@ export class PluginsAdapter {
|
|
|
429
508
|
* 由选择模型保证勾选/取消同步。
|
|
430
509
|
*/
|
|
431
510
|
listUnits(section) {
|
|
432
|
-
// 预览产物没有 tarball 字节,体积来自 measureLocalSources 挂上的统计(导出产物走 base64
|
|
433
|
-
|
|
511
|
+
// 预览产物没有 tarball 字节,体积来自 measureLocalSources 挂上的统计(导出产物走 base64 分支);
|
|
512
|
+
// 「按插件看」的数据区域骨架同样由 collect() 登记在载荷对象上(两条路径逐字一致,units.test 钉住)
|
|
513
|
+
return unitsOf(section.data, previewTarballBytes.get(section.data), relatedAreasByData.get(section.data));
|
|
434
514
|
}
|
|
435
515
|
/**
|
|
436
516
|
* 导入后目标机**可能**存在的 patch 文件集合(相对 profile 目录)——issue #35。
|
package/lib/client.d.ts
CHANGED
|
@@ -185,6 +185,24 @@ interface ExportUnit {
|
|
|
185
185
|
* 只下发**事实**,不下发文案 —— 界面按当前语言显示(与 DSH 同口径),绝不用 uuid 冒充标题。
|
|
186
186
|
*/
|
|
187
187
|
blank?: boolean;
|
|
188
|
+
/**
|
|
189
|
+
* 该插件**声明的第三方数据区域**(只有 \`plugin:<包名>\` 单元可能有值;导出选择器「按插件看」用)。
|
|
190
|
+
*
|
|
191
|
+
* 用途:选择器要在**不读任何数据目录**的前提下画出「插件 → 数据目录 / 大文件」的树骨架 ——
|
|
192
|
+
* 区域清单来自来源注册表(内置来源表 + 插件自述 dsh.dataDirs),是静态知识,零额外 I/O;
|
|
193
|
+
* 体积与「实际有没有内容」等该分区的清单到货后再填。
|
|
194
|
+
*
|
|
195
|
+
* 语义:**纯展示**,不参与勾选/导出契约(契约只认 id)。这里是「注册表说它应该在哪儿」,
|
|
196
|
+
* **不是**「磁盘上确实有」—— 界面必须把两者分开渲染(unread / 无内容 / 已读到)。
|
|
197
|
+
*/
|
|
198
|
+
relatedAreas?: readonly PluginAreaRef[];
|
|
199
|
+
}
|
|
200
|
+
/** 插件数据区域引用(pluginData / pluginAssets;只读展示用,不参与勾选契约)。 */
|
|
201
|
+
interface PluginAreaRef {
|
|
202
|
+
section: 'pluginData' | 'pluginAssets';
|
|
203
|
+
/** home 相对 POSIX 路径 —— 就是该分区单元的 id 后缀(\`pluginData:.mnemon/runtime\` 的末段) */
|
|
204
|
+
relPath: string;
|
|
205
|
+
role: 'config' | 'assets';
|
|
188
206
|
}
|
|
189
207
|
type PlanItemKind = 'Create' | 'Update' | 'Skip' | 'Conflict' | 'Install' | 'MissingSecret' | 'MissingDependency' | 'PathMapping' | 'Warning' | 'Error';
|
|
190
208
|
type ItemResolution = 'keepCurrent' | 'useImported' | 'review';
|
|
@@ -647,6 +665,12 @@ interface SnapshotMeta {
|
|
|
647
665
|
* (maxLines / maxD)时**降级为整块替换**(全删 + 全增),保证任何输入都有确定
|
|
648
666
|
* 耗时的输出 —— 大文件不会卡死宿主。
|
|
649
667
|
*
|
|
668
|
+
* 预算怎么定(2026-10-15 真机实测,本仓 rollback-point.ts 的一对版本):
|
|
669
|
+
* · 时间是 (N+M)·D 量级:D=4000、两侧合计 ~7500 行时 ≈ 70 ms(D=8000 ≈ 115 ms);
|
|
670
|
+
* · 内存是 O(D²)·4B(回溯要逐轮留 V 快照):D=2000 时 RSS +~30 MB、D=4000 +~100 MB、D=8000 +~175 MB。
|
|
671
|
+
* ⇒ 缺省取 2000(覆盖「大文件只改了几处」这一大类);单文件按需查看的调用方可以往上调,
|
|
672
|
+
* 到 4000 仍是一次点击可接受的开销;再大就该换线性空间算法,而不是继续加预算。
|
|
673
|
+
*
|
|
650
674
|
* 行为约定:
|
|
651
675
|
* - 行尾 \r\n 归一为 \n;文本末尾的换行不产生"空行"(行数 = 换行符语义)。
|
|
652
676
|
* - 仅空白差异同样是差异(不做 ignoreWhitespace)。
|
|
@@ -1523,7 +1547,7 @@ declare const uiZh: {
|
|
|
1523
1547
|
readonly 'error.downloadFailed': '下载失败:HTTP {status}';
|
|
1524
1548
|
readonly 'error.exportTimeout': '导出超时({minutes} 分钟):导出过程未完成,请重试;若反复超时请检查 DSH 状态';
|
|
1525
1549
|
readonly 'error.syncTimeout': '同步请求超时({minutes} 分钟):请检查网络与仓库可达性后重试';
|
|
1526
|
-
readonly 'error.recoveryTimeout': '恢复请求超时({minutes}
|
|
1550
|
+
readonly 'error.recoveryTimeout': '恢复请求超时({minutes} 分钟):过程可能仍在后台进行,请先刷新预览确认;确实未完成再重试';
|
|
1527
1551
|
readonly 'error.settingsConflict.title': '配置已被并发修改';
|
|
1528
1552
|
readonly 'error.settingsConflict.action': '目标 DSH 的该配置在导入期间被其他进程修改,为避免覆盖请重新导出或手动核对后再试。';
|
|
1529
1553
|
readonly 'error.notConfirmed.title': '导入未确认';
|
|
@@ -1948,10 +1972,10 @@ declare const uiZh: {
|
|
|
1948
1972
|
readonly 'export.category.settings': 'DSH 全局设置(namespace 分区,redacted)';
|
|
1949
1973
|
readonly 'export.category.providers': 'LLM Provider / Model / 默认模型 / BaseURL';
|
|
1950
1974
|
readonly 'export.category.plugins': '已安装插件清单与启用状态(不含二进制)';
|
|
1951
|
-
readonly 'export.category.pluginFiles': '
|
|
1952
|
-
readonly 'export.category.self': '
|
|
1953
|
-
readonly 'export.category.pluginData': '
|
|
1954
|
-
readonly 'export.category.pluginAssets': '
|
|
1975
|
+
readonly 'export.category.pluginFiles': '插件放在 ~/.dsh 根下的配置文件(白名单 + plugin-config/ 目录,整文件复制)';
|
|
1976
|
+
readonly 'export.category.self': '配置管理器自身的设置(同步/自动同步/分区选择/UI 偏好/市场;sync-*.json 等,不含凭据值)';
|
|
1977
|
+
readonly 'export.category.pluginData': '插件在系统主目录下的数据(如 ~/.dsh-wallpaper-engine、~/.mnemon 的配置与状态;可再生成缓存绝不收录)';
|
|
1978
|
+
readonly 'export.category.pluginAssets': '插件的大体积文件(上传的壁纸、本地数据库等;默认不勾选)';
|
|
1955
1979
|
readonly 'export.category.mcp': 'MCP 服务器组合配置(需重启生效)';
|
|
1956
1980
|
readonly 'export.category.prompts': 'System Prompt / Plan Mode 提示';
|
|
1957
1981
|
readonly 'export.category.skills': '用户技能文件(~/.dsh/skills)';
|
|
@@ -4158,6 +4182,23 @@ interface RollbackPreviewFile {
|
|
|
4158
4182
|
removed: number | null;
|
|
4159
4183
|
/** true = 本次没有可信行数(added/removed 均为 null)。**宁可说「不知道」,也绝不谎报行数。** */
|
|
4160
4184
|
truncated: boolean;
|
|
4185
|
+
/**
|
|
4186
|
+
* true = 该文件在留点**之后**是**别的会话**写的(写入归属台账证明;见 core/rollback-attribution.ts)。
|
|
4187
|
+
* 界面据此默认不勾选它(用户仍可勾上)。缺字段 / false = **没有归属证据**,
|
|
4188
|
+
* 而不是「本会话写的」——「未判定」与「确知是本会话」必须分开。
|
|
4189
|
+
*/
|
|
4190
|
+
foreign?: boolean;
|
|
4191
|
+
/** 那些「别的会话」的会话 id(仅当 foreign 为 true) */
|
|
4192
|
+
foreignSessionId?: string;
|
|
4193
|
+
/** 那次写入的时间(毫秒;仅当 foreign 为 true) */
|
|
4194
|
+
foreignAt?: number;
|
|
4195
|
+
/**
|
|
4196
|
+
* true = 这次回退**不会改动它**(与其它对话的改动冲突 / 无法安全合并)。行级统计为 null ——
|
|
4197
|
+
* 「不改动」不是「0 行变化」,界面必须分开说。
|
|
4198
|
+
*/
|
|
4199
|
+
skipped?: boolean;
|
|
4200
|
+
/** 不改动的机器原因(界面映射成字典键,绝不裸渲染) */
|
|
4201
|
+
mergeReason?: string;
|
|
4161
4202
|
}
|
|
4162
4203
|
interface RollbackWorkspacePreview {
|
|
4163
4204
|
/** 将被写回(覆盖)的条目数 */
|
|
@@ -4279,6 +4320,12 @@ interface RollbackFileDiffResult {
|
|
|
4279
4320
|
identical: boolean;
|
|
4280
4321
|
degraded: boolean;
|
|
4281
4322
|
reason?: RollbackFileDiffReason;
|
|
4323
|
+
/** true = 「回退后」一侧显示的是三方合并的结果(不是记录里的原文) */
|
|
4324
|
+
merged?: boolean;
|
|
4325
|
+
/** true = 这次回退不会改动它(冲突 / 无法安全合并)——此时两侧相同 */
|
|
4326
|
+
mergeSkipped?: boolean;
|
|
4327
|
+
/** 不改动的机器原因(界面映射成字典键) */
|
|
4328
|
+
mergeReason?: string;
|
|
4282
4329
|
}
|
|
4283
4330
|
interface RollbackSegmentResult {
|
|
4284
4331
|
state: string;
|
|
@@ -4304,6 +4351,22 @@ interface RollbackSegmentResult {
|
|
|
4304
4351
|
createdRefusedReason?: string | null;
|
|
4305
4352
|
/** 工作区段:实际写回的字节数(宿主回传才有) */
|
|
4306
4353
|
writtenBytes?: number;
|
|
4354
|
+
/** 因归属隔离(B)跳过的记录条目数:该文件在留点后被**别的会话**写过(宿主回传才有) */
|
|
4355
|
+
foreignSkipped?: number;
|
|
4356
|
+
/** 因路径选择(A)跳过的记录条目数(宿主回传才有) */
|
|
4357
|
+
selectionSkipped?: number;
|
|
4358
|
+
/** 三方合并成功、写回「撤销本会话 + 保留对方改动」的条目数(宿主回传才有) */
|
|
4359
|
+
mergedCount?: number;
|
|
4360
|
+
mergedPaths?: string[];
|
|
4361
|
+
/** 冲突:两侧改到同一处 ⇒ 未写(宿主回传才有) */
|
|
4362
|
+
conflictCount?: number;
|
|
4363
|
+
conflictPaths?: string[];
|
|
4364
|
+
/** 无法安全撤销 ⇒ 未写(宿主回传才有) */
|
|
4365
|
+
unavailableCount?: number;
|
|
4366
|
+
unavailablePaths?: string[];
|
|
4367
|
+
/** 写入前发现又被改过(并发)⇒ 未写(宿主回传才有) */
|
|
4368
|
+
concurrentCount?: number;
|
|
4369
|
+
concurrentPaths?: string[];
|
|
4307
4370
|
/** 兼容形态:宿主只回传计数时也算得出来 */
|
|
4308
4371
|
createdAfter?: RollbackCount;
|
|
4309
4372
|
createdAfterTruncated?: boolean;
|
|
@@ -4342,6 +4405,13 @@ interface RollbackRewindOptions {
|
|
|
4342
4405
|
segments?: readonly RollbackState[];
|
|
4343
4406
|
allowPartial?: boolean;
|
|
4344
4407
|
preRewindGuard?: RollbackGuardPolicy;
|
|
4408
|
+
/**
|
|
4409
|
+
* 路径级选择性恢复(2026-10-17 定案 A):只恢复这些路径。
|
|
4410
|
+
*
|
|
4411
|
+
* 缺省 = 全量口径(一字不变)。**空数组是无效输入**(宿主 400)—— 「一个都没勾」由界面拦住,
|
|
4412
|
+
* 绝不让它变成「整次回退静默 noop」。
|
|
4413
|
+
*/
|
|
4414
|
+
onlyPaths?: readonly string[];
|
|
4345
4415
|
}
|
|
4346
4416
|
/** 端口:由 src/client/recovery/recovery-api.ts 实现(Partial 消费,缺失时界面如实说能力缺失)。 */
|
|
4347
4417
|
interface RollbackPointPort {
|
|
@@ -4683,20 +4753,20 @@ declare const zh$4: {
|
|
|
4683
4753
|
readonly 'section.settings': '设置';
|
|
4684
4754
|
readonly 'section.ui': 'UI 偏好';
|
|
4685
4755
|
readonly 'section.providers': 'Provider 与模型';
|
|
4686
|
-
readonly 'section.plugins': '
|
|
4756
|
+
readonly 'section.plugins': '插件清单';
|
|
4687
4757
|
readonly 'section.mcp': 'MCP 服务器';
|
|
4688
4758
|
readonly 'section.prompts': '提示词';
|
|
4689
4759
|
readonly 'section.skills': '技能';
|
|
4690
4760
|
readonly 'section.agentPresets': 'Agent 预设';
|
|
4691
4761
|
readonly 'section.agentInstructions': '全局指令';
|
|
4692
4762
|
readonly 'section.workspaces': '工作区';
|
|
4693
|
-
readonly 'section.pluginFiles': '
|
|
4763
|
+
readonly 'section.pluginFiles': '插件散落配置';
|
|
4694
4764
|
readonly 'section.credentialsStatus': '凭据状态';
|
|
4695
4765
|
readonly 'section.secrets': '密钥';
|
|
4696
4766
|
readonly 'section.sessions': '历史会话';
|
|
4697
|
-
readonly 'section.self': '
|
|
4698
|
-
readonly 'section.pluginData': '
|
|
4699
|
-
readonly 'section.pluginAssets': '
|
|
4767
|
+
readonly 'section.self': '配置管理器设置';
|
|
4768
|
+
readonly 'section.pluginData': '插件数据目录';
|
|
4769
|
+
readonly 'section.pluginAssets': '插件大文件';
|
|
4700
4770
|
readonly 'view.about': '关于';
|
|
4701
4771
|
readonly 'about.snippet.environment': '### 环境 / Environment';
|
|
4702
4772
|
readonly 'about.releaseNotes.rateLimited': 'GitHub API 速率限制(Rate limit exceeded),请稍后再试或直接在 GitHub 上查看。';
|
|
@@ -4836,6 +4906,26 @@ declare const zh$4: {
|
|
|
4836
4906
|
readonly 'picker.sensitive': '敏感';
|
|
4837
4907
|
readonly 'picker.showAll': '显示全部(共 {count} 条)';
|
|
4838
4908
|
readonly 'picker.summaryImport': '将导入 {sections} 个分区 · {units} 个条目';
|
|
4909
|
+
readonly 'picker.pluginRoot': '插件';
|
|
4910
|
+
readonly 'picker.pluginLeafPlugin': '插件清单条目';
|
|
4911
|
+
readonly 'picker.pluginLeafConfig': '数据目录(配置)';
|
|
4912
|
+
readonly 'picker.pluginLeafAssets': '大文件(壁纸 / 数据库)';
|
|
4913
|
+
readonly 'picker.pluginAreaUnread': '未读取';
|
|
4914
|
+
readonly 'picker.pluginAreaAbsent': '无内容';
|
|
4915
|
+
readonly 'picker.pluginViewEmpty': '插件清单还没读到,先读取「插件」分区。';
|
|
4916
|
+
readonly 'picker.bucketPlugins': '插件分区的其他条目(补丁行 / pnpm-workspace.yaml)';
|
|
4917
|
+
readonly 'picker.bucketLooseFiles': '未归属到插件的配置(~/.dsh 根)';
|
|
4918
|
+
readonly 'picker.bucketHint': '这些条目没有可证明的插件归属,不猜;展开可逐条勾选。';
|
|
4919
|
+
readonly 'picker.groupHint.extensions': '这一组里的插件分区合并成一棵按插件分组的树:默认只显示一个「插件」区块,展开它才列出各个插件,插件名下面是它的清单条目、数据目录与大文件。只显示能证明的归属(内置来源表 + 插件自述 dsh.dataDirs)——「插件散落配置」没有包名信息,因此留在「未归属」里;带「设备相关」的分区默认不勾选,跨设备导入时可能不适用。';
|
|
4920
|
+
readonly 'group.general': '通用';
|
|
4921
|
+
readonly 'group.ai': 'AI';
|
|
4922
|
+
readonly 'group.extensions': '插件与扩展';
|
|
4923
|
+
readonly 'group.mcp': 'MCP / 工具';
|
|
4924
|
+
readonly 'group.customization': '定制';
|
|
4925
|
+
readonly 'group.automation': '自动化';
|
|
4926
|
+
readonly 'group.workspace': '工作区';
|
|
4927
|
+
readonly 'group.ui': '界面';
|
|
4928
|
+
readonly 'group.optional': '可选数据';
|
|
4839
4929
|
readonly 'import.nothingSelected': '当前没有勾选任何要导入的内容,请把需要导入的分区或条目勾上。';
|
|
4840
4930
|
readonly 'import.excludedByUser': '本次有 {count} 项被你取消勾选:未导入,也未进快照。';
|
|
4841
4931
|
readonly 'import.consult.hint': '只读分析:先看结论与依据,确认后再选择要导入的内容。';
|
|
@@ -4997,7 +5087,6 @@ declare const zh$4: {
|
|
|
4997
5087
|
readonly 'snapshots.plan.diffEscape': '路径越界,已拒绝读取';
|
|
4998
5088
|
readonly 'snapshots.plan.diffNotApplicable': '该动作不是文件内容变更,无逐行对照';
|
|
4999
5089
|
readonly 'snapshots.plan.diffDegraded': '差异过大,已按整块对比显示';
|
|
5000
|
-
readonly 'snapshots.plan.diffTruncated': '仅显示前 {shown} 行(共 {total} 行)';
|
|
5001
5090
|
readonly 'snapshots.plan.statSkipped.tooLarge': '体积超上限,未统计';
|
|
5002
5091
|
readonly 'snapshots.plan.statSkipped.binary': '二进制';
|
|
5003
5092
|
readonly 'snapshots.plan.statSkipped.budget': '未统计(文件数上限)';
|
|
@@ -6485,7 +6574,7 @@ declare const zh$1: {
|
|
|
6485
6574
|
readonly 'rollback.code.fork-failed': 'fork 子会话失败,会话段未回退';
|
|
6486
6575
|
readonly 'rollback.code.capture-failed': '该态留点失败';
|
|
6487
6576
|
readonly 'rollback.code.capture-truncated': '该态留点到达上限被截断(已如实标注)';
|
|
6488
|
-
readonly 'rollback.code.capture-degraded': '
|
|
6577
|
+
readonly 'rollback.code.capture-degraded': '该态留点不完整(有读不到或期间被改写的内容,已逐条留痕)';
|
|
6489
6578
|
readonly 'rollback.code.no-chunks': '没有给出工作区路径分块';
|
|
6490
6579
|
readonly 'rollback.code.chunk-not-absolute': '工作区分块必须是绝对路径';
|
|
6491
6580
|
readonly 'rollback.code.chunk-not-found': '工作区分块不存在或不可用';
|
|
@@ -6524,6 +6613,14 @@ declare const zh$1: {
|
|
|
6524
6613
|
readonly 'rollback.preview.notice.skipped': '{n} 项在留点时被跳过(未记录;已计入证明名单 ⇒ 不参与删除判据)';
|
|
6525
6614
|
readonly 'rollback.preview.notice.truncated': '该点捕获被上限截断(超单文件上限 / 字节预算 / 文件数预算);删除判据另看证明名单(路径索引)是否完整';
|
|
6526
6615
|
readonly 'rollback.preview.files.title': '将被回退的文件:';
|
|
6616
|
+
readonly 'rollback.preview.select.foreign': '其它对话改过';
|
|
6617
|
+
readonly 'rollback.preview.select.toggle': '「{path}」:勾上则这次回退恢复它';
|
|
6618
|
+
readonly 'rollback.result.foreignSkipped': '{n} 个文件在留点后被其它对话改过,这次没有回退它们';
|
|
6619
|
+
readonly 'rollback.result.merged': '{n} 个文件已合并:撤销本次对话的改动,保留其它对话的改动';
|
|
6620
|
+
readonly 'rollback.result.conflict': '{n} 个文件两侧改到同一处,已保持原样 —— 需要你手工处理';
|
|
6621
|
+
readonly 'rollback.result.unavailable': '{n} 个文件无法安全撤销(缺少中间版本 / 二进制 / 差异过大),已保持原样';
|
|
6622
|
+
readonly 'rollback.result.concurrent': '{n} 个文件在回退过程中又被改过,已保持原样';
|
|
6623
|
+
readonly 'rollback.result.selectionSkipped': '{n} 个文件按你的勾选跳过了';
|
|
6527
6624
|
readonly 'rollback.preview.files.empty': '工作区与该回退点一致,无需恢复';
|
|
6528
6625
|
readonly 'rollback.preview.files.unavailable': '该回退点的工作区态没有可用的记录(原因:{reason}),无需恢复';
|
|
6529
6626
|
readonly 'rollback.preview.files.unavailableSession': '该回退点的工作区态没有可用的记录(原因:{reason});会话仍会回退到该点之前';
|
|
@@ -6533,6 +6630,9 @@ declare const zh$1: {
|
|
|
6533
6630
|
readonly 'rollback.preview.file.removed': '−{n}';
|
|
6534
6631
|
/** 整文件删除(用户 2026-10-12 第 6 条):写「删除」而不是 `+0 −N` —— 那是两件事。 */
|
|
6535
6632
|
readonly 'rollback.preview.file.deleted': '删除';
|
|
6633
|
+
readonly 'rollback.preview.file.skipped': '不会改动(与其它对话的改动冲突,或无法安全合并)';
|
|
6634
|
+
readonly 'rollback.diff.merged': '下面是合并后的结果:撤销本对话的改动、保留其它对话的改动';
|
|
6635
|
+
readonly 'rollback.diff.skipped': '这次回退不会改动它(与其它对话的改动冲突,或无法安全合并)';
|
|
6536
6636
|
readonly 'rollback.preview.file.unknown': '行数未统计';
|
|
6537
6637
|
readonly 'rollback.preview.file.unknownHint': '这些文件的行数没有统计出来(文件过大、不是文本或内容副本读不出来)—— 回退仍会照常覆盖它们。';
|
|
6538
6638
|
readonly 'rollback.diff.viewHint': '查看差异:{path}';
|
|
@@ -6541,7 +6641,6 @@ declare const zh$1: {
|
|
|
6541
6641
|
readonly 'rollback.diff.after': '回退后';
|
|
6542
6642
|
readonly 'rollback.diff.identical': '该文件与回退点内容一致,回退不会改动它';
|
|
6543
6643
|
readonly 'rollback.diff.degraded': '差异规模较大,已按整块替换展示(仍是完整结果,只是不是最小的编辑脚本)';
|
|
6544
|
-
readonly 'rollback.diff.truncated': '只画了前 {shown} 行(共 {total} 行),数据本身是完整的';
|
|
6545
6644
|
readonly 'rollback.diff.reason.missingBlob': '这份内容副本读不出来,无法逐行对照(回退仍会尝试写回它)';
|
|
6546
6645
|
readonly 'rollback.diff.reason.binary': '不是文本文件,没有逐行差异可看';
|
|
6547
6646
|
readonly 'rollback.diff.reason.tooLarge': '文件超过展示上限,没有逐行差异可看(回退仍会照常覆盖它)';
|