dsh-config-manager 0.1.72 → 0.1.73
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/README.md +40 -4
- package/README.zh-CN.md +40 -4
- package/lib/adapters/index.d.ts +1 -0
- package/lib/adapters/index.js +5 -0
- package/lib/adapters/messages.d.ts +3 -0
- package/lib/adapters/messages.js +7 -0
- package/lib/adapters/plugin-data.d.ts +114 -0
- package/lib/adapters/plugin-data.js +309 -0
- package/lib/adapters/self.d.ts +1 -1
- package/lib/adapters/self.js +2 -1
- package/lib/adapters/test-helpers.d.ts +9 -0
- package/lib/adapters/test-helpers.js +14 -0
- package/lib/client.d.ts +95 -6
- package/lib/client.js +10630 -10133
- package/lib/core/analyzer.d.ts +11 -2
- package/lib/core/analyzer.js +31 -8
- package/lib/core/backup-plan.d.ts +10 -0
- package/lib/core/backup-plan.js +21 -3
- package/lib/core/backup.d.ts +20 -1
- package/lib/core/backup.js +48 -3
- package/lib/core/command-rollback.d.ts +6 -2
- package/lib/core/command-rollback.js +6 -2
- package/lib/core/messages-core.d.ts +2 -0
- package/lib/core/messages-core.js +4 -0
- package/lib/core/messages.d.ts +2 -1
- package/lib/core/messages.js +4 -2
- package/lib/core/plugin-data-registry.d.ts +124 -0
- package/lib/core/plugin-data-registry.js +198 -0
- package/lib/core/rollback-gate.d.ts +33 -0
- package/lib/core/rollback-gate.js +57 -0
- package/lib/core/rollback-ledger.d.ts +248 -0
- package/lib/core/rollback-ledger.js +1282 -0
- package/lib/core/rollback-point.d.ts +187 -4
- package/lib/core/rollback-point.js +689 -179
- package/lib/core/rollback.js +6 -4
- package/lib/core/types.d.ts +19 -0
- package/lib/foreign/registry.d.ts +1 -1
- package/lib/foreign/registry.js +1 -1
- package/lib/index.d.ts +5 -1
- package/lib/index.js +83 -13
- package/lib/routes/messages.d.ts +1 -0
- package/lib/routes/messages.js +2 -0
- package/lib/routes/prefs.js +26 -3
- package/lib/routes/recovery.d.ts +21 -0
- package/lib/routes/recovery.js +127 -7
- package/lib/routes/sync.js +11 -1
- package/lib/schema/section-registry.d.ts +1 -1
- package/lib/schema/section-registry.js +32 -3
- package/lib/schema/types.d.ts +1 -1
- package/lib/sync/sync-engine.d.ts +21 -7
- package/lib/sync/sync-engine.js +52 -11
- package/lib/sync/ui-prefs.d.ts +22 -0
- package/lib/sync/ui-prefs.js +28 -0
- package/lib/ui/diff-view.d.ts +10 -0
- package/lib/ui/diff-view.js +17 -0
- package/lib/ui/export-flow.js +2 -0
- package/lib/ui/i18n.d.ts +4 -2
- package/lib/ui/i18n.js +8 -4
- package/lib/ui/rollback-view.d.ts +59 -0
- package/lib/ui/rollback-view.js +14 -0
- package/lib/ui/test-helpers.js +2 -1
- package/package.json +1 -1
- package/src/adapters/index.ts +5 -0
- package/src/adapters/messages.ts +7 -0
- package/src/adapters/plugin-data.test.ts +225 -0
- package/src/adapters/plugin-data.ts +351 -0
- package/src/adapters/self.ts +2 -1
- package/src/adapters/test-helpers.ts +15 -0
- package/src/client/chat/ChatEntryView.tsx +21 -1
- package/src/client/chat/RollbackDiffTab.tsx +49 -2
- package/src/client/chat/RollbackReviewDialog.tsx +20 -4
- package/src/client/chat/chat-point-window.ts +11 -1
- package/src/client/chat/rollback-diff-tab.ts +13 -3
- package/src/client/chat/rollback-entry-view.ts +5 -0
- package/src/client/chat/sidebar-slots.ts +24 -2
- package/src/client/common/DiffTable.tsx +44 -8
- package/src/client/common/client-route-parity.test.ts +6 -0
- package/src/client/common/routes.ts +10 -0
- package/src/client/common/section-labels.test.ts +1 -0
- package/src/client/common/section-labels.ts +2 -0
- package/src/client/config-manager.module.css +113 -10
- package/src/client/index.ts +47 -1
- package/src/client/locales.ts +8 -4
- package/src/client/recovery/RollbackCard.tsx +201 -7
- package/src/client/recovery/RollbackDiffPane.tsx +13 -2
- package/src/client/recovery/recovery-api.ts +28 -0
- package/src/client/recovery/recovery-locales.ts +34 -2
- package/src/client/recovery/rollback-enabled.ts +41 -0
- package/src/client/recovery/rollback-file-changes.test.ts +41 -0
- package/src/client/sync/sync-view.test.ts +1 -1
- package/src/core/analyzer.ts +31 -9
- package/src/core/backup-plan.test.ts +20 -5
- package/src/core/backup-plan.ts +22 -3
- package/src/core/backup.ts +50 -4
- package/src/core/command-rollback.ts +6 -2
- package/src/core/messages-core.ts +4 -0
- package/src/core/messages.ts +4 -2
- package/src/core/plugin-data-registry.test.ts +113 -0
- package/src/core/plugin-data-registry.ts +261 -0
- package/src/core/rollback-gate.test.ts +66 -0
- package/src/core/rollback-gate.ts +61 -0
- package/src/core/rollback-ledger.test.ts +655 -0
- package/src/core/rollback-ledger.ts +1314 -0
- package/src/core/rollback-point.test.ts +274 -24
- package/src/core/rollback-point.ts +745 -160
- package/src/core/rollback.ts +5 -3
- package/src/core/types.ts +19 -0
- package/src/foreign/registry.test.ts +1 -1
- package/src/foreign/registry.ts +1 -1
- package/src/index.ts +73 -10
- package/src/routes/messages.ts +2 -0
- package/src/routes/prefs.ts +31 -2
- package/src/routes/recovery.ts +122 -7
- package/src/routes/sync.ts +15 -1
- package/src/schema/manifest-schema.test.ts +4 -4
- package/src/schema/registry.test.ts +9 -2
- package/src/schema/section-registry.ts +32 -3
- package/src/schema/types.ts +2 -1
- package/src/sync/sync-credentials.test.ts +119 -0
- package/src/sync/sync-engine.ts +51 -11
- package/src/sync/transport.test.ts +1 -0
- package/src/sync/ui-prefs.test.ts +44 -1
- package/src/sync/ui-prefs.ts +39 -0
- package/src/ui/diff-view.test.ts +9 -1
- package/src/ui/diff-view.ts +22 -0
- package/src/ui/export-flow.ts +2 -0
- package/src/ui/i18n.ts +8 -4
- package/src/ui/rollback-view.test.ts +17 -0
- package/src/ui/rollback-view.ts +66 -0
- package/src/ui/test-helpers.ts +2 -1
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pluginData / pluginAssets 分区 adapter(第三方插件数据迁移)。
|
|
3
|
+
*
|
|
4
|
+
* 数据来源 = \`src/core/plugin-data-registry.ts\` 的来源注册表:内置已取证来源 + 已安装插件在
|
|
5
|
+
* 自己 package.json 里声明的 \`dsh.dataDirs\`。每个「区域(area)」在备份里是**一个可勾选单元**,
|
|
6
|
+
* 单元 id = 区域的 home 相对 POSIX 路径(如 \`.dsh-wallpaper-engine/uploads\`)——
|
|
7
|
+
* 用户能在导出选择器里逐区域看到「几个文件 / 多大」,也能只带走配置、不带几百 MB 的上传壁纸。
|
|
8
|
+
*
|
|
9
|
+
* 为什么两个分区而不是一个:
|
|
10
|
+
* - \`pluginData\` = 配置与轻量状态;\`pluginAssets\` = 大体积用户数据(上传的壁纸、本地数据库);
|
|
11
|
+
* - 「默认不勾选大资产」在既有 UI 选择模型里**只有分区级开关**这一条可靠通道(单元级只有
|
|
12
|
+
* 「默认全选 + 稀疏排除」,没有「默认排除」的概念)—— 拆成两个分区后,「可见但默认不勾选」
|
|
13
|
+
* 就是既有的 \`defaultIncluded=false\` 语义,不需要动共享选择模型,也不会与导入/市场两处选择语义打架。
|
|
14
|
+
*
|
|
15
|
+
* 实现:继承 FileCollectionAdapter(baseDir = '',即 home 根;与 agentInstructions 同一形态),
|
|
16
|
+
* 复用它的单元分组 / 单分区字节闸门 / 预览-导出一致性 / 幂等哈希比对;只覆写三处:
|
|
17
|
+
* ① \`listRelPaths()\` —— 清单来自来源注册表(并如实记录被环境变量覆盖而跳过的区域);
|
|
18
|
+
* ② \`unitIdOf()\` —— 单元 = 区域(最长前缀归属);
|
|
19
|
+
* ③ \`analyzeImport() / applyItem()\` —— **写入路径白名单**(见下)。
|
|
20
|
+
*
|
|
21
|
+
* 安全不变量(本分区是全仓唯一「按 home 相对路径任意写」的分区,必须自己守住):
|
|
22
|
+
* - **导入白名单**:只有「本机内置来源的区域」与「本机已安装插件声明的区域」之下的路径才允许写。
|
|
23
|
+
* 恶意/损坏的包想把 \`.credentials.yaml\` 或 \`AGENTS.md\` 塞进来,在这里被判 Error(计划里可见,
|
|
24
|
+
* 执行期不写);applyItem 再查一遍(纵深防御),内层还有基类的 control-plane namespace 拒绝。
|
|
25
|
+
* - **缓存永不收录**:role === 'cache' 的区域不属于任何分区,导出时根本不进清单。
|
|
26
|
+
* - **环境变量覆盖即跳过**:区域声明的 env 存在且解析结果与默认位置不同 → 报码跳过,绝不猜新位置。
|
|
27
|
+
* - **不产生用户可见散文**:告警与计划项文案一律走消息目录(i18n 铁律)。
|
|
28
|
+
*/
|
|
29
|
+
import { normalizePath } from '../utils/paths.js';
|
|
30
|
+
import { engineMsg } from './messages.js';
|
|
31
|
+
import { listFilesDetailed } from './link-report.js';
|
|
32
|
+
import { sectionMeta } from '../schema/section-registry.js';
|
|
33
|
+
import { BUILTIN_PLUGIN_DATA_SOURCES, areaOfPath, areasForSection, isExcludedRel, normalizeHomeRel, parseDeclaredDataDirs, } from '../core/plugin-data-registry.js';
|
|
34
|
+
import { osHomeDirOf } from '../core/backup.js';
|
|
35
|
+
import { FileCollectionAdapter } from './file-collection.js';
|
|
36
|
+
/** 声明来源缓存的 TTL(毫秒):一次导出/导入/执行里会多次解析,逐次读 15+ 个 package.json 是净开销 */
|
|
37
|
+
const DECLARED_TTL_MS = 5_000;
|
|
38
|
+
/** 单次解析里最多读取多少个已安装插件的 manifest(防御性上限:profile 依赖数量异常时不无限读) */
|
|
39
|
+
const MAX_DECLARED_PACKAGES = 300;
|
|
40
|
+
export class PluginDataAdapter extends FileCollectionAdapter {
|
|
41
|
+
id;
|
|
42
|
+
displayName;
|
|
43
|
+
defaultIncluded = false;
|
|
44
|
+
portability = 'deviceSpecific';
|
|
45
|
+
/** home 根(相对路径 = home 相对路径,与 agentInstructions 同形) */
|
|
46
|
+
baseDir = '';
|
|
47
|
+
builtin;
|
|
48
|
+
declaredTtlMs;
|
|
49
|
+
env;
|
|
50
|
+
/**
|
|
51
|
+
* 自述声明缓存。**必须带 ctx 键**(homeDir + profile):这台机器装了某插件,不代表另一台装了 ——
|
|
52
|
+
* 缓存若按实例共享,导入到「没装该插件」的目标机时会拿源机的声明去放行写入,
|
|
53
|
+
* 白名单就变成了「谁先导入谁说了算」。
|
|
54
|
+
*/
|
|
55
|
+
declaredMemo;
|
|
56
|
+
/** 本轮清单(listRelPaths 写入;unitIdOf 是同步纯函数,只能从这里拿区域表) */
|
|
57
|
+
active;
|
|
58
|
+
/** 本轮告警(环境变量覆盖跳过等;export/preview 收尾合并进返回的 warnings) */
|
|
59
|
+
pendingWarnings = [];
|
|
60
|
+
constructor(section, options = {}) {
|
|
61
|
+
super();
|
|
62
|
+
this.id = section;
|
|
63
|
+
this.displayName = sectionMeta(section).displayName;
|
|
64
|
+
this.builtin = options.sources ?? BUILTIN_PLUGIN_DATA_SOURCES;
|
|
65
|
+
this.declaredTtlMs = options.declaredTtlMs ?? DECLARED_TTL_MS;
|
|
66
|
+
this.env = options.env ?? process.env;
|
|
67
|
+
}
|
|
68
|
+
/** 本分区可收集区域(内置 + 自述声明) */
|
|
69
|
+
async allowedEntries(ctx) {
|
|
70
|
+
const sources = await this.allSources(ctx);
|
|
71
|
+
return areasForSection(sources, this.id);
|
|
72
|
+
}
|
|
73
|
+
async allSources(ctx) {
|
|
74
|
+
const declared = await this.declaredSources(ctx);
|
|
75
|
+
return [...this.builtin, ...declared];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 已安装插件的自述声明(dsh.dataDirs)。
|
|
79
|
+
*
|
|
80
|
+
* 为什么读 profile 的 node_modules 而不是全局目录:DSH 的插件装在 \`profiles/<profile>/node_modules\`
|
|
81
|
+
* 下(pnpm 隔离布局),且同一个包在不同 profile 可以是不同版本 —— 声明必须取**本 profile 实际装的**那份。
|
|
82
|
+
*
|
|
83
|
+
* 读不到(未安装 / 无声明 / 形状非法)一律跳过:本机制是**附加**信息,绝不因为一个包读不出来
|
|
84
|
+
* 就影响内置来源或整次导出。
|
|
85
|
+
*/
|
|
86
|
+
async declaredSources(ctx) {
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
const profile = ctx.profile !== undefined && ctx.profile !== '' ? ctx.profile : 'web';
|
|
89
|
+
const memoKey = ctx.homeDir + '|' + profile;
|
|
90
|
+
if (this.declaredMemo !== undefined
|
|
91
|
+
&& this.declaredMemo.key === memoKey
|
|
92
|
+
&& now - this.declaredMemo.at < this.declaredTtlMs) {
|
|
93
|
+
return this.declaredMemo.sources;
|
|
94
|
+
}
|
|
95
|
+
const base = 'profiles/' + profile;
|
|
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
|
+
}
|
|
120
|
+
this.declaredMemo = { key: memoKey, at: now, sources: out };
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 区域是否被环境变量覆盖(覆盖 → 返回变量名;未覆盖 / 未声明 → null)。
|
|
125
|
+
*
|
|
126
|
+
* 判定口径:变量**存在且解析结果与默认位置不同**才算覆盖 —— 仅仅 \`DSH_WE_DATA_DIR=~/.dsh-wallpaper-engine\`
|
|
127
|
+
* 这种「设成同一个值」的写法不该让区域凭空消失(那会让用户以为「没有数据」)。
|
|
128
|
+
* 解析只做 \`~\` 展开与分隔符归一(不做 realpath:不读盘、不猜)。
|
|
129
|
+
*/
|
|
130
|
+
envOverrideOf(ctx, area) {
|
|
131
|
+
for (const name of area.envOverrides ?? []) {
|
|
132
|
+
const raw = this.env[name];
|
|
133
|
+
if (raw === undefined || raw.trim() === '')
|
|
134
|
+
continue;
|
|
135
|
+
const normalized = raw.trim().replace(/\\/g, '/').replace(/\/+$/, '');
|
|
136
|
+
// 默认位置以**系统 home** 为根(本分区的根;见 core/backup.ts 的 OS_HOME_FILE_SECTIONS)
|
|
137
|
+
const home = osHomeDirOf(ctx).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
138
|
+
const expanded = normalized.startsWith('~/') ? home + normalized.slice(1) : normalized;
|
|
139
|
+
const defaultAbs = home + '/' + area.relPath;
|
|
140
|
+
if (expanded !== defaultAbs)
|
|
141
|
+
return name;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 区域是否是**普通文件**(而不是目录 / 不存在)。
|
|
147
|
+
*
|
|
148
|
+
* 判据顺序为什么是这样:`statSize` 对「非普通文件」与「不存在」都返回 null,无法单独区分
|
|
149
|
+
* 目录与缺失;所以先判「是文件」,其余一律按目录走遍历 —— 目录不存在时遍历返回空,
|
|
150
|
+
* 结果同样是「没有内容」,不会把缺失误报成文件,也不会把目录误读成空内容而静默丢数据。
|
|
151
|
+
* 门面未实现 statSize(旧宿主)时退回读一次内容:读得出来就是文件。
|
|
152
|
+
*/
|
|
153
|
+
async isRegularFile(fsFacade, rel) {
|
|
154
|
+
if (fsFacade.statSize !== undefined) {
|
|
155
|
+
try {
|
|
156
|
+
return (await fsFacade.statSize(rel)) !== null;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
await fsFacade.readFile(rel);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
static emptyListing() {
|
|
171
|
+
return { paths: [], skippedLinks: [], skippedDepDirs: [], followedLinks: 0, unreadableDirs: [] };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 清单枚举 = 来源注册表的区域(覆写基类的目录递归;baseDir='' 时基类实现会递归**整个 home**)。
|
|
175
|
+
*
|
|
176
|
+
* **必须覆写**:home 根上递归会遍历 profiles/ sessions/ attachments/ 等全部内容(本机实测
|
|
177
|
+
* 4000+ 文件 / 数百 MB),既慢又会把无关目录当成该分区的「可勾选单元」。
|
|
178
|
+
*/
|
|
179
|
+
async listRelPaths(ctx) {
|
|
180
|
+
// 本分区的根是**系统 home**(~/.dsh-wallpaper-engine、~/.mnemon …),不是 $DSH_HOME ——
|
|
181
|
+
// 内容 I/O 一律走 homeFs;宿主没提供就什么都不收集,并如实报码(绝不退回 $DSH_HOME 去读错位置)。
|
|
182
|
+
const homeFs = ctx.homeFs;
|
|
183
|
+
if (homeFs === undefined) {
|
|
184
|
+
this.active = [];
|
|
185
|
+
this.pendingWarnings = [engineMsg('adapter.pluginDataNoOsHome')];
|
|
186
|
+
return PluginDataAdapter.emptyListing();
|
|
187
|
+
}
|
|
188
|
+
const entries = await this.allowedEntries(ctx);
|
|
189
|
+
this.active = entries;
|
|
190
|
+
const warnings = [];
|
|
191
|
+
const paths = [];
|
|
192
|
+
const listing = PluginDataAdapter.emptyListing();
|
|
193
|
+
for (const entry of entries) {
|
|
194
|
+
const rel = entry.area.relPath;
|
|
195
|
+
const env = this.envOverrideOf(ctx, entry.area);
|
|
196
|
+
if (env !== null) {
|
|
197
|
+
warnings.push(engineMsg('adapter.pluginDataEnvOverridden', { path: rel, env }));
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (await this.isRegularFile(homeFs, rel)) {
|
|
201
|
+
paths.push(rel);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
let sub;
|
|
205
|
+
try {
|
|
206
|
+
sub = await listFilesDetailed(homeFs, rel);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// 目录不存在 / 读不到 → 视为无内容(存在的空目录与缺失在这里同义:都没有文件可带)
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
listing.skippedLinks.push(...sub.skippedLinks);
|
|
213
|
+
listing.skippedDepDirs.push(...sub.skippedDepDirs);
|
|
214
|
+
listing.unreadableDirs.push(...sub.unreadableDirs);
|
|
215
|
+
listing.followedLinks += sub.followedLinks;
|
|
216
|
+
for (const p of sub.paths) {
|
|
217
|
+
if (isExcludedRel(entry.area, p))
|
|
218
|
+
continue;
|
|
219
|
+
paths.push(p);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
listing.paths = paths;
|
|
223
|
+
this.pendingWarnings = warnings;
|
|
224
|
+
return listing;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* 单元 = 区域(最长前缀归属)。
|
|
228
|
+
*
|
|
229
|
+
* 用「本轮清单写入的区域表」而不是重新解析:listRelPaths 与本函数在同一次 collect 里成对调用,
|
|
230
|
+
* 重算会让两次解析结果不一致时出现「单元 id 不在清单里」(选择器勾不上)。测试/异常路径下
|
|
231
|
+
* \`active\` 可能是 undefined(例如直接调 listUnits),此时退回**内置来源**的最长前缀 —— 与清单口径同源。
|
|
232
|
+
*/
|
|
233
|
+
unitIdOf(relativePath) {
|
|
234
|
+
const entries = this.active ?? areasForSection(this.builtin, this.id);
|
|
235
|
+
const hit = areaOfPath(entries, normalizePath(relativePath));
|
|
236
|
+
return hit === null ? normalizePath(relativePath) : hit.area.relPath;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 读一个成员文件:**换根到系统 home 门面**(基类的其余逻辑 —— 体积、哈希、字节闸门 —— 一字不动)。
|
|
240
|
+
*
|
|
241
|
+
* 能走到这里说明 listRelPaths 产出过条目(缺门面时它不产出任何路径),因此缺门面即**抛错**,
|
|
242
|
+
* 不再造一个「空文件」出来(那会把缺通道伪装成空内容)。
|
|
243
|
+
*/
|
|
244
|
+
async readMember(ctx, rel, mode, statTimes) {
|
|
245
|
+
const homeFs = ctx.homeFs;
|
|
246
|
+
if (homeFs === undefined)
|
|
247
|
+
throw new Error(engineMsg('adapter.pluginDataNoOsHome'));
|
|
248
|
+
return super.readMember({ ...ctx, fs: homeFs }, rel, mode, statTimes);
|
|
249
|
+
}
|
|
250
|
+
async export(ctx, options) {
|
|
251
|
+
this.pendingWarnings = [];
|
|
252
|
+
const out = await super.export(ctx, options);
|
|
253
|
+
return { ...out, warnings: [...out.warnings, ...this.pendingWarnings] };
|
|
254
|
+
}
|
|
255
|
+
async preview(ctx, options) {
|
|
256
|
+
this.pendingWarnings = [];
|
|
257
|
+
const out = await super.preview(ctx, options);
|
|
258
|
+
return {
|
|
259
|
+
...out,
|
|
260
|
+
section: { ...out.section, warnings: [...out.section.warnings, ...this.pendingWarnings] },
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* 导入分析 + **写入路径白名单**(本分区唯一的越界防线)。
|
|
265
|
+
*
|
|
266
|
+
* 判据:路径必须是本机可解析的某个区域(内置来源,或已安装插件的自述声明)之下,且未被该区域
|
|
267
|
+
* 的 excludeRel 排除。不满足 → Error(计划里可见、执行期不写),绝不「尽力而为」地写出去。
|
|
268
|
+
*/
|
|
269
|
+
async analyzeImport(data, ctx) {
|
|
270
|
+
// 宿主没有系统 home 门面(旧宿主)→ 白名单为空 + 目标读也换不了根:**全部拒绝**并说明原因。
|
|
271
|
+
// 绝不退回 $DSH_HOME 的 ctx.fs 去比对/写入(那会落到错误位置)。
|
|
272
|
+
const homeFs = ctx.target.homeFs;
|
|
273
|
+
const entries = homeFs === undefined ? [] : await this.allowedEntries(ctx.target);
|
|
274
|
+
const items = await super.analyzeImport(data, homeFs === undefined ? ctx : { ...ctx, target: { ...ctx.target, fs: homeFs } });
|
|
275
|
+
return items.map((item) => {
|
|
276
|
+
const ref = item.target?.ref;
|
|
277
|
+
if (ref === undefined)
|
|
278
|
+
return item;
|
|
279
|
+
const rel = normalizeHomeRel(ref);
|
|
280
|
+
if (rel !== null && areaOfPath(entries, rel) !== null)
|
|
281
|
+
return item;
|
|
282
|
+
return {
|
|
283
|
+
id: item.id,
|
|
284
|
+
...(item.unitId !== undefined ? { unitId: item.unitId } : {}),
|
|
285
|
+
kind: 'Error',
|
|
286
|
+
adapter: this.id,
|
|
287
|
+
description: engineMsg(homeFs === undefined ? 'adapter.pluginDataNoOsHome' : 'adapter.pluginDataPathNotAllowed', { path: ref }),
|
|
288
|
+
severity: 'error',
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/** 应用前再查一遍白名单(纵深防御:plan 可能来自消费者手工构造/缓存反序列化) */
|
|
293
|
+
async applyItem(item, ctx) {
|
|
294
|
+
const homeFs = ctx.target.homeFs;
|
|
295
|
+
if (homeFs === undefined) {
|
|
296
|
+
return { ok: false, message: engineMsg('adapter.pluginDataNoOsHome') };
|
|
297
|
+
}
|
|
298
|
+
const ref = item.target?.ref;
|
|
299
|
+
if (ref !== undefined) {
|
|
300
|
+
const rel = normalizeHomeRel(ref);
|
|
301
|
+
const entries = await this.allowedEntries(ctx.target);
|
|
302
|
+
if (rel === null || areaOfPath(entries, rel) === null) {
|
|
303
|
+
return { ok: false, message: engineMsg('adapter.pluginDataPathNotAllowed', { path: ref }) };
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return super.applyItem(item, { ...ctx, target: { ...ctx.target, fs: homeFs } });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
//# sourceMappingURL=plugin-data.js.map
|
package/lib/adapters/self.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ import { FileCollectionAdapter } from './file-collection.ts';
|
|
|
47
47
|
export declare const SELF_CONFIG_FILES: readonly string[];
|
|
48
48
|
export declare class SelfAdapter extends FileCollectionAdapter {
|
|
49
49
|
readonly id: 'self';
|
|
50
|
-
readonly displayName
|
|
50
|
+
readonly displayName: string;
|
|
51
51
|
readonly defaultIncluded = true;
|
|
52
52
|
readonly portability: 'portable';
|
|
53
53
|
/** 插件自身配置目录(相对 homeDir;宿主按 dataDir 解析注入,缺省 dsh-config-manager) */
|
package/lib/adapters/self.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sectionMeta } from '../schema/section-registry.js';
|
|
1
2
|
import { FileCollectionAdapter } from './file-collection.js';
|
|
2
3
|
/** self 分区白名单文件(相对 baseDir,即 $DSH_HOME/dsh-config-manager/)。 */
|
|
3
4
|
export const SELF_CONFIG_FILES = [
|
|
@@ -65,7 +66,7 @@ function canonicalizeWithoutRunState(raw) {
|
|
|
65
66
|
}
|
|
66
67
|
export class SelfAdapter extends FileCollectionAdapter {
|
|
67
68
|
id = 'self';
|
|
68
|
-
displayName = '
|
|
69
|
+
displayName = sectionMeta('self').displayName;
|
|
69
70
|
defaultIncluded = true;
|
|
70
71
|
portability = 'portable';
|
|
71
72
|
/** 插件自身配置目录(相对 homeDir;宿主按 dataDir 解析注入,缺省 dsh-config-manager) */
|
|
@@ -167,6 +167,15 @@ export declare class MockHostContext implements HostContext {
|
|
|
167
167
|
sessions?: HostContext['sessions'];
|
|
168
168
|
/** 可选(issue #71):技能服务端口;测试按需注入(缺省 = 退回纯目录扫描)。 */
|
|
169
169
|
skills?: HostContext['skills'];
|
|
170
|
+
/**
|
|
171
|
+
* 可选(第三方插件数据):系统 home 与它的文件门面。缺省 undefined = **旧宿主形态**
|
|
172
|
+
* (pluginData / pluginAssets 不收集也不写入,如实报码);需要它们的测试自行注入,
|
|
173
|
+
* 或用 withOsHome() 把系统 home 指向本夹具自己的根。
|
|
174
|
+
*/
|
|
175
|
+
osHomeDir?: string;
|
|
176
|
+
homeFs?: HostContext['homeFs'];
|
|
177
|
+
/** 测试助手:把「系统 home」指向本上下文的根(两分区在本夹具里即可直接用) */
|
|
178
|
+
withOsHome(): this;
|
|
170
179
|
constructor(platform: string, homeDir: string, profile?: string);
|
|
171
180
|
}
|
|
172
181
|
export declare function makeContext(platform: string, homeDir: string, profile?: string): MockHostContext;
|
|
@@ -261,6 +261,19 @@ export class MockHostContext {
|
|
|
261
261
|
sessions;
|
|
262
262
|
/** 可选(issue #71):技能服务端口;测试按需注入(缺省 = 退回纯目录扫描)。 */
|
|
263
263
|
skills;
|
|
264
|
+
/**
|
|
265
|
+
* 可选(第三方插件数据):系统 home 与它的文件门面。缺省 undefined = **旧宿主形态**
|
|
266
|
+
* (pluginData / pluginAssets 不收集也不写入,如实报码);需要它们的测试自行注入,
|
|
267
|
+
* 或用 withOsHome() 把系统 home 指向本夹具自己的根。
|
|
268
|
+
*/
|
|
269
|
+
osHomeDir;
|
|
270
|
+
homeFs;
|
|
271
|
+
/** 测试助手:把「系统 home」指向本上下文的根(两分区在本夹具里即可直接用) */
|
|
272
|
+
withOsHome() {
|
|
273
|
+
this.osHomeDir = this.homeDir;
|
|
274
|
+
this.homeFs = this.fs;
|
|
275
|
+
return this;
|
|
276
|
+
}
|
|
264
277
|
constructor(platform, homeDir, profile) {
|
|
265
278
|
this.platform = platform;
|
|
266
279
|
this.homeDir = homeDir;
|
|
@@ -283,6 +296,7 @@ export function makeManifest(platform = 'win32') {
|
|
|
283
296
|
settings: false, ui: false, providers: false, plugins: false, mcp: false, prompts: false,
|
|
284
297
|
skills: false, agentPresets: false, agentInstructions: false, workspaces: false, pluginFiles: false,
|
|
285
298
|
credentialsStatus: false, secrets: false, sessions: false, self: false,
|
|
299
|
+
pluginData: false, pluginAssets: false,
|
|
286
300
|
},
|
|
287
301
|
security: { containsSecrets: false, encrypted: false, encryption: null },
|
|
288
302
|
};
|
package/lib/client.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ type Platform = 'win32' | 'darwin' | 'linux' | 'freebsd' | 'openbsd' | 'aix' | '
|
|
|
20
20
|
* 明确不实现的分区(keybindings/workflows/commands)不在此列;
|
|
21
21
|
* rules 已由 agentInstructions(~/.dsh/AGENTS.md,dsh-agent-instructions 全局指令)承接。
|
|
22
22
|
*/
|
|
23
|
-
type SectionId = 'settings' | 'ui' | 'providers' | 'plugins' | 'mcp' | 'prompts' | 'skills' | 'agentPresets' | 'agentInstructions' | 'workspaces' | 'pluginFiles' | 'credentialsStatus' | 'secrets' | 'sessions' | 'self';
|
|
23
|
+
type SectionId = 'settings' | 'ui' | 'providers' | 'plugins' | 'mcp' | 'prompts' | 'skills' | 'agentPresets' | 'agentInstructions' | 'workspaces' | 'pluginFiles' | 'credentialsStatus' | 'secrets' | 'sessions' | 'self' | 'pluginData' | 'pluginAssets';
|
|
24
24
|
/** 加密备份的算法参数(非秘密:salt/iv/authTag 仅用于解密与完整性校验) */
|
|
25
25
|
interface EncryptionInfo {
|
|
26
26
|
algorithm: 'aes-256-gcm';
|
|
@@ -1593,7 +1593,7 @@ declare const uiZh: {
|
|
|
1593
1593
|
readonly 'sync.kind.skip': '跳过';
|
|
1594
1594
|
readonly 'sync.kind.conflict': '冲突';
|
|
1595
1595
|
readonly 'sync.kind.install': '安装';
|
|
1596
|
-
readonly 'sync.kind.missingSecret': '
|
|
1596
|
+
readonly 'sync.kind.missingSecret': '凭据迁移';
|
|
1597
1597
|
readonly 'sync.kind.missingDependency': '缺依赖';
|
|
1598
1598
|
readonly 'sync.kind.pathMapping': '路径映射';
|
|
1599
1599
|
readonly 'sync.kind.warning': '警告';
|
|
@@ -1948,8 +1948,10 @@ declare const uiZh: {
|
|
|
1948
1948
|
readonly 'export.category.settings': 'DSH 全局设置(namespace 分区,redacted)';
|
|
1949
1949
|
readonly 'export.category.providers': 'LLM Provider / Model / 默认模型 / BaseURL';
|
|
1950
1950
|
readonly 'export.category.plugins': '已安装插件清单与启用状态(不含二进制)';
|
|
1951
|
-
readonly 'export.category.pluginFiles': '
|
|
1951
|
+
readonly 'export.category.pluginFiles': '其他插件放在 ~/.dsh 根下的零散配置文件(白名单 + plugin-config/ 目录,整文件复制)';
|
|
1952
1952
|
readonly 'export.category.self': '本插件自身配置(同步/自动同步/分区选择/UI 偏好/市场;sync-*.json 等,不含凭据值)';
|
|
1953
|
+
readonly 'export.category.pluginData': '其他插件在系统主目录下的数据(如 ~/.dsh-wallpaper-engine、~/.mnemon 的配置与状态;可再生成缓存绝不收录)';
|
|
1954
|
+
readonly 'export.category.pluginAssets': '其他插件的大体积文件(上传的壁纸、本地数据库等;默认不勾选)';
|
|
1953
1955
|
readonly 'export.category.mcp': 'MCP 服务器组合配置(需重启生效)';
|
|
1954
1956
|
readonly 'export.category.prompts': 'System Prompt / Plan Mode 提示';
|
|
1955
1957
|
readonly 'export.category.skills': '用户技能文件(~/.dsh/skills)';
|
|
@@ -4062,12 +4064,50 @@ interface RollbackPointSummary {
|
|
|
4062
4064
|
spreadMs?: number;
|
|
4063
4065
|
segments?: RollbackSegmentSummary[];
|
|
4064
4066
|
}
|
|
4067
|
+
/**
|
|
4068
|
+
* 台账占用(issue #89 P2:可观测)。
|
|
4069
|
+
* `bytes` = records/ + payloads/ 的实际字节数;`budgetBytes` = 触发淘汰的字节预算;
|
|
4070
|
+
* `evicted` = 本进程内被预算淘汰的自动点累计数。缺省(老宿主没这个字段)= 界面不渲染这一行。
|
|
4071
|
+
*/
|
|
4072
|
+
interface RollbackLedgerStatus {
|
|
4073
|
+
records: number;
|
|
4074
|
+
bytes: number;
|
|
4075
|
+
budgetBytes: number;
|
|
4076
|
+
evicted: number;
|
|
4077
|
+
/**
|
|
4078
|
+
* 已超预算且**无处可淘汰**(典型:单条点本身就大于预算)。
|
|
4079
|
+
* 老宿主没有这个字段 ⇒ `undefined` 与 `false` 同义,绝不因为读不到就报警。
|
|
4080
|
+
*/
|
|
4081
|
+
overBudget?: boolean;
|
|
4082
|
+
}
|
|
4065
4083
|
interface RollbackStorageStatus {
|
|
4066
4084
|
available: boolean;
|
|
4067
4085
|
root?: string;
|
|
4068
4086
|
detail?: string;
|
|
4069
4087
|
guidance?: RollbackGuidance[];
|
|
4070
4088
|
requirements?: readonly string[];
|
|
4089
|
+
/** 磁盘偏好:回合自动留点开关(缺省 / false = 关闭) */
|
|
4090
|
+
rollbackEnabled?: boolean;
|
|
4091
|
+
/** 本进程此刻是否真的在留点(关闭后订阅已解绑)。与 rollbackEnabled 正常一致 */
|
|
4092
|
+
rollbackActive?: boolean;
|
|
4093
|
+
/** 台账占用(老宿主没有这个字段) */
|
|
4094
|
+
ledger?: RollbackLedgerStatus;
|
|
4095
|
+
}
|
|
4096
|
+
/** 开关读 / 写的结果(POST 成功才会改进程 gate)。 */
|
|
4097
|
+
interface RollbackPrefsResult {
|
|
4098
|
+
ok: boolean;
|
|
4099
|
+
rollbackEnabled: boolean;
|
|
4100
|
+
rollbackActive?: boolean;
|
|
4101
|
+
}
|
|
4102
|
+
/** 台账清理结果(只删自动点;guard 与 protected 保留)。 */
|
|
4103
|
+
interface RollbackPurgeResult {
|
|
4104
|
+
ok: boolean;
|
|
4105
|
+
code?: string;
|
|
4106
|
+
detail?: string;
|
|
4107
|
+
removedRecords?: number;
|
|
4108
|
+
removedPayloads?: number;
|
|
4109
|
+
removedObjects?: number;
|
|
4110
|
+
bytes?: number;
|
|
4071
4111
|
}
|
|
4072
4112
|
interface RollbackListResult {
|
|
4073
4113
|
ok?: boolean;
|
|
@@ -4316,6 +4356,19 @@ interface RollbackPointPort {
|
|
|
4316
4356
|
rollbackPointFileDiff?(id: string, path: string): Promise<RollbackFileDiffResult>;
|
|
4317
4357
|
restoreRollbackPoint(id: string, opts: RollbackRewindOptions): Promise<RollbackRewindResult>;
|
|
4318
4358
|
removeRollbackPoint(id: string, userConfirmed: boolean): Promise<RollbackRemoveResult>;
|
|
4359
|
+
/**
|
|
4360
|
+
* 回合自动留点开关(issue #88:它跟配置备份/同步是两件事,必须能关)。
|
|
4361
|
+
* 可选实现:端口缺失时界面**不渲染**开关(绝不给一个点了没反应的入口)。
|
|
4362
|
+
*/
|
|
4363
|
+
rollbackPrefs?(): Promise<RollbackPrefsResult>;
|
|
4364
|
+
setRollbackEnabled?(enabled: boolean): Promise<RollbackPrefsResult>;
|
|
4365
|
+
/**
|
|
4366
|
+
* 清理台账(issue #89 P1/P2:把已经涨上去的占用收回来)。
|
|
4367
|
+
* **只删自动点**;`includeGuards` 为 true 时连 guard 也删,protected 永不删。
|
|
4368
|
+
*/
|
|
4369
|
+
purgeRollbackPoints?(opts: {
|
|
4370
|
+
includeGuards: boolean;
|
|
4371
|
+
}): Promise<RollbackPurgeResult>;
|
|
4319
4372
|
}
|
|
4320
4373
|
//#endregion
|
|
4321
4374
|
//#region src/client/recovery/recovery-api.d.ts
|
|
@@ -4390,6 +4443,25 @@ declare class RecoveryApi implements RecoveryPort, RollbackPointPort {
|
|
|
4390
4443
|
* body 必须 `userConfirmed: true`(未确认 → 409 `confirmation-required`);本方法不自动置 true。
|
|
4391
4444
|
*/
|
|
4392
4445
|
removeRollbackPoint(id: string, userConfirmed: boolean): Promise<RollbackRemoveResult>;
|
|
4446
|
+
/**
|
|
4447
|
+
* GET /recovery/rollback-points/prefs(issue #88):读开关。
|
|
4448
|
+
* `rollbackEnabled` = 磁盘偏好(用户意图,随 self 分区进备份);
|
|
4449
|
+
* `rollbackActive` = 本进程此刻是否真的在留点(关闭后订阅已解绑)。两者正常一致。
|
|
4450
|
+
*/
|
|
4451
|
+
rollbackPrefs(): Promise<RollbackPrefsResult>;
|
|
4452
|
+
/**
|
|
4453
|
+
* POST /recovery/rollback-points/prefs(issue #88):写开关。
|
|
4454
|
+
* 宿主写盘成功后才切进程 gate —— 写失败却把功能打开,等于「界面说开着、实际不留点」。
|
|
4455
|
+
*/
|
|
4456
|
+
setRollbackEnabled(enabled: boolean): Promise<RollbackPrefsResult>;
|
|
4457
|
+
/**
|
|
4458
|
+
* POST /recovery/rollback-points/cleanup(issue #89):清理台账占用。
|
|
4459
|
+
* **只删自动点**;`includeGuards` 为 true 时连救援点也删(protected 永不删)。
|
|
4460
|
+
* 返回逐类删除计数与释放字节,界面照实说「删了多少 / 还剩多少」。
|
|
4461
|
+
*/
|
|
4462
|
+
purgeRollbackPoints(opts: {
|
|
4463
|
+
includeGuards: boolean;
|
|
4464
|
+
}): Promise<RollbackPurgeResult>;
|
|
4393
4465
|
/** POST /recovery/sessions/rollback(T8):按台账 repairId 回滚(客户端不传路径)。 */
|
|
4394
4466
|
rollbackSessionRepair(repairId: string): Promise<SessionRepairRollbackResult>;
|
|
4395
4467
|
/**
|
|
@@ -4618,11 +4690,13 @@ declare const zh$4: {
|
|
|
4618
4690
|
readonly 'section.agentPresets': 'Agent 预设';
|
|
4619
4691
|
readonly 'section.agentInstructions': '全局指令';
|
|
4620
4692
|
readonly 'section.workspaces': '工作区';
|
|
4621
|
-
readonly 'section.pluginFiles': '
|
|
4693
|
+
readonly 'section.pluginFiles': '其他插件零散文件';
|
|
4622
4694
|
readonly 'section.credentialsStatus': '凭据状态';
|
|
4623
4695
|
readonly 'section.secrets': '密钥';
|
|
4624
4696
|
readonly 'section.sessions': '历史会话';
|
|
4625
|
-
readonly 'section.self': '
|
|
4697
|
+
readonly 'section.self': '本插件自身配置';
|
|
4698
|
+
readonly 'section.pluginData': '其他插件数据';
|
|
4699
|
+
readonly 'section.pluginAssets': '其他插件大文件';
|
|
4626
4700
|
readonly 'view.about': '关于';
|
|
4627
4701
|
readonly 'about.snippet.environment': '### 环境 / Environment';
|
|
4628
4702
|
readonly 'about.releaseNotes.rateLimited': 'GitHub API 速率限制(Rate limit exceeded),请稍后再试或直接在 GitHub 上查看。';
|
|
@@ -6349,6 +6423,20 @@ declare const zh$1: {
|
|
|
6349
6423
|
readonly 'rollback.badge.incomplete': '两态不全';
|
|
6350
6424
|
readonly 'rollback.badge.protected': '受保护(只进不出)';
|
|
6351
6425
|
readonly 'rollback.apiMissing': '当前宿主没有回滚点能力(插件版本过旧):界面不假装可用';
|
|
6426
|
+
readonly 'rollback.switch.label': '启用回合自动留点';
|
|
6427
|
+
readonly 'rollback.switch.hint': '关闭后不再订阅会话事件、不再写台账,聊天里的「↺ 回退」入口一并收起;已有回滚点仍可查看、回退与清理。';
|
|
6428
|
+
readonly 'rollback.switch.unknown': '正在读取开关状态(还没读到之前不显示成「已关闭」)。';
|
|
6429
|
+
readonly 'rollback.switch.diverged': '磁盘上的偏好是「开启」,但本进程没有启用留点(通常是被另一个 DSH 实例改过)。重新打开一次开关即可让本进程生效。';
|
|
6430
|
+
readonly 'rollback.ledger.summary': '台账 {records} 个点 · {size}';
|
|
6431
|
+
readonly 'rollback.ledger.budget': '字节预算 {budget},已按预算淘汰 {evicted} 个最旧自动点';
|
|
6432
|
+
readonly 'rollback.ledger.overBudget': '台账已到 {size},超过 {budget} 的字节预算,但已经没有可自动淘汰的点了(通常是单个回滚点本身大于预算)—— 再增长请手动清理。';
|
|
6433
|
+
readonly 'rollback.cleanup': '清理台账';
|
|
6434
|
+
readonly 'rollback.cleanup.confirmTitle': '清理回滚点台账';
|
|
6435
|
+
readonly 'rollback.cleanup.confirmMessage': '删除全部自动回滚点(救援点与受保护点按下面的勾选处置),并回收它们的内容副本。工作区文件与会话日志不会被改动。';
|
|
6436
|
+
readonly 'rollback.cleanup.includeGuards': '同时删除救援点(回退前的自动救援点,删了不可撤销)';
|
|
6437
|
+
readonly 'rollback.cleanup.done': '已删除 {records} 个回滚点,释放 {size}';
|
|
6438
|
+
readonly 'rollback.cleanup.none': '没有可删除的回滚点(自动点为空)';
|
|
6439
|
+
readonly 'rollback.switch.saveFailed': '开关保存失败,仍按原状态运行:';
|
|
6352
6440
|
readonly 'rollback.retention.hint': '自动点:每会话最多 {perSession} 个、全局最多 {total} 个,超出只淘汰最旧的自动点。回退前的救援点不占这两个额度、也不被它们淘汰,但用户删不掉,所以另有自己的两道上限:每会话最多 {guardsPerSession} 个、全局最多 {guards} 个(无论按哪道算,超出都淘汰最旧的救援点)。';
|
|
6353
6441
|
readonly 'rollback.autopoint.hint': '回滚点由每个回合结束时自动留:工作区快照 + 会话游标(没有「手动拍点」入口)。';
|
|
6354
6442
|
readonly 'rollback.state.workspace': '工作区';
|
|
@@ -6390,6 +6478,7 @@ declare const zh$1: {
|
|
|
6390
6478
|
readonly 'rollback.code.session-out-of-root': '会话日志不在本机会话根内,已拒绝';
|
|
6391
6479
|
readonly 'rollback.code.session-too-large': '会话日志超过上限:只记下了游标(回退不依赖副本)';
|
|
6392
6480
|
readonly 'rollback.code.session-active': '这条对话正在运行(会话日志刚被写过):此时带文件段的回退会被拒绝、工作区一个字节都不动;只回溯消息不受影响(回退只 fork 子会话,不改写原日志)';
|
|
6481
|
+
readonly 'rollback.code.subagent-session': '这是子代理会话(origin=subagent),插件不为它自动留回滚点:它的工作区与父对话相同,留点只会挤占你自己对话的台账空间。';
|
|
6393
6482
|
readonly 'rollback.preview.sessionActive': '这条对话正在运行:本轮结束前只回溯消息(不改动工作区),避免与正在写盘的工具抢文件。等本轮结束后重新打开本弹窗,即可连文件一起回退。';
|
|
6394
6483
|
readonly 'rollback.sessionActive.denied': '这条对话正在运行:本次一个文件都没有改动。请等本轮结束,或改成「只回溯消息」再试。';
|
|
6395
6484
|
readonly 'rollback.code.session-fork-unavailable': '当前宿主没有提供会话 fork 能力,因此不能回退会话';
|
|
@@ -6415,7 +6504,7 @@ declare const zh$1: {
|
|
|
6415
6504
|
readonly 'rollback.warn.trackDegraded': '有一态留得不完整(回退可能不完整)';
|
|
6416
6505
|
readonly 'rollback.warn.protected': '该回滚点受保护(只进不出):不可回退、不可删除';
|
|
6417
6506
|
readonly 'rollback.warn.guardFailed': '回退前的救援点没留成(已记告警后继续)';
|
|
6418
|
-
readonly 'rollback.warn.createdRefused': '本次不删除任何「留点后新建」的路径:捕获时的证明名单不完整(有整目录被忽略 / 路径索引缺失 /
|
|
6507
|
+
readonly 'rollback.warn.createdRefused': '本次不删除任何「留点后新建」的路径:捕获时的证明名单不完整(有整目录被忽略 / 路径索引缺失 / 目录读不出 / 留点被时间预算中断),无法证明哪些是新建的';
|
|
6419
6508
|
readonly 'rollback.warn.unknown': '未识别的告警(技术细节见下方)';
|
|
6420
6509
|
readonly 'rollback.preview': '预览回退';
|
|
6421
6510
|
readonly 'rollback.preview.title': '回退预览';
|