dsh-recall-plugin 2.4.5 → 2.4.7
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/CHANGELOG.md +36 -0
- package/README.en.md +14 -4
- package/README.md +22 -12
- package/lib/client.js +770 -199
- package/lib/config.js +11 -4
- package/lib/errors.js +3 -0
- package/lib/exclude-patterns.js +2 -1
- package/lib/index.js +31 -4
- package/lib/intent-journal.js +108 -0
- package/lib/maintenance.js +9 -4
- package/lib/routes-core.js +23 -6
- package/lib/routes-manage.js +24 -6
- package/lib/scripts.posix.js +4 -0
- package/lib/scripts.pwsh.js +4 -0
- package/lib/snapshots.js +74 -4
- package/lib/store.js +7 -5
- package/package.json +16 -8
package/lib/config.js
CHANGED
|
@@ -40,7 +40,11 @@ const Config = Schema.object({
|
|
|
40
40
|
refillDraft: withVolatile(Schema.boolean().default(true).description("\u64A4\u56DE\u540E\u628A\u88AB\u64A4\u56DE\u7684\u6D88\u606F\uFF08\u6587\u672C\u4E0E\u9644\u4EF6\uFF09\u56DE\u586B\u5230\u8F93\u5165\u6846")),
|
|
41
41
|
snapshotEnabled: withVolatile(Schema.boolean().default(true).description("\u542F\u7528\u6D88\u606F\u5FEB\u7167\uFF08\u5173\u95ED\u540E\u4E0D\u518D\u65B0\u5EFA\uFF0C\u5DF2\u6709\u5FEB\u7167\u4ECD\u53EF\u64A4\u56DE\uFF09")),
|
|
42
42
|
archiveOriginal: withVolatile(Schema.boolean().default(true).description("\u64A4\u56DE\u540E\u5F52\u6863\u539F\u4F1A\u8BDD\uFF08\u5173\u95ED\u540E\u539F\u4F1A\u8BDD\u4FDD\u7559\u5728\u5217\u8868\u4E2D\uFF09")),
|
|
43
|
-
retentionDays: withVolatile(Schema.number().default(0).description("\u6309\u5929\u6570\u4FDD\u7559\u5FEB\u7167\uFF0C\u8D85\u671F\u81EA\u52A8\u5220\u9664\uFF1B0 \u8868\u793A\u4E0D\u542F\u7528"))
|
|
43
|
+
retentionDays: withVolatile(Schema.number().default(0).description("\u6309\u5929\u6570\u4FDD\u7559\u5FEB\u7167\uFF0C\u8D85\u671F\u81EA\u52A8\u5220\u9664\uFF1B0 \u8868\u793A\u4E0D\u542F\u7528")),
|
|
44
|
+
// A4:界面语言。union 三值而不是自由字符串——非法配置在加载时响亮失败
|
|
45
|
+
//(合规清单 #3);description 保持中文是 v1 明示限制:它由宿主官方设置
|
|
46
|
+
// 表单在 host 侧渲染,插件无法本地化(自定义配置卡片不受影响,见 README)
|
|
47
|
+
locale: withVolatile(Schema.union(["auto", "zh", "en"]).default("auto").description("\u754C\u9762\u8BED\u8A00\uFF1Aauto \u8DDF\u968F\u7CFB\u7EDF\uFF0C\u53EF\u9009 zh / en\uFF08\u4FDD\u5B58\u540E\u751F\u6548\uFF09"))
|
|
44
48
|
});
|
|
45
49
|
function isLegacySettingsFace(settings) {
|
|
46
50
|
const svc = settings;
|
|
@@ -65,7 +69,8 @@ function resolveSettingsNs(ctx, settings) {
|
|
|
65
69
|
for (const id of entryIds) {
|
|
66
70
|
if (known.indexOf(id) >= 0) return id;
|
|
67
71
|
}
|
|
68
|
-
|
|
72
|
+
const first = entryIds[0];
|
|
73
|
+
return first !== void 0 ? first : null;
|
|
69
74
|
}
|
|
70
75
|
function describeNamespaces(settings) {
|
|
71
76
|
const svc = settings;
|
|
@@ -119,7 +124,8 @@ const DEFAULTS = {
|
|
|
119
124
|
refillDraft: true,
|
|
120
125
|
snapshotEnabled: true,
|
|
121
126
|
archiveOriginal: true,
|
|
122
|
-
retentionDays: 0
|
|
127
|
+
retentionDays: 0,
|
|
128
|
+
locale: "auto"
|
|
123
129
|
};
|
|
124
130
|
function createConfig(raw) {
|
|
125
131
|
const cfg = raw && typeof raw === "object" ? raw : {};
|
|
@@ -139,7 +145,8 @@ function createConfig(raw) {
|
|
|
139
145
|
const archiveOriginal = typeof cfg.archiveOriginal === "boolean" ? cfg.archiveOriginal : true;
|
|
140
146
|
const rawDays = typeof cfg.retentionDays === "number" ? cfg.retentionDays : parseInt(String(cfg.retentionDays == null ? "" : cfg.retentionDays), 10);
|
|
141
147
|
const retentionDays = Number.isFinite(rawDays) ? Math.max(0, rawDays) : 0;
|
|
142
|
-
|
|
148
|
+
const locale = cfg.locale === "zh" || cfg.locale === "en" ? cfg.locale : "auto";
|
|
149
|
+
return { gcSnaps, gcHours, maxFileBytes, maxSnapshotsPerWorkspace, baseExcludes, refillDraft, snapshotEnabled, archiveOriginal, retentionDays, locale };
|
|
143
150
|
}
|
|
144
151
|
export {
|
|
145
152
|
Config,
|
package/lib/errors.js
CHANGED
|
@@ -14,6 +14,7 @@ const RECALL_NO_ROOT = "NO_ROOT";
|
|
|
14
14
|
const RECALL_NO_SESSION = "NO_SESSION";
|
|
15
15
|
const RECALL_PARTIAL_DELETE = "PARTIAL_DELETE";
|
|
16
16
|
const RECALL_UNKNOWN_OP = "UNKNOWN_OP";
|
|
17
|
+
const RECALL_FORMAT_BLOCKED = "FORMAT_BLOCKED";
|
|
17
18
|
const RECALL_UNKNOWN_ENDPOINT = "UNKNOWN_ENDPOINT";
|
|
18
19
|
const RECALL_INDEX_CORRUPT = "INDEX_CORRUPT";
|
|
19
20
|
const ALL_CODES = Object.freeze([
|
|
@@ -33,6 +34,7 @@ const ALL_CODES = Object.freeze([
|
|
|
33
34
|
RECALL_NO_SESSION,
|
|
34
35
|
RECALL_PARTIAL_DELETE,
|
|
35
36
|
RECALL_UNKNOWN_OP,
|
|
37
|
+
RECALL_FORMAT_BLOCKED,
|
|
36
38
|
RECALL_UNKNOWN_ENDPOINT,
|
|
37
39
|
RECALL_INDEX_CORRUPT
|
|
38
40
|
]);
|
|
@@ -43,6 +45,7 @@ export {
|
|
|
43
45
|
RECALL_BODY_TOO_LARGE,
|
|
44
46
|
RECALL_EMPTY_PATCH,
|
|
45
47
|
RECALL_ERROR,
|
|
48
|
+
RECALL_FORMAT_BLOCKED,
|
|
46
49
|
RECALL_INDEX_CORRUPT,
|
|
47
50
|
RECALL_NO_ROOT,
|
|
48
51
|
RECALL_NO_SESSION,
|
package/lib/exclude-patterns.js
CHANGED
|
@@ -20,7 +20,8 @@ function buildArtifactRootSegment(root, base, isWin) {
|
|
|
20
20
|
const pool = isWin ? names.map((n) => n.toLowerCase()) : names;
|
|
21
21
|
for (const seg of segs) {
|
|
22
22
|
const i = pool.indexOf(isWin ? seg.toLowerCase() : seg);
|
|
23
|
-
|
|
23
|
+
const hit = i >= 0 ? names[i] : void 0;
|
|
24
|
+
if (hit !== void 0) return hit;
|
|
24
25
|
}
|
|
25
26
|
return null;
|
|
26
27
|
}
|
package/lib/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createMaintenance } from "./maintenance.js";
|
|
|
7
7
|
import { createSessionInfo, titleFromEvents, messageTextFromEvents } from "./session-info.js";
|
|
8
8
|
import { createRoutesCore } from "./routes-core.js";
|
|
9
9
|
import { createRoutesManage } from "./routes-manage.js";
|
|
10
|
+
import { createIntentJournal } from "./intent-journal.js";
|
|
10
11
|
import * as dshSettings from "@deepseek-ai/dsh-settings";
|
|
11
12
|
import * as E from "./errors.js";
|
|
12
13
|
const name = "dsh-recall-plugin";
|
|
@@ -65,7 +66,7 @@ function apply(ctx, config) {
|
|
|
65
66
|
const workers = Array.from({ length: Math.min(limit, tasks.length) }, async () => {
|
|
66
67
|
while (index < tasks.length) {
|
|
67
68
|
const task = tasks[index++];
|
|
68
|
-
await task();
|
|
69
|
+
if (task) await task();
|
|
69
70
|
}
|
|
70
71
|
});
|
|
71
72
|
await Promise.all(workers);
|
|
@@ -215,6 +216,26 @@ function apply(ctx, config) {
|
|
|
215
216
|
}
|
|
216
217
|
return records;
|
|
217
218
|
}
|
|
219
|
+
const intentJournal = createIntentJournal({
|
|
220
|
+
runShell: rt.runShell,
|
|
221
|
+
writeTextViaShell: rt.writeTextViaShell,
|
|
222
|
+
scripts: rt.scripts,
|
|
223
|
+
isWin: rt.isWin,
|
|
224
|
+
recordError: rt.recordError,
|
|
225
|
+
workspaceMatchesTag: async (messageId) => {
|
|
226
|
+
const d = await snaps.diffFor(messageId);
|
|
227
|
+
return Boolean(d && d.total === 0 && d.changes.length === 0);
|
|
228
|
+
},
|
|
229
|
+
resetToSafety: async (store, safetyId, root) => {
|
|
230
|
+
try {
|
|
231
|
+
const out = await rt.runShell(rt.scripts.rescueScript(root, store, state.gitExe || "", "snap-" + safetyId), { timeoutMs: 6e5, stdoutMaxBytes: 65536 });
|
|
232
|
+
return String(out || "").indexOf("RESCUE_OK") >= 0;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
agentBusy: (root) => agentBusy(null, root)
|
|
238
|
+
});
|
|
218
239
|
const deps = {
|
|
219
240
|
ctx,
|
|
220
241
|
rt,
|
|
@@ -245,6 +266,7 @@ function apply(ctx, config) {
|
|
|
245
266
|
readSettings: () => readSettings(),
|
|
246
267
|
DEFAULTS,
|
|
247
268
|
rescueRollback,
|
|
269
|
+
intentJournal,
|
|
248
270
|
E
|
|
249
271
|
};
|
|
250
272
|
const endpoints = {
|
|
@@ -252,7 +274,7 @@ function apply(ctx, config) {
|
|
|
252
274
|
...createRoutesManage(deps)
|
|
253
275
|
};
|
|
254
276
|
const handleRecall = async (request) => {
|
|
255
|
-
const name2 = new URL(request.url).pathname.replace(/^\/api\/recall\/?/, "").split("/")[0];
|
|
277
|
+
const name2 = new URL(request.url).pathname.replace(/^\/api\/recall\/?/, "").split("/")[0] ?? "";
|
|
256
278
|
const endpoint = endpoints[name2];
|
|
257
279
|
if (!endpoint) {
|
|
258
280
|
return jsonResponse({ ok: false, code: E.RECALL_UNKNOWN_ENDPOINT, message: "unknown endpoint: " + name2 });
|
|
@@ -319,10 +341,15 @@ function apply(ctx, config) {
|
|
|
319
341
|
}
|
|
320
342
|
}
|
|
321
343
|
for (const [cwd, sessionId] of warmupRoots) {
|
|
322
|
-
Promise.resolve(rt.resolveStore(cwd)).then(() => rt.tryUpgradeToHome(cwd)).then((store) => rt.ensureGit(cwd, store)).then(() => snaps.loadIndex(cwd, sessionId)).then(() => snaps.rebuildOrphans(cwd, sessionId)).then(
|
|
344
|
+
Promise.resolve(rt.resolveStore(cwd)).then(() => rt.tryUpgradeToHome(cwd)).then((store) => rt.ensureGit(cwd, store)).then(() => snaps.loadIndex(cwd, sessionId)).then(() => snaps.rebuildOrphans(cwd, sessionId)).then(async () => {
|
|
345
|
+
const st = state.stores.get(cwd);
|
|
346
|
+
if (st && await snaps.guardStoreFormat(st)) await intentJournal.recover(st);
|
|
347
|
+
}).then(() => rt.cleanupLegacy(cwd)).catch(() => {
|
|
323
348
|
});
|
|
324
349
|
}
|
|
325
|
-
})()
|
|
350
|
+
})().catch((error) => {
|
|
351
|
+
console.error("recall warmup skipped:", error);
|
|
352
|
+
});
|
|
326
353
|
}
|
|
327
354
|
export {
|
|
328
355
|
Config,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const INTENT_VERSION = 1;
|
|
2
|
+
function describeIntent(intent) {
|
|
3
|
+
return "message=" + intent.messageId + ", phase=" + intent.phase + ", safety=snap-" + intent.safetyId;
|
|
4
|
+
}
|
|
5
|
+
function createIntentJournal(deps) {
|
|
6
|
+
const handled = /* @__PURE__ */ new Set();
|
|
7
|
+
function intentFile(store) {
|
|
8
|
+
return store.dir + (deps.isWin ? "\\" : "/") + "recall-intent.json";
|
|
9
|
+
}
|
|
10
|
+
async function readRaw(store) {
|
|
11
|
+
let raw = "";
|
|
12
|
+
try {
|
|
13
|
+
raw = deps.scripts.stripBom(await deps.runShell(deps.scripts.fileReadCmd(intentFile(store)), { timeoutMs: 3e4, stdoutMaxBytes: 65536 })).trim();
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return { ok: false, intent: null };
|
|
16
|
+
}
|
|
17
|
+
if (!raw) return { ok: true, intent: null };
|
|
18
|
+
try {
|
|
19
|
+
const obj = JSON.parse(raw);
|
|
20
|
+
if (!obj || obj.v !== INTENT_VERSION || obj.op !== "execute" || typeof obj.messageId !== "string" || !obj.messageId || typeof obj.safetyId !== "string" || !obj.safetyId || obj.phase !== "rollback" && obj.phase !== "rescue") {
|
|
21
|
+
return { ok: true, intent: null };
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
ok: true,
|
|
25
|
+
intent: {
|
|
26
|
+
v: INTENT_VERSION,
|
|
27
|
+
op: "execute",
|
|
28
|
+
messageId: obj.messageId,
|
|
29
|
+
root: typeof obj.root === "string" ? obj.root : "",
|
|
30
|
+
safetyId: obj.safetyId,
|
|
31
|
+
safetyOk: obj.safetyOk !== false,
|
|
32
|
+
phase: obj.phase,
|
|
33
|
+
time: typeof obj.time === "number" ? obj.time : 0
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return { ok: true, intent: null };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function write(store, text) {
|
|
41
|
+
try {
|
|
42
|
+
await deps.writeTextViaShell(intentFile(store), text);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
deps.recordError("recall intent journal write failed: " + String(error));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function read(store) {
|
|
48
|
+
return readRaw(store).then((r) => r.intent);
|
|
49
|
+
}
|
|
50
|
+
async function begin(store, root, fields) {
|
|
51
|
+
const intent = {
|
|
52
|
+
v: INTENT_VERSION,
|
|
53
|
+
op: "execute",
|
|
54
|
+
messageId: fields.messageId,
|
|
55
|
+
root,
|
|
56
|
+
safetyId: fields.safetyId,
|
|
57
|
+
safetyOk: fields.safetyOk,
|
|
58
|
+
phase: "rollback",
|
|
59
|
+
time: Date.now()
|
|
60
|
+
};
|
|
61
|
+
await write(store, JSON.stringify(intent));
|
|
62
|
+
}
|
|
63
|
+
async function advance(store, phase) {
|
|
64
|
+
const cur = await read(store);
|
|
65
|
+
if (cur) await write(store, JSON.stringify(Object.assign({}, cur, { phase })));
|
|
66
|
+
}
|
|
67
|
+
function clear(store) {
|
|
68
|
+
return write(store, "");
|
|
69
|
+
}
|
|
70
|
+
async function recover(store) {
|
|
71
|
+
if (handled.has(store.dir)) return false;
|
|
72
|
+
const got = await readRaw(store);
|
|
73
|
+
if (!got.ok) return false;
|
|
74
|
+
const intent = got.intent;
|
|
75
|
+
if (!intent) {
|
|
76
|
+
handled.add(store.dir);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (await deps.workspaceMatchesTag(intent.messageId)) {
|
|
80
|
+
await clear(store);
|
|
81
|
+
handled.add(store.dir);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (!intent.safetyOk) {
|
|
85
|
+
deps.recordError("recall interrupted rollback detected, no safety snapshot: " + describeIntent(intent) + "\uFF08\u8BB0\u5F55\u4FDD\u7559\u4E8E " + intentFile(store) + "\uFF09");
|
|
86
|
+
handled.add(store.dir);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (deps.agentBusy(intent.root)) {
|
|
90
|
+
deps.recordError("recall interrupted rollback deferred (agent busy): " + describeIntent(intent));
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const reset = await deps.resetToSafety(store, intent.safetyId, intent.root);
|
|
94
|
+
if (!reset) {
|
|
95
|
+
deps.recordError("recall interrupted rollback rescue failed: " + describeIntent(intent) + "\uFF08\u8BB0\u5F55\u4FDD\u7559\u4E8E " + intentFile(store) + "\uFF09");
|
|
96
|
+
handled.add(store.dir);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
await clear(store);
|
|
100
|
+
handled.add(store.dir);
|
|
101
|
+
deps.recordError("recovered interrupted rollback: " + describeIntent(intent) + " \u2014 \u5DE5\u4F5C\u533A\u5DF2\u590D\u4F4D\u5230\u5B89\u5168\u5FEB\u7167");
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
return { begin, advance, clear, read, file: intentFile, recover };
|
|
105
|
+
}
|
|
106
|
+
export {
|
|
107
|
+
createIntentJournal
|
|
108
|
+
};
|
package/lib/maintenance.js
CHANGED
|
@@ -5,8 +5,9 @@ function selectOverLimitVictims(snapshots, limit) {
|
|
|
5
5
|
const byRoot = /* @__PURE__ */ new Map();
|
|
6
6
|
for (const [id, s] of snapshots.entries()) {
|
|
7
7
|
if (!s || !s.root) continue;
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const list = byRoot.get(s.root);
|
|
9
|
+
if (list) list.push({ id, time: s.time });
|
|
10
|
+
else byRoot.set(s.root, [{ id, time: s.time }]);
|
|
10
11
|
}
|
|
11
12
|
const victims = /* @__PURE__ */ new Map();
|
|
12
13
|
for (const [root, list] of byRoot) {
|
|
@@ -37,8 +38,9 @@ function createMaintenance(ctx, rt, snaps, config, deps = {}) {
|
|
|
37
38
|
const byRoot = /* @__PURE__ */ new Map();
|
|
38
39
|
for (const [id, s] of state.snapshots.entries()) {
|
|
39
40
|
if (!s || s.sessionId !== sessionId) continue;
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
const list = byRoot.get(s.root);
|
|
42
|
+
if (list) list.push(id);
|
|
43
|
+
else byRoot.set(s.root, [id]);
|
|
42
44
|
}
|
|
43
45
|
let purged = 0;
|
|
44
46
|
for (const [root, ids] of byRoot) {
|
|
@@ -51,6 +53,7 @@ function createMaintenance(ctx, rt, snaps, config, deps = {}) {
|
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
if (!store || !state.gitExe) continue;
|
|
56
|
+
if (!await snaps.guardStoreFormat(store)) continue;
|
|
54
57
|
try {
|
|
55
58
|
for (let i = 0; i < ids.length; i += 100) {
|
|
56
59
|
await rt.runShell(S.purgeTagsScript(store, state.gitExe, ids.slice(i, i + 100).map((id) => "snap-" + id)), { timeoutMs: 12e4, stdoutMaxBytes: 4096 });
|
|
@@ -99,6 +102,7 @@ function createMaintenance(ctx, rt, snaps, config, deps = {}) {
|
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
if (!store || !state.gitExe) continue;
|
|
105
|
+
if (!await snaps.guardStoreFormat(store)) continue;
|
|
102
106
|
try {
|
|
103
107
|
for (let i = 0; i < victims.length; i += 100) {
|
|
104
108
|
await rt.runShell(S.purgeTagsScript(store, state.gitExe, victims.slice(i, i + 100).map((v) => "snap-" + v.id)), { timeoutMs: 12e4, stdoutMaxBytes: 4096 });
|
|
@@ -127,6 +131,7 @@ function createMaintenance(ctx, rt, snaps, config, deps = {}) {
|
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
if (!store || !state.gitExe) continue;
|
|
134
|
+
if (!await snaps.guardStoreFormat(store)) continue;
|
|
130
135
|
try {
|
|
131
136
|
for (let i = 0; i < victims.length; i += 100) {
|
|
132
137
|
await rt.runShell(S.purgeTagsScript(store, state.gitExe, victims.slice(i, i + 100).map((v) => "snap-" + v.id)), { timeoutMs: 12e4, stdoutMaxBytes: 4096 });
|
package/lib/routes-core.js
CHANGED
|
@@ -2,7 +2,7 @@ import { ENV_HINTS } from "./diagnostics.js";
|
|
|
2
2
|
import { parseTreeId } from "./snapshots.js";
|
|
3
3
|
import { buildArtifactRootSegment, buildRootNotice } from "./exclude-patterns.js";
|
|
4
4
|
function createRoutesCore(deps) {
|
|
5
|
-
const { rt, snaps, state, cfg, supported, enqueue, agentBusy, rescueRollback, E } = deps;
|
|
5
|
+
const { rt, snaps, state, cfg, supported, enqueue, agentBusy, rescueRollback, intentJournal, E } = deps;
|
|
6
6
|
return {
|
|
7
7
|
"init": async (args) => {
|
|
8
8
|
if (!supported) {
|
|
@@ -17,15 +17,19 @@ function createRoutesCore(deps) {
|
|
|
17
17
|
await rt.ensureGit(root, store);
|
|
18
18
|
await snaps.loadIndex(root, sessionId);
|
|
19
19
|
await snaps.rebuildOrphans(root, sessionId);
|
|
20
|
+
if (store && await snaps.guardStoreFormat(store)) await intentJournal.recover(store);
|
|
20
21
|
rt.cleanupLegacy(root);
|
|
21
22
|
notice = {
|
|
22
23
|
gitMissing: state.gitExe === "",
|
|
23
24
|
homeFallback: store ? !store.home : false
|
|
24
25
|
};
|
|
25
26
|
const artifactSeg = buildArtifactRootSegment(root, cfg.baseExcludes, rt.isWin);
|
|
26
|
-
if (artifactSeg)
|
|
27
|
+
if (artifactSeg) {
|
|
28
|
+
notice.buildRootArtifactSeg = artifactSeg;
|
|
29
|
+
notice.buildRootNotice = buildRootNotice(artifactSeg);
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
|
-
return { ok: Boolean(root), root: root || null, notice, config: { refillDraft: cfg.refillDraft, archiveOriginal: cfg.archiveOriginal } };
|
|
32
|
+
return { ok: Boolean(root), root: root || null, notice, config: { refillDraft: cfg.refillDraft, archiveOriginal: cfg.archiveOriginal, locale: cfg.locale } };
|
|
29
33
|
},
|
|
30
34
|
"snapshot-info": async (args) => {
|
|
31
35
|
const id = args && args.messageId ? String(args.messageId) : "";
|
|
@@ -38,7 +42,9 @@ function createRoutesCore(deps) {
|
|
|
38
42
|
time: snap ? snap.time : null,
|
|
39
43
|
id,
|
|
40
44
|
...feedback,
|
|
41
|
-
|
|
45
|
+
// A4:段名与中文 notice 并存下发(client 优先段名本地取词,见 init 同处注释)
|
|
46
|
+
notice: artifactSeg ? buildRootNotice(artifactSeg) : void 0,
|
|
47
|
+
artifactSeg: artifactSeg || void 0
|
|
42
48
|
};
|
|
43
49
|
},
|
|
44
50
|
"preview": async (args) => {
|
|
@@ -69,6 +75,9 @@ function createRoutesCore(deps) {
|
|
|
69
75
|
if (!snap) return { ok: false, code: E.RECALL_NO_SNAPSHOT, message: "\u8BE5\u6D88\u606F\u6CA1\u6709\u53EF\u7528\u7684\u9879\u76EE\u5FEB\u7167" };
|
|
70
76
|
const store = state.stores.get(snap.root);
|
|
71
77
|
if (!store) return { ok: false, code: E.RECALL_NO_STORE, message: "\u5FEB\u7167\u5B58\u50A8\u4E0D\u53EF\u7528" };
|
|
78
|
+
if (!await snaps.guardStoreFormat(store)) {
|
|
79
|
+
return { ok: false, code: E.RECALL_FORMAT_BLOCKED, message: "\u78C1\u76D8\u683C\u5F0F\u4E0D\u53D7\u652F\u6301\uFF0C\u5DF2\u505C\u6B62\u5199\u5165\uFF1B\u8BE6\u89C1\u300C\u6700\u8FD1\u9519\u8BEF\u300D" };
|
|
80
|
+
}
|
|
72
81
|
if (agentBusy(sessionId, snap.root)) return { ok: false, code: E.RECALL_AGENT_BUSY, message: "Agent \u6B63\u5728\u8FD0\u884C\u4E2D\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u64A4\u56DE" };
|
|
73
82
|
const previewTreeId = args && typeof args.previewTreeId === "string" && args.previewTreeId ? args.previewTreeId : null;
|
|
74
83
|
if (!previewTreeId && args && typeof args.previewTotal === "number") {
|
|
@@ -90,12 +99,20 @@ function createRoutesCore(deps) {
|
|
|
90
99
|
if (previewTreeId && safetyTreeId && safetyTreeId !== previewTreeId) {
|
|
91
100
|
return { ok: false, code: E.RECALL_STALE, message: "\u9884\u89C8\u540E\u9879\u76EE\u6587\u4EF6\u53D1\u751F\u4E86\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u786E\u8BA4" };
|
|
92
101
|
}
|
|
102
|
+
await intentJournal.begin(store, snap.root, { messageId: id, safetyId, safetyOk });
|
|
93
103
|
const rolled = await snaps.rollbackFor(id);
|
|
94
|
-
if (rolled.ok)
|
|
95
|
-
|
|
104
|
+
if (rolled.ok) {
|
|
105
|
+
await intentJournal.clear(store);
|
|
106
|
+
return rolled;
|
|
107
|
+
}
|
|
108
|
+
await intentJournal.advance(store, "rescue");
|
|
109
|
+
const rescueResult = await rescueRollback(
|
|
96
110
|
{ runShell: rt.runShell, scripts: rt.scripts, gitExe: state.gitExe || "", recordError: rt.recordError },
|
|
97
111
|
{ root: snap.root, store, safetyId, safetyOk, rollbackError: rolled.error }
|
|
98
112
|
);
|
|
113
|
+
await intentJournal.clear(store);
|
|
114
|
+
if (rescueResult && rescueResult.message) rescueResult.message += "\uFF08\u4E2D\u65AD\u70B9\u8BB0\u5F55\uFF1A" + intentJournal.file(store) + "\uFF09";
|
|
115
|
+
return rescueResult;
|
|
99
116
|
});
|
|
100
117
|
if (!result.ok) return result;
|
|
101
118
|
const cutSeq = await snaps.resolveCutSeq(sessionId, id);
|
package/lib/routes-manage.js
CHANGED
|
@@ -109,8 +109,9 @@ function createRoutesManage(deps) {
|
|
|
109
109
|
const byRoot = /* @__PURE__ */ new Map();
|
|
110
110
|
for (const rec of records.values()) {
|
|
111
111
|
if (!match(rec) || !rec.root) continue;
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
const list = byRoot.get(rec.root);
|
|
113
|
+
if (list) list.push(rec.id);
|
|
114
|
+
else byRoot.set(rec.root, [rec.id]);
|
|
114
115
|
}
|
|
115
116
|
let deleted = 0;
|
|
116
117
|
await enqueue(async () => {
|
|
@@ -124,6 +125,7 @@ function createRoutesManage(deps) {
|
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
if (!store) continue;
|
|
128
|
+
if (!await snaps.guardStoreFormat(store)) continue;
|
|
127
129
|
try {
|
|
128
130
|
if (state.gitExe) {
|
|
129
131
|
const tags = rootIds.map((id) => "snap-" + id);
|
|
@@ -181,6 +183,10 @@ function createRoutesManage(deps) {
|
|
|
181
183
|
let clearedStores = 0;
|
|
182
184
|
let failed = 0;
|
|
183
185
|
for (const { store, root } of stores.values()) {
|
|
186
|
+
if (!await snaps.guardStoreFormat(store)) {
|
|
187
|
+
failed += 1;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
184
190
|
try {
|
|
185
191
|
const output = await rt.runShell(rt.scripts.listTagsScript(store, gitExe), { timeoutMs: 12e4, stdoutMaxBytes: 4194304 });
|
|
186
192
|
const tags = rt.scripts.stripBom(output).split(/\r?\n/).map((tag) => tag.trim()).filter((tag) => tag.indexOf("snap-") === 0);
|
|
@@ -279,7 +285,9 @@ function createRoutesManage(deps) {
|
|
|
279
285
|
refillDraft: cfg.refillDraft,
|
|
280
286
|
snapshotEnabled: cfg.snapshotEnabled,
|
|
281
287
|
archiveOriginal: cfg.archiveOriginal,
|
|
282
|
-
retentionDays: cfg.retentionDays
|
|
288
|
+
retentionDays: cfg.retentionDays,
|
|
289
|
+
// A4:界面语言偏好随 values 下发(设置卡片据此渲染下拉并即时切语言)
|
|
290
|
+
locale: cfg.locale
|
|
283
291
|
},
|
|
284
292
|
overridden,
|
|
285
293
|
envLocks,
|
|
@@ -314,6 +322,11 @@ function createRoutesManage(deps) {
|
|
|
314
322
|
if (!Array.isArray(patch.baseExcludes)) return { ok: false, code: E.RECALL_BAD_TYPE, message: "baseExcludes \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4" };
|
|
315
323
|
clean.baseExcludes = patch.baseExcludes.filter((p) => typeof p === "string" && p.trim());
|
|
316
324
|
}
|
|
325
|
+
if (patch.locale !== void 0) {
|
|
326
|
+
const v = String(patch.locale);
|
|
327
|
+
if (v !== "auto" && v !== "zh" && v !== "en") return { ok: false, code: E.RECALL_BAD_TYPE, message: "\u754C\u9762\u8BED\u8A00\u5FC5\u987B\u662F auto / zh / en \u4E4B\u4E00" };
|
|
328
|
+
clean.locale = v;
|
|
329
|
+
}
|
|
317
330
|
if (!Object.keys(clean).length) return { ok: false, code: E.RECALL_EMPTY_PATCH, message: "\u6CA1\u6709\u53EF\u5199\u5165\u7684\u914D\u7F6E\u5B57\u6BB5" };
|
|
318
331
|
let settings = null;
|
|
319
332
|
try {
|
|
@@ -378,14 +391,16 @@ function createRoutesManage(deps) {
|
|
|
378
391
|
return { ok: true, titles: out };
|
|
379
392
|
}
|
|
380
393
|
if (op === "messages") {
|
|
381
|
-
const
|
|
394
|
+
const reqsRaw = args && args.requests;
|
|
395
|
+
const reqs = Array.isArray(reqsRaw) ? reqsRaw.slice(0, 200) : [];
|
|
382
396
|
const bySession = /* @__PURE__ */ new Map();
|
|
383
397
|
for (const r of reqs) {
|
|
384
398
|
const sid = r && r.sessionId ? String(r.sessionId) : null;
|
|
385
399
|
const mid = r && r.messageId ? String(r.messageId) : null;
|
|
386
400
|
if (!sid || !mid) continue;
|
|
387
|
-
|
|
388
|
-
|
|
401
|
+
const midList = bySession.get(sid);
|
|
402
|
+
if (midList) midList.push(mid);
|
|
403
|
+
else bySession.set(sid, [mid]);
|
|
389
404
|
}
|
|
390
405
|
const texts = {};
|
|
391
406
|
await runLimited(Array.from(bySession.entries()).map(([sid, mids]) => async () => {
|
|
@@ -492,6 +507,9 @@ function createRoutesManage(deps) {
|
|
|
492
507
|
}
|
|
493
508
|
}
|
|
494
509
|
if (!store) return { ok: false, code: E.RECALL_NO_SNAPSHOT, message: "\u8BE5\u5FEB\u7167\u4E0D\u5B58\u5728" };
|
|
510
|
+
if (!await snaps.guardStoreFormat(store)) {
|
|
511
|
+
return { ok: false, code: E.RECALL_FORMAT_BLOCKED, message: "\u78C1\u76D8\u683C\u5F0F\u4E0D\u53D7\u652F\u6301\uFF0C\u5DF2\u505C\u6B62\u5199\u5165\uFF1B\u8BE6\u89C1\u300C\u6700\u8FD1\u9519\u8BEF\u300D" };
|
|
512
|
+
}
|
|
495
513
|
const finalStore = store;
|
|
496
514
|
const finalRoot = snapRoot;
|
|
497
515
|
await enqueue(async () => {
|
package/lib/scripts.posix.js
CHANGED
|
@@ -363,6 +363,9 @@ function indexReadCmd(dir) {
|
|
|
363
363
|
function lineageReadCmd(dir) {
|
|
364
364
|
return "cat " + psq(dir + "/lineage.json") + " 2>/dev/null || true";
|
|
365
365
|
}
|
|
366
|
+
function fileReadCmd(file) {
|
|
367
|
+
return "cat " + psq(file) + " 2>/dev/null || true";
|
|
368
|
+
}
|
|
366
369
|
function legacyRmScript(path) {
|
|
367
370
|
return "rm -rf -- " + psq(path);
|
|
368
371
|
}
|
|
@@ -441,6 +444,7 @@ export {
|
|
|
441
444
|
ensureGitScript,
|
|
442
445
|
excludeDumpScript,
|
|
443
446
|
excludeReadCmd,
|
|
447
|
+
fileReadCmd,
|
|
444
448
|
fileWriteStdinCmd,
|
|
445
449
|
gcScript,
|
|
446
450
|
indexReadCmd,
|
package/lib/scripts.pwsh.js
CHANGED
|
@@ -420,6 +420,9 @@ function indexReadCmd(dir) {
|
|
|
420
420
|
function lineageReadCmd(dir) {
|
|
421
421
|
return "Get-Content -LiteralPath " + psq(dir + "\\lineage.json") + " -Raw -Encoding UTF8 -ErrorAction SilentlyContinue";
|
|
422
422
|
}
|
|
423
|
+
function fileReadCmd(file) {
|
|
424
|
+
return "if (Test-Path -LiteralPath " + psq(file) + ") { Get-Content -LiteralPath " + psq(file) + " -Raw -Encoding UTF8 -ErrorAction SilentlyContinue }";
|
|
425
|
+
}
|
|
423
426
|
function legacyRmScript(path) {
|
|
424
427
|
return "Remove-Item -Recurse -Force -LiteralPath " + psq(path) + " -ErrorAction SilentlyContinue";
|
|
425
428
|
}
|
|
@@ -525,6 +528,7 @@ export {
|
|
|
525
528
|
ensureGitScript,
|
|
526
529
|
excludeDumpScript,
|
|
527
530
|
excludeReadCmd,
|
|
531
|
+
fileReadCmd,
|
|
528
532
|
fileWriteStdinCmd,
|
|
529
533
|
gcScript,
|
|
530
534
|
homeDirScript,
|
package/lib/snapshots.js
CHANGED
|
@@ -140,6 +140,20 @@ function parseTagsWithTime(text) {
|
|
|
140
140
|
}
|
|
141
141
|
return out;
|
|
142
142
|
}
|
|
143
|
+
function judgeStoreFormat(raw, supported) {
|
|
144
|
+
if (raw === null) return { ok: false, v: null, reason: "unreadable" };
|
|
145
|
+
const t = String(raw == null ? "" : raw).trim();
|
|
146
|
+
let v;
|
|
147
|
+
if (!t) {
|
|
148
|
+
v = 1;
|
|
149
|
+
} else {
|
|
150
|
+
if (!/^\d+$/.test(t)) return { ok: false, v: null, reason: "corrupt" };
|
|
151
|
+
v = parseInt(t, 10);
|
|
152
|
+
if (v < 1) return { ok: false, v: null, reason: "corrupt" };
|
|
153
|
+
}
|
|
154
|
+
if (v > supported) return { ok: false, v, reason: "future" };
|
|
155
|
+
return { ok: true, v, reason: t ? "ok" : "absent" };
|
|
156
|
+
}
|
|
143
157
|
function createSnapshots(ctx, rt, config) {
|
|
144
158
|
const sessions = ctx.sessions;
|
|
145
159
|
const state = rt.state;
|
|
@@ -149,9 +163,58 @@ function createSnapshots(ctx, rt, config) {
|
|
|
149
163
|
const FUSE_BACKOFF_BASE_MS = 5 * 60 * 1e3;
|
|
150
164
|
const FUSE_BACKOFF_CAP_MS = 60 * 60 * 1e3;
|
|
151
165
|
const snapFailures = /* @__PURE__ */ new Map();
|
|
166
|
+
const SUPPORTED_FORMAT = 1;
|
|
167
|
+
const FORMAT_OK_TTL_MS = 3e4;
|
|
168
|
+
const formatOkAt = /* @__PURE__ */ new Map();
|
|
169
|
+
const formatBlockedAt = /* @__PURE__ */ new Map();
|
|
170
|
+
function formatFile(store) {
|
|
171
|
+
return store.dir + (rt.isWin ? "\\" : "/") + "format";
|
|
172
|
+
}
|
|
173
|
+
async function readFormatRaw(store) {
|
|
174
|
+
try {
|
|
175
|
+
return S.stripBom(await rt.runShell(S.fileReadCmd(formatFile(store)), { stdoutMaxBytes: 4096 }));
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function storeFormatVerdict(store) {
|
|
181
|
+
const at = formatOkAt.get(store.dir);
|
|
182
|
+
if (at !== void 0 && Date.now() - at < FORMAT_OK_TTL_MS) return { ok: true, v: SUPPORTED_FORMAT, reason: "ok" };
|
|
183
|
+
const verdict = judgeStoreFormat(await readFormatRaw(store), SUPPORTED_FORMAT);
|
|
184
|
+
if (verdict.ok && verdict.reason === "ok") formatOkAt.set(store.dir, Date.now());
|
|
185
|
+
return verdict;
|
|
186
|
+
}
|
|
187
|
+
function formatBlockedThrottled(store, verdict) {
|
|
188
|
+
const last = formatBlockedAt.get(store.dir) || 0;
|
|
189
|
+
if (Date.now() - last < 5 * 60 * 1e3) return;
|
|
190
|
+
formatBlockedAt.set(store.dir, Date.now());
|
|
191
|
+
const detail = verdict.reason === "future" ? "\u78C1\u76D8\u683C\u5F0F\u6807\u8BB0\u4E3A v" + verdict.v + "\uFF08\u672C\u63D2\u4EF6\u652F\u6301 v" + SUPPORTED_FORMAT + "\uFF09\uFF1A\u5199\u5165\u5DF2\u6682\u505C\uFF08\u5FEB\u7167/\u64A4\u56DE/\u6E05\u7406\uFF09\uFF0C\u5217\u8868\u4ECD\u53EF\u67E5\u770B\uFF1B\u8BF7\u5347\u7EA7\u63D2\u4EF6" : verdict.reason === "corrupt" ? "\u78C1\u76D8\u683C\u5F0F\u6807\u8BB0\u5185\u5BB9\u975E\u6CD5\uFF08\u5E94\u4E3A " + SUPPORTED_FORMAT + "\uFF09\uFF1A\u5199\u5165\u5DF2\u6682\u505C\uFF08\u5FEB\u7167/\u64A4\u56DE/\u6E05\u7406\uFF09\uFF1B\u8BF7\u68C0\u67E5\u8BE5 store \u76EE\u5F55\u4E0B\u7684 format \u6587\u4EF6" : "\u8BFB\u4E0D\u5230\u5FEB\u7167\u5E93\u7684\u78C1\u76D8\u683C\u5F0F\u6807\u8BB0\uFF08\u73AF\u5883\u672A\u5C31\u7EEA\u6216\u6587\u4EF6\u4E0D\u53EF\u8BFB\uFF09\uFF1A\u672C\u6B21\u5199\u5165\u5DF2\u6682\u505C\uFF0C\u4E0B\u4E00\u6761\u6D88\u606F\u81EA\u52A8\u91CD\u8BD5\uFF1B\u6301\u7EED\u51FA\u73B0\u8BF7\u68C0\u67E5\u8BE5 store \u76EE\u5F55\u6743\u9650";
|
|
192
|
+
rt.recordError("recall store format blocked: " + detail);
|
|
193
|
+
}
|
|
194
|
+
async function guardStoreFormat(store) {
|
|
195
|
+
const verdict = await storeFormatVerdict(store);
|
|
196
|
+
if (verdict.ok) return true;
|
|
197
|
+
formatBlockedThrottled(store, verdict);
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
async function stampStoreFormat(store, verdict) {
|
|
201
|
+
if (!verdict.ok || verdict.reason !== "absent") return;
|
|
202
|
+
try {
|
|
203
|
+
await rt.writeTextViaShell(formatFile(store), String(SUPPORTED_FORMAT));
|
|
204
|
+
formatOkAt.set(store.dir, Date.now());
|
|
205
|
+
} catch (error) {
|
|
206
|
+
rt.recordError("recall format stamp failed: " + String(error));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
152
209
|
async function saveIndex(root, sessionId) {
|
|
153
210
|
const store = state.stores.get(root);
|
|
154
211
|
if (!store) return;
|
|
212
|
+
const verdict = await storeFormatVerdict(store);
|
|
213
|
+
if (!verdict.ok) {
|
|
214
|
+
formatBlockedThrottled(store, verdict);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
await stampStoreFormat(store, verdict);
|
|
155
218
|
const entries = Array.from(state.snapshots.entries()).filter(([, s]) => s.root === root).map(([id, s]) => {
|
|
156
219
|
const rec = { id, time: s.time, root: s.root, sessionId: s.sessionId };
|
|
157
220
|
const fb = state.snapFeedback.get(id);
|
|
@@ -168,6 +231,7 @@ function createSnapshots(ctx, rt, config) {
|
|
|
168
231
|
if (state.indexLoaded.has(root)) return;
|
|
169
232
|
const store = state.stores.get(root);
|
|
170
233
|
if (!store) return;
|
|
234
|
+
if (!await guardStoreFormat(store)) return;
|
|
171
235
|
let raw = "";
|
|
172
236
|
let truncated = false;
|
|
173
237
|
try {
|
|
@@ -259,6 +323,7 @@ function createSnapshots(ctx, rt, config) {
|
|
|
259
323
|
async function recordLineage(root, childId, parentId) {
|
|
260
324
|
const store = state.stores.get(root);
|
|
261
325
|
if (!store) return;
|
|
326
|
+
if (!await guardStoreFormat(store)) return;
|
|
262
327
|
const sep = rt.isWin ? "\\" : "/";
|
|
263
328
|
const existing = await loadLineage(root);
|
|
264
329
|
if (!existing.some((e) => e.childId === childId && e.parentId === parentId)) {
|
|
@@ -313,6 +378,7 @@ function createSnapshots(ctx, rt, config) {
|
|
|
313
378
|
if (buildArtifactRootSegment(root, BASE(), rt.isWin)) return;
|
|
314
379
|
let store = await rt.resolveStore(root);
|
|
315
380
|
store = await rt.tryUpgradeToHome(root);
|
|
381
|
+
if (!await guardStoreFormat(store)) return;
|
|
316
382
|
const g = await rt.ensureGit(root, store);
|
|
317
383
|
if (!g.ok) {
|
|
318
384
|
setFeedback(messageId, { failed: true, ...buildFeedbackError(g.error || "\u672A\u77E5\u539F\u56E0") });
|
|
@@ -336,7 +402,10 @@ function createSnapshots(ctx, rt, config) {
|
|
|
336
402
|
const keep = rec && (rec.failed || Array.isArray(rec.skipped) && rec.skipped.length);
|
|
337
403
|
if (keep) state.snapFeedback.set(id, rec);
|
|
338
404
|
else state.snapFeedback.delete(id);
|
|
339
|
-
if (state.snapFeedback.size > 200)
|
|
405
|
+
if (state.snapFeedback.size > 200) {
|
|
406
|
+
const oldest = state.snapFeedback.keys().next().value;
|
|
407
|
+
if (oldest !== void 0) state.snapFeedback.delete(oldest);
|
|
408
|
+
}
|
|
340
409
|
}
|
|
341
410
|
async function feedbackFor(sessionId, messageId) {
|
|
342
411
|
const rec = state.snapFeedback.get(String(messageId || ""));
|
|
@@ -391,8 +460,8 @@ function createSnapshots(ctx, rt, config) {
|
|
|
391
460
|
if (!m) {
|
|
392
461
|
return { ok: false, partial: true, error: "\u56DE\u9000\u811A\u672C\u672A\u6B63\u5E38\u5B8C\u6210\uFF08\u5DE5\u4F5C\u533A\u53EF\u80FD\u5904\u4E8E\u534A\u56DE\u9000\u72B6\u6001\uFF09\uFF1A" + text.slice(0, 300) };
|
|
393
462
|
}
|
|
394
|
-
const deleted = parseInt(m[1], 10);
|
|
395
|
-
const restored = parseInt(m[2], 10);
|
|
463
|
+
const deleted = parseInt(m[1] ?? "", 10);
|
|
464
|
+
const restored = parseInt(m[2] ?? "", 10);
|
|
396
465
|
return { ok: true, count: (Number.isNaN(deleted) ? 0 : deleted) + (Number.isNaN(restored) ? 0 : restored) };
|
|
397
466
|
} catch (error) {
|
|
398
467
|
const re = error;
|
|
@@ -461,11 +530,12 @@ function createSnapshots(ctx, rt, config) {
|
|
|
461
530
|
return [];
|
|
462
531
|
}
|
|
463
532
|
}
|
|
464
|
-
return { saveIndex, loadIndex, readExclude, writeExclude, rebuildOrphans, captureSnapshot, diffFor, rollbackFor, resolveCutSeq, resolveStaleQueueItemIds, feedbackFor, loadLineage, recordLineage };
|
|
533
|
+
return { saveIndex, loadIndex, readExclude, writeExclude, rebuildOrphans, captureSnapshot, diffFor, rollbackFor, resolveCutSeq, resolveStaleQueueItemIds, feedbackFor, loadLineage, recordLineage, guardStoreFormat };
|
|
465
534
|
}
|
|
466
535
|
export {
|
|
467
536
|
createSnapshots,
|
|
468
537
|
isSafetySnapshotId,
|
|
538
|
+
judgeStoreFormat,
|
|
469
539
|
parseChanges,
|
|
470
540
|
parseDiffOutput,
|
|
471
541
|
parseSkipped,
|