dsh-rewind-plugin 0.9.0 → 0.10.0-alpha.1
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.en.md +2 -2
- package/README.md +2 -2
- package/docs/architecture.md +12 -8
- package/docs/format.md +3 -4
- package/docs/harness-reference.md +1 -4
- package/lib/client.js +12 -6
- package/lib/index.js +13 -628
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/locales.d.ts +0 -20
- package/lib/types/snapshot-cleanup.d.ts +12 -48
- package/package.json +44 -44
- package/lib/types/rewind-fix.d.ts +0 -111
- package/lib/types/rewind-marker-repair.d.ts +0 -110
- package/lib/types/session-log-io.d.ts +0 -57
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
3
|
-
import { unlink
|
|
3
|
+
import { unlink } from "node:fs/promises";
|
|
4
4
|
import "@deepseek-ai/schemastery";
|
|
5
5
|
|
|
6
6
|
// src/locales.ts
|
|
@@ -50,27 +50,7 @@ var en = {
|
|
|
50
50
|
"cleanup.clearActive": "Could not clear session {sessionId}: the session is still running and could not be stopped. Try again once it is idle.",
|
|
51
51
|
"cleanup.clearCancelled": "Clear cancelled.",
|
|
52
52
|
"cleanup.clearFailed": "Could not clear session {sessionId}: {detail}.",
|
|
53
|
-
"cleanup.usage": "Usage:\n /snapshot-auto-cleanup show status\n /snapshot-auto-cleanup on|off enable/disable auto-cleanup\n /snapshot-auto-cleanup max-age <days> set the idle cutoff\n /snapshot-auto-cleanup run [--apply] dry-run, or execute with --apply\n /snapshot-auto-cleanup run --current [--apply] dry-run/clear this session's snapshots"
|
|
54
|
-
"rewindfix.description": "Update legacy rewind markers (A/B) in closed sessions to the current form-C shape",
|
|
55
|
-
"rewindfix.inputHint": "no args = dry-run preview; --apply = execute (takes minutes)",
|
|
56
|
-
"rewindfix.dryRun": "=== dry-run: {scan} sessions scanned / {will} will be updated / {skip} skipped / {fail} failed ({secs}s) ===",
|
|
57
|
-
"rewindfix.done": "=== done: {repair} updated / {skip} skipped / {fail} failed ({secs}s) ===",
|
|
58
|
-
"rewindfix.cancelled": "=== cancelled ===",
|
|
59
|
-
"rewindfix.launcherGuard": "! This session itself needs updating; it is open and cannot be modified in place. Start a NEW session (or close it) \u2014 a closed session is updated when no window holds it.",
|
|
60
|
-
"rewindfix.counts": "A={a} B={b} \u2192 C={c}",
|
|
61
|
-
"rewindfix.args": "\xB7 rewired args target {n}",
|
|
62
|
-
"rewindfix.writeOk": "\u21B3 snapCleared \u2713 written \u2713",
|
|
63
|
-
"rewindfix.skip.loaded": "SKIP (loaded)",
|
|
64
|
-
"rewindfix.skip.noMarkers": "SKIP (no markers)",
|
|
65
|
-
"rewindfix.skip.noArtifact": "SKIP (no artifact)",
|
|
66
|
-
"rewindfix.skip.loadedNow": "SKIP (loaded now)",
|
|
67
|
-
"rewindfix.fail.unreadable": "FAIL (unreadable) {error}",
|
|
68
|
-
"rewindfix.fail.repair": "FAIL (update) {error}",
|
|
69
|
-
"rewindfix.fail.locked": "FAIL (locked) {error}",
|
|
70
|
-
"rewindfix.fail.generic": "FAIL: {error}",
|
|
71
|
-
"rewindfix.persistenceUnavailable": "Session persistence is unavailable; cannot run the rewind-fix command.",
|
|
72
|
-
"rewindfix.sessionStoreUnavailable": "The session store is unavailable; cannot run the rewind-fix command.",
|
|
73
|
-
"rewindfix.locked": "locked by another process"
|
|
53
|
+
"cleanup.usage": "Usage:\n /snapshot-auto-cleanup show status\n /snapshot-auto-cleanup on|off enable/disable auto-cleanup\n /snapshot-auto-cleanup max-age <days> set the idle cutoff\n /snapshot-auto-cleanup run [--apply] dry-run, or execute with --apply\n /snapshot-auto-cleanup run --current [--apply] dry-run/clear this session's snapshots"
|
|
74
54
|
};
|
|
75
55
|
var zh = {
|
|
76
56
|
"usage.title": "\u7528\u6CD5\uFF1A",
|
|
@@ -118,27 +98,7 @@ var zh = {
|
|
|
118
98
|
"cleanup.clearActive": "\u65E0\u6CD5\u6E05\u9664\u4F1A\u8BDD {sessionId}\uFF1A\u4F1A\u8BDD\u4ECD\u5728\u8FD0\u884C\u4E14\u672A\u80FD\u505C\u6B62\uFF0C\u8BF7\u5F85\u5176\u7A7A\u95F2\u540E\u91CD\u8BD5\u3002",
|
|
119
99
|
"cleanup.clearCancelled": "\u6E05\u7A7A\u5DF2\u53D6\u6D88\u3002",
|
|
120
100
|
"cleanup.clearFailed": "\u65E0\u6CD5\u6E05\u9664\u4F1A\u8BDD {sessionId}\uFF1A{detail}\u3002",
|
|
121
|
-
"cleanup.usage": "\u7528\u6CD5\uFF1A\n /snapshot-auto-cleanup \u67E5\u770B\u72B6\u6001\n /snapshot-auto-cleanup on|off \u5F00\u542F/\u5173\u95ED\u81EA\u52A8\u6E05\u7406\n /snapshot-auto-cleanup max-age <\u5929\u6570> \u8BBE\u7F6E\u5931\u6D3B\u9608\u503C\uFF08\u5929\uFF09\n /snapshot-auto-cleanup run [--apply] \u9884\u6F14\uFF0C\u6216\u52A0 --apply \u6267\u884C\n /snapshot-auto-cleanup run --current [--apply] \u9884\u6F14/\u6E05\u9664\u672C\u4F1A\u8BDD\u5FEB\u7167"
|
|
122
|
-
"rewindfix.description": "\u5C06\u5DF2\u5173\u95ED\u4F1A\u8BDD\u4E2D\u7684\u65E7\u7248 rewind \u6807\u8BB0\uFF08A/B\uFF09\u66F4\u65B0\u4E3A\u5F53\u524D form-C \u5F62\u6001",
|
|
123
|
-
"rewindfix.inputHint": "\u65E0\u53C2 = dry-run \u9884\u89C8\uFF1B--apply = \u6267\u884C\uFF08\u9700\u6570\u5206\u949F\uFF09",
|
|
124
|
-
"rewindfix.dryRun": "=== \u9884\u6F14\uFF1A\u626B\u63CF {scan} \u4E2A\u4F1A\u8BDD / \u5C06\u66F4\u65B0 {will} \u4E2A / \u8DF3\u8FC7 {skip} \u4E2A / \u5931\u8D25 {fail} \u4E2A\uFF08{secs}s\uFF09===",
|
|
125
|
-
"rewindfix.done": "=== \u5B8C\u6210\uFF1A\u66F4\u65B0 {repair} \u4E2A / \u8DF3\u8FC7 {skip} \u4E2A / \u5931\u8D25 {fail} \u4E2A\uFF08{secs}s\uFF09===",
|
|
126
|
-
"rewindfix.cancelled": "=== \u5DF2\u53D6\u6D88 ===",
|
|
127
|
-
"rewindfix.launcherGuard": "! \u4F60\u6B63\u5728\u4E00\u4E2A\u672C\u8EAB\u9700\u8981\u66F4\u65B0\u7684\u4F1A\u8BDD\u91CC\u8FD0\u884C\uFF1B\u5B83\u5904\u4E8E\u6253\u5F00\u72B6\u6001\uFF0C\u65E0\u6CD5\u539F\u5730\u4FEE\u6539\u3002\u8BF7\u65B0\u5EFA\u4E00\u4E2A\u4F1A\u8BDD\uFF08\u6216\u5173\u95ED\u5B83\uFF09\u2014\u2014\u5173\u95ED\u7684\u4F1A\u8BDD\u4F1A\u5728\u6CA1\u6709\u7A97\u53E3\u6301\u6709\u5B83\u65F6\u88AB\u66F4\u65B0\u3002",
|
|
128
|
-
"rewindfix.counts": "A={a} B={b} \u2192 C={c}",
|
|
129
|
-
"rewindfix.args": "\xB7 \u4FEE\u6B63 args \u76EE\u6807 {n}",
|
|
130
|
-
"rewindfix.writeOk": "\u21B3 \u5FEB\u7167\u5DF2\u6E05 \u2713 \u5DF2\u5199\u56DE \u2713",
|
|
131
|
-
"rewindfix.skip.loaded": "\u8DF3\u8FC7\uFF08\u5DF2\u52A0\u8F7D\uFF09",
|
|
132
|
-
"rewindfix.skip.noMarkers": "\u8DF3\u8FC7\uFF08\u65E0\u6807\u8BB0\uFF09",
|
|
133
|
-
"rewindfix.skip.noArtifact": "\u8DF3\u8FC7\uFF08\u65E0\u4EA7\u7269\uFF09",
|
|
134
|
-
"rewindfix.skip.loadedNow": "\u8DF3\u8FC7\uFF08\u73B0\u5DF2\u52A0\u8F7D\uFF09",
|
|
135
|
-
"rewindfix.fail.unreadable": "\u5931\u8D25\uFF08\u65E0\u6CD5\u8BFB\u53D6\uFF09{error}",
|
|
136
|
-
"rewindfix.fail.repair": "\u5931\u8D25\uFF08\u66F4\u65B0\uFF09{error}",
|
|
137
|
-
"rewindfix.fail.locked": "\u5931\u8D25\uFF08\u88AB\u9501\u5B9A\uFF09{error}",
|
|
138
|
-
"rewindfix.fail.generic": "\u5931\u8D25\uFF1A{error}",
|
|
139
|
-
"rewindfix.persistenceUnavailable": "\u4F1A\u8BDD\u6301\u4E45\u5C42\u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u6267\u884C rewind-fix \u547D\u4EE4\u3002",
|
|
140
|
-
"rewindfix.sessionStoreUnavailable": "\u4F1A\u8BDD\u5B58\u50A8\u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u6267\u884C rewind-fix \u547D\u4EE4\u3002",
|
|
141
|
-
"rewindfix.locked": "\u5DF2\u88AB\u53E6\u4E00\u8FDB\u7A0B\u9501\u5B9A"
|
|
101
|
+
"cleanup.usage": "\u7528\u6CD5\uFF1A\n /snapshot-auto-cleanup \u67E5\u770B\u72B6\u6001\n /snapshot-auto-cleanup on|off \u5F00\u542F/\u5173\u95ED\u81EA\u52A8\u6E05\u7406\n /snapshot-auto-cleanup max-age <\u5929\u6570> \u8BBE\u7F6E\u5931\u6D3B\u9608\u503C\uFF08\u5929\uFF09\n /snapshot-auto-cleanup run [--apply] \u9884\u6F14\uFF0C\u6216\u52A0 --apply \u6267\u884C\n /snapshot-auto-cleanup run --current [--apply] \u9884\u6F14/\u6E05\u9664\u672C\u4F1A\u8BDD\u5FEB\u7167"
|
|
142
102
|
};
|
|
143
103
|
var HOST_DICTS = { en, zh };
|
|
144
104
|
function translate(lang, key, params = {}) {
|
|
@@ -1342,533 +1302,11 @@ async function reconcileTracked(store, sessionId, anchorSeq, tracked, probe = de
|
|
|
1342
1302
|
return recorded;
|
|
1343
1303
|
}
|
|
1344
1304
|
|
|
1345
|
-
// src/rewind-marker-repair.ts
|
|
1346
|
-
var REWIND_MARKER_SOURCE = { kind: "plugin", plugin: "dsh-rewind" };
|
|
1347
|
-
var REWIND_MARKER_CONTENT = [];
|
|
1348
|
-
function buildRewindMarkerData(id) {
|
|
1349
|
-
return {
|
|
1350
|
-
role: "user",
|
|
1351
|
-
content: REWIND_MARKER_CONTENT,
|
|
1352
|
-
source: { ...REWIND_MARKER_SOURCE },
|
|
1353
|
-
...id !== void 0 ? { id } : {}
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
function isReplaceSurfaceOp(value) {
|
|
1357
|
-
return typeof value === "object" && value !== null && value.op === "replace" && typeof value.start === "number" && typeof value.end === "number";
|
|
1358
|
-
}
|
|
1359
|
-
function isLegacyRewindMarker(event) {
|
|
1360
|
-
return isLegacyRewindMarkerRow(asRow(event));
|
|
1361
|
-
}
|
|
1362
|
-
function isFormCMarker(event) {
|
|
1363
|
-
return isFormCMarkerRow(asRow(event));
|
|
1364
|
-
}
|
|
1365
|
-
function repairRewindMarkers(input) {
|
|
1366
|
-
const rows = input.map(toRow);
|
|
1367
|
-
const stats = { a: 0, b: 0, c: 0, removedGhosts: 0 };
|
|
1368
|
-
for (const row of rows) {
|
|
1369
|
-
if (isFormCMarkerRow(row)) stats.c += 1;
|
|
1370
|
-
}
|
|
1371
|
-
const removed = /* @__PURE__ */ new Set();
|
|
1372
|
-
for (let i = 0; i < rows.length; i += 1) {
|
|
1373
|
-
const row = rows[i];
|
|
1374
|
-
if (!isLegacyRewindMarkerRow(row)) continue;
|
|
1375
|
-
const prev = rows[i - 1];
|
|
1376
|
-
const next = rows[i + 1];
|
|
1377
|
-
const hasBefore = prev !== void 0 && prev.type === "step/start";
|
|
1378
|
-
const hasAfter = next !== void 0 && next.type === "step/end";
|
|
1379
|
-
if (hasBefore && hasAfter) {
|
|
1380
|
-
removed.add(i - 1);
|
|
1381
|
-
removed.add(i + 1);
|
|
1382
|
-
stats.b += 1;
|
|
1383
|
-
stats.removedGhosts += 2;
|
|
1384
|
-
} else {
|
|
1385
|
-
stats.a += 1;
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
const survivors = [];
|
|
1389
|
-
const oldToNew = /* @__PURE__ */ new Map();
|
|
1390
|
-
for (let i = 0; i < rows.length; i += 1) {
|
|
1391
|
-
if (removed.has(i)) continue;
|
|
1392
|
-
const original = rows[i];
|
|
1393
|
-
const newSeq = survivors.length;
|
|
1394
|
-
oldToNew.set(original.seq, newSeq);
|
|
1395
|
-
survivors.push(isLegacyRewindMarkerRow(original) ? toEvent(retargetToC(original, newSeq)) : toEvent({ ...original, seq: newSeq }));
|
|
1396
|
-
}
|
|
1397
|
-
const mapSeq = (old) => {
|
|
1398
|
-
const mapped = oldToNew.get(old);
|
|
1399
|
-
if (mapped === void 0) {
|
|
1400
|
-
throw new Error(`rewind-fix: reference targets consumed seq ${old} (not a surviving event)`);
|
|
1401
|
-
}
|
|
1402
|
-
return mapped;
|
|
1403
|
-
};
|
|
1404
|
-
const events = survivors.map((event) => toEvent(remapReferences(toRow(event), mapSeq)));
|
|
1405
|
-
return { events, mapSeq: oldToNew, stats };
|
|
1406
|
-
}
|
|
1407
|
-
function isLegacyRewindMarkerRow(row) {
|
|
1408
|
-
if (row.type !== "assistant/message") return false;
|
|
1409
|
-
const message = row.data["message"];
|
|
1410
|
-
const source = message?.["source"];
|
|
1411
|
-
if (source?.["kind"] !== "model") return false;
|
|
1412
|
-
if (source?.["provider"] !== "dsh-rewind" || source?.["model"] !== "rewind-marker") return false;
|
|
1413
|
-
return isReplaceSurfaceOp(row.surfaceOp);
|
|
1414
|
-
}
|
|
1415
|
-
function isFormCMarkerRow(row) {
|
|
1416
|
-
if (row.type !== "user/message") return false;
|
|
1417
|
-
const source = row.data["source"];
|
|
1418
|
-
return source?.["kind"] === "plugin" && source?.["plugin"] === "dsh-rewind";
|
|
1419
|
-
}
|
|
1420
|
-
function asRow(event) {
|
|
1421
|
-
const e = event;
|
|
1422
|
-
return {
|
|
1423
|
-
type: e.type,
|
|
1424
|
-
seq: e.seq,
|
|
1425
|
-
time: e.time,
|
|
1426
|
-
data: { ...e.data },
|
|
1427
|
-
..."surfaceOp" in e ? { surfaceOp: e.surfaceOp } : {},
|
|
1428
|
-
...Array.isArray(e.sourceEventSeqs) ? { sourceEventSeqs: [...e.sourceEventSeqs] } : {},
|
|
1429
|
-
..."ignorable" in e ? { ignorable: e.ignorable } : {}
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
function toRow(event) {
|
|
1433
|
-
return asRow(event);
|
|
1434
|
-
}
|
|
1435
|
-
function toEvent(row) {
|
|
1436
|
-
const e = {
|
|
1437
|
-
type: row.type,
|
|
1438
|
-
seq: row.seq,
|
|
1439
|
-
time: row.time,
|
|
1440
|
-
data: row.data
|
|
1441
|
-
};
|
|
1442
|
-
if (row.surfaceOp !== void 0) e["surfaceOp"] = row.surfaceOp;
|
|
1443
|
-
if (row.sourceEventSeqs !== void 0) e["sourceEventSeqs"] = row.sourceEventSeqs;
|
|
1444
|
-
if (row.ignorable !== void 0) e["ignorable"] = row.ignorable;
|
|
1445
|
-
return e;
|
|
1446
|
-
}
|
|
1447
|
-
function retargetToC(row, seq) {
|
|
1448
|
-
const message = row.data["message"];
|
|
1449
|
-
const id = typeof message?.["id"] === "string" ? message["id"] : void 0;
|
|
1450
|
-
return {
|
|
1451
|
-
type: "user/message",
|
|
1452
|
-
seq,
|
|
1453
|
-
time: row.time,
|
|
1454
|
-
data: buildRewindMarkerData(id),
|
|
1455
|
-
surfaceOp: row.surfaceOp,
|
|
1456
|
-
sourceEventSeqs: row.sourceEventSeqs
|
|
1457
|
-
};
|
|
1458
|
-
}
|
|
1459
|
-
function remapReferences(row, mapSeq) {
|
|
1460
|
-
const map = (value) => mapSeq(value);
|
|
1461
|
-
if (row.sourceEventSeqs !== void 0) {
|
|
1462
|
-
row.sourceEventSeqs = row.sourceEventSeqs.map(map);
|
|
1463
|
-
}
|
|
1464
|
-
if (isReplaceSurfaceOp(row.surfaceOp)) {
|
|
1465
|
-
row.surfaceOp = { op: "replace", start: map(row.surfaceOp.start), end: map(row.surfaceOp.end) };
|
|
1466
|
-
}
|
|
1467
|
-
const data = row.data;
|
|
1468
|
-
const range = data["shadowedRange"];
|
|
1469
|
-
if (range !== void 0 && typeof range === "object") {
|
|
1470
|
-
data["shadowedRange"] = { start: map(range.start), end: map(range.end) };
|
|
1471
|
-
}
|
|
1472
|
-
for (const key of ["shadowedSeqs", "messageSeqs"]) {
|
|
1473
|
-
const seqs = data[key];
|
|
1474
|
-
if (Array.isArray(seqs)) data[key] = seqs.map(map);
|
|
1475
|
-
}
|
|
1476
|
-
const singleSeq = data["sourceEventSeq"];
|
|
1477
|
-
if (typeof singleSeq === "number") data["sourceEventSeq"] = map(singleSeq);
|
|
1478
|
-
if (row.type === "command/run" && data["name"] === "rewind") {
|
|
1479
|
-
const args = data["args"];
|
|
1480
|
-
if (typeof args === "string") {
|
|
1481
|
-
data["args"] = args.replace(/@(\d+)/g, (match, seqText) => {
|
|
1482
|
-
const seq = Number(seqText);
|
|
1483
|
-
return Number.isSafeInteger(seq) ? `@${mapSeq(seq)}` : match;
|
|
1484
|
-
});
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
return row;
|
|
1488
|
-
}
|
|
1489
|
-
function rewindTargetSeqOfArgs(args) {
|
|
1490
|
-
if (typeof args !== "string") return void 0;
|
|
1491
|
-
const match = args.match(/@(\d+)/);
|
|
1492
|
-
return match !== null ? Number(match[1]) : void 0;
|
|
1493
|
-
}
|
|
1494
|
-
function repairStaleArgs(input) {
|
|
1495
|
-
const markerBySeq = /* @__PURE__ */ new Map();
|
|
1496
|
-
for (const event of input) {
|
|
1497
|
-
if (isFormCMarker(event)) markerBySeq.set(event.seq, event);
|
|
1498
|
-
}
|
|
1499
|
-
const markerSeqByCommandId = /* @__PURE__ */ new Map();
|
|
1500
|
-
for (const event of input) {
|
|
1501
|
-
if (event.type !== "command/done") continue;
|
|
1502
|
-
const data = event.data;
|
|
1503
|
-
if (typeof data.commandId === "string" && typeof data.sourceEventSeq === "number") {
|
|
1504
|
-
markerSeqByCommandId.set(data.commandId, data.sourceEventSeq);
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
let fixed = 0;
|
|
1508
|
-
const events = input.map((event) => {
|
|
1509
|
-
if (event.type !== "command/run") return event;
|
|
1510
|
-
const data = event.data;
|
|
1511
|
-
if (data.name !== "rewind" || typeof data.commandId !== "string" || typeof data.args !== "string") {
|
|
1512
|
-
return event;
|
|
1513
|
-
}
|
|
1514
|
-
const markerSeq = markerSeqByCommandId.get(data.commandId);
|
|
1515
|
-
const marker = markerSeq === void 0 ? void 0 : markerBySeq.get(markerSeq);
|
|
1516
|
-
const start = marker?.surfaceOp?.start;
|
|
1517
|
-
const target = rewindTargetSeqOfArgs(data.args);
|
|
1518
|
-
if (start === void 0 || target === void 0 || target === start) return event;
|
|
1519
|
-
const newArgs = data.args.replace(new RegExp(`@${target}(?!\\d)`), `@${start}`);
|
|
1520
|
-
if (newArgs === data.args) return event;
|
|
1521
|
-
fixed += 1;
|
|
1522
|
-
return { ...event, data: { ...data, args: newArgs } };
|
|
1523
|
-
});
|
|
1524
|
-
return { events, fixed };
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
// src/rewind-fix.ts
|
|
1528
|
-
import { copyFile, rename as rename2, unlink, writeFile as writeFile2, readFile as readFile2, open } from "node:fs/promises";
|
|
1529
|
-
import { isDeepStrictEqual } from "node:util";
|
|
1530
|
-
|
|
1531
|
-
// src/session-log-io.ts
|
|
1532
|
-
import { constants, zstdCompress, zstdDecompress } from "node:zlib";
|
|
1533
|
-
import { promisify } from "node:util";
|
|
1534
|
-
import {
|
|
1535
|
-
decodeSeqRanges,
|
|
1536
|
-
decodeStorageRecord,
|
|
1537
|
-
encodeSeqRanges,
|
|
1538
|
-
packChunkRuns
|
|
1539
|
-
} from "@deepseek-ai/dsh-session";
|
|
1540
|
-
var zstdCompressAsync = promisify(zstdCompress);
|
|
1541
|
-
var zstdDecompressAsync = promisify(zstdDecompress);
|
|
1542
|
-
async function compressZstdFrame(input) {
|
|
1543
|
-
return zstdCompressAsync(Buffer.from(input), { params: { [constants.ZSTD_c_checksumFlag]: 1 } });
|
|
1544
|
-
}
|
|
1545
|
-
function splitSession(plaintext) {
|
|
1546
|
-
const nl = plaintext.indexOf("\n");
|
|
1547
|
-
if (nl === -1) throw new Error("session plaintext has no header line");
|
|
1548
|
-
return { headerLine: plaintext.slice(0, nl), body: plaintext.slice(nl + 1) };
|
|
1549
|
-
}
|
|
1550
|
-
function expandProvenanceFromStorage(parsed) {
|
|
1551
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1552
|
-
throw new TypeError("stored session records must be objects");
|
|
1553
|
-
}
|
|
1554
|
-
const record = parsed;
|
|
1555
|
-
if (record.sourceEventSeqs === void 0) return parsed;
|
|
1556
|
-
if (!Number.isSafeInteger(record.seq) || record.seq < 0) {
|
|
1557
|
-
throw new TypeError("stored session event seq must be a non-negative safe integer");
|
|
1558
|
-
}
|
|
1559
|
-
return { ...record, sourceEventSeqs: decodeSeqRanges(record.sourceEventSeqs, record.seq) };
|
|
1560
|
-
}
|
|
1561
|
-
function decodeEventBody(body) {
|
|
1562
|
-
const events = [];
|
|
1563
|
-
for (const line of body.split("\n")) {
|
|
1564
|
-
if (line.length === 0) continue;
|
|
1565
|
-
const record = JSON.parse(line);
|
|
1566
|
-
const expanded = expandProvenanceFromStorage(record);
|
|
1567
|
-
events.push(...decodeStorageRecord(expanded));
|
|
1568
|
-
}
|
|
1569
|
-
return events;
|
|
1570
|
-
}
|
|
1571
|
-
function eventLines(events, packChunks) {
|
|
1572
|
-
const records = packChunks ? packChunkRuns(events) : events;
|
|
1573
|
-
return records.map((record) => {
|
|
1574
|
-
const provenance = record.sourceEventSeqs;
|
|
1575
|
-
if (provenance !== void 0) {
|
|
1576
|
-
return JSON.stringify({ ...record, sourceEventSeqs: encodeSeqRanges(provenance) });
|
|
1577
|
-
}
|
|
1578
|
-
return JSON.stringify(record);
|
|
1579
|
-
}).join("\n");
|
|
1580
|
-
}
|
|
1581
|
-
async function encodeSessionLog(headerLine, events, options = {}) {
|
|
1582
|
-
const packChunks = options.packChunks ?? true;
|
|
1583
|
-
const headerOut = headerLine + "\n";
|
|
1584
|
-
const bodyOut = eventLines(events, packChunks) + "\n";
|
|
1585
|
-
const [headerFrame, eventFrame] = await Promise.all([compressZstdFrame(headerOut), compressZstdFrame(bodyOut)]);
|
|
1586
|
-
return Buffer.concat([headerFrame, eventFrame]);
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
// src/rewind-fix.ts
|
|
1590
|
-
function isApplyInput(raw) {
|
|
1591
|
-
return raw.trim() === "--apply";
|
|
1592
|
-
}
|
|
1593
|
-
function registerRewindFix(ctx, store, render) {
|
|
1594
|
-
let persistence;
|
|
1595
|
-
ctx.inject(["sessionPersistence"], (scope) => {
|
|
1596
|
-
persistence = scope.sessionPersistence;
|
|
1597
|
-
});
|
|
1598
|
-
ctx.effect(function* () {
|
|
1599
|
-
yield ctx.commands.register({
|
|
1600
|
-
name: "dsh-rewind-fix",
|
|
1601
|
-
description: render("rewindfix.description"),
|
|
1602
|
-
input: { hint: render("rewindfix.inputHint") },
|
|
1603
|
-
handler: (invocation) => handleRewindFix(ctx, store, persistence, invocation, render)
|
|
1604
|
-
});
|
|
1605
|
-
}, "dsh-rewind-fix command");
|
|
1606
|
-
}
|
|
1607
|
-
async function handleRewindFix(ctx, store, persistence, invocation, render) {
|
|
1608
|
-
if (persistence === void 0) {
|
|
1609
|
-
return { kind: "error", text: render("rewindfix.persistenceUnavailable") };
|
|
1610
|
-
}
|
|
1611
|
-
const sessionStore = ctx.get("sessions");
|
|
1612
|
-
if (sessionStore === void 0) {
|
|
1613
|
-
return { kind: "error", text: render("rewindfix.sessionStoreUnavailable") };
|
|
1614
|
-
}
|
|
1615
|
-
const launcher = invocation.agent?.session;
|
|
1616
|
-
const deps = {
|
|
1617
|
-
listSnapshots: (signal) => persistence.listSnapshots(signal),
|
|
1618
|
-
readRaw: (id, signal) => persistence.readRaw(id, signal),
|
|
1619
|
-
locate: (header) => persistence.locate(header),
|
|
1620
|
-
isSessionLoaded: (id) => sessionStore.get(id) !== void 0,
|
|
1621
|
-
clearSession: (id) => store.clearSession(id)
|
|
1622
|
-
};
|
|
1623
|
-
const result = await runRewindFix(deps, {
|
|
1624
|
-
apply: isApplyInput(invocation.rawInput),
|
|
1625
|
-
launcherHasMarkers: (launcher?.snapshotEvents() ?? []).some(isLegacyRewindMarker),
|
|
1626
|
-
signal: invocation.signal
|
|
1627
|
-
});
|
|
1628
|
-
return { kind: "success", text: renderRewindFixReport(result, render) };
|
|
1629
|
-
}
|
|
1630
|
-
async function runRewindFix(deps, opts) {
|
|
1631
|
-
const snapshots = await deps.listSnapshots(opts.signal);
|
|
1632
|
-
const total = snapshots.length;
|
|
1633
|
-
const started = Date.now();
|
|
1634
|
-
let cancelled = false;
|
|
1635
|
-
const concurrency = Math.max(1, Math.min(opts.concurrency ?? 4, total));
|
|
1636
|
-
const results = new Array(total);
|
|
1637
|
-
let cursor = 0;
|
|
1638
|
-
const worker = async () => {
|
|
1639
|
-
for (; ; ) {
|
|
1640
|
-
if (opts.signal?.aborted) {
|
|
1641
|
-
cancelled = true;
|
|
1642
|
-
return;
|
|
1643
|
-
}
|
|
1644
|
-
const i = cursor++;
|
|
1645
|
-
if (i >= total) return;
|
|
1646
|
-
results[i] = await processOneSession(deps, opts, snapshots[i].header);
|
|
1647
|
-
}
|
|
1648
|
-
};
|
|
1649
|
-
await Promise.all(Array.from({ length: concurrency }, () => worker()));
|
|
1650
|
-
const sessions = results.filter((o) => o !== void 0);
|
|
1651
|
-
return {
|
|
1652
|
-
launcherHasMarkers: opts.launcherHasMarkers,
|
|
1653
|
-
cancelled,
|
|
1654
|
-
scanned: total,
|
|
1655
|
-
durationMs: Date.now() - started,
|
|
1656
|
-
apply: opts.apply,
|
|
1657
|
-
sessions
|
|
1658
|
-
};
|
|
1659
|
-
}
|
|
1660
|
-
async function processOneSession(deps, opts, header) {
|
|
1661
|
-
const id = header.id;
|
|
1662
|
-
if (deps.isSessionLoaded(header.id)) {
|
|
1663
|
-
return { id, status: "skipped", a: 0, b: 0, c: 0, staleArgs: 0, reason: "loaded" };
|
|
1664
|
-
}
|
|
1665
|
-
let decoded;
|
|
1666
|
-
try {
|
|
1667
|
-
decoded = await readEvents(deps, header.id, opts.signal);
|
|
1668
|
-
} catch (error) {
|
|
1669
|
-
return { id, status: "failed", a: 0, b: 0, c: 0, staleArgs: 0, reason: "unreadable", error: textOf(error) };
|
|
1670
|
-
}
|
|
1671
|
-
if (decoded === void 0) {
|
|
1672
|
-
return { id, status: "skipped", a: 0, b: 0, c: 0, staleArgs: 0, reason: "no-artifact" };
|
|
1673
|
-
}
|
|
1674
|
-
let converted;
|
|
1675
|
-
try {
|
|
1676
|
-
converted = repairRewindMarkers(decoded.events);
|
|
1677
|
-
} catch (error) {
|
|
1678
|
-
return { id, status: "failed", a: 0, b: 0, c: 0, staleArgs: 0, reason: "repair", error: textOf(error) };
|
|
1679
|
-
}
|
|
1680
|
-
let stale;
|
|
1681
|
-
try {
|
|
1682
|
-
stale = repairStaleArgs(converted.events);
|
|
1683
|
-
} catch (error) {
|
|
1684
|
-
return { id, status: "failed", a: 0, b: 0, c: 0, staleArgs: 0, reason: "repair", error: textOf(error) };
|
|
1685
|
-
}
|
|
1686
|
-
const a = converted.stats.a;
|
|
1687
|
-
const b = converted.stats.b;
|
|
1688
|
-
const c = converted.stats.a + converted.stats.b + converted.stats.c;
|
|
1689
|
-
const staleArgs = stale.fixed;
|
|
1690
|
-
const needsRepair = a + b > 0 || staleArgs > 0;
|
|
1691
|
-
if (!needsRepair) {
|
|
1692
|
-
return { id, status: "skipped", a, b, c, staleArgs, reason: "no-markers" };
|
|
1693
|
-
}
|
|
1694
|
-
if (!opts.apply) {
|
|
1695
|
-
return { id, status: "repaired", a, b, c, staleArgs };
|
|
1696
|
-
}
|
|
1697
|
-
const repair = { events: stale.events, a, b, c, staleArgs };
|
|
1698
|
-
return executeSession(deps, header, decoded.headerLine, repair);
|
|
1699
|
-
}
|
|
1700
|
-
async function executeSession(deps, header, headerLine, repair) {
|
|
1701
|
-
const base = {
|
|
1702
|
-
id: header.id,
|
|
1703
|
-
a: repair.a,
|
|
1704
|
-
b: repair.b,
|
|
1705
|
-
c: repair.c,
|
|
1706
|
-
staleArgs: repair.staleArgs
|
|
1707
|
-
};
|
|
1708
|
-
if (deps.isSessionLoaded(header.id)) {
|
|
1709
|
-
return { ...base, status: "skipped", reason: "loaded-between", error: "loaded between scan and write" };
|
|
1710
|
-
}
|
|
1711
|
-
const location = deps.locate(header);
|
|
1712
|
-
if (location === void 0) return { ...base, status: "failed", error: "locate() returned no path" };
|
|
1713
|
-
const path = location.path;
|
|
1714
|
-
const lockPath = `${path}.rewind-fix.lock`;
|
|
1715
|
-
const tmpPath = `${path}.rewind-fix.tmp`;
|
|
1716
|
-
const bakPath = `${path}.rewind-fix.bak`;
|
|
1717
|
-
const lock = await acquireSessionLock(lockPath);
|
|
1718
|
-
if (lock === void 0) {
|
|
1719
|
-
return { ...base, status: "failed", reason: "locked", error: "another process holds the session lock" };
|
|
1720
|
-
}
|
|
1721
|
-
try {
|
|
1722
|
-
await copyFile(path, bakPath);
|
|
1723
|
-
const buffer = await encodeSessionLog(headerLine, repair.events);
|
|
1724
|
-
await writeFile2(tmpPath, buffer);
|
|
1725
|
-
await rename2(tmpPath, path);
|
|
1726
|
-
const verification = await verifyWritten(deps, header.id);
|
|
1727
|
-
if (verification === void 0) throw new Error("written artifact could not be re-read");
|
|
1728
|
-
if (!isDeepStrictEqual(normalizeForVerify(verification), normalizeForVerify(repair.events))) {
|
|
1729
|
-
throw new Error("round-trip mismatch after write");
|
|
1730
|
-
}
|
|
1731
|
-
await deps.clearSession(idOf(header));
|
|
1732
|
-
return { ...base, status: "repaired" };
|
|
1733
|
-
} catch (error) {
|
|
1734
|
-
await unlink(tmpPath).catch(() => {
|
|
1735
|
-
});
|
|
1736
|
-
await rename2(bakPath, path).catch(() => {
|
|
1737
|
-
});
|
|
1738
|
-
return { ...base, status: "failed", error: textOf(error) };
|
|
1739
|
-
} finally {
|
|
1740
|
-
await lock.close().catch(() => {
|
|
1741
|
-
});
|
|
1742
|
-
await unlink(lockPath).catch(() => {
|
|
1743
|
-
});
|
|
1744
|
-
await unlink(bakPath).catch(() => {
|
|
1745
|
-
});
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
async function readEvents(deps, id, signal) {
|
|
1749
|
-
const raw = await deps.readRaw(id, signal);
|
|
1750
|
-
if (raw === void 0) return void 0;
|
|
1751
|
-
const { headerLine, body } = splitSession(raw.content);
|
|
1752
|
-
return { events: decodeEventBody(body), headerLine };
|
|
1753
|
-
}
|
|
1754
|
-
async function verifyWritten(deps, id) {
|
|
1755
|
-
const raw = await deps.readRaw(id);
|
|
1756
|
-
if (raw === void 0) return void 0;
|
|
1757
|
-
return decodeEventBody(splitSession(raw.content).body);
|
|
1758
|
-
}
|
|
1759
|
-
function normalizeForVerify(events) {
|
|
1760
|
-
return events.map((e) => ({ type: e.type, seq: e.seq, data: e.data }));
|
|
1761
|
-
}
|
|
1762
|
-
function textOf(error) {
|
|
1763
|
-
return error instanceof Error ? error.message : String(error);
|
|
1764
|
-
}
|
|
1765
|
-
function idOf(header) {
|
|
1766
|
-
return header.id;
|
|
1767
|
-
}
|
|
1768
|
-
async function acquireSessionLock(lockPath) {
|
|
1769
|
-
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1770
|
-
try {
|
|
1771
|
-
const handle = await open(lockPath, "wx");
|
|
1772
|
-
await handle.writeFile(String(process.pid));
|
|
1773
|
-
return handle;
|
|
1774
|
-
} catch (error) {
|
|
1775
|
-
const code = error.code;
|
|
1776
|
-
if (code !== "EEXIST") throw error;
|
|
1777
|
-
if (attempt === 1) return void 0;
|
|
1778
|
-
if (await removeStaleLock(lockPath)) continue;
|
|
1779
|
-
return void 0;
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
return void 0;
|
|
1783
|
-
}
|
|
1784
|
-
async function removeStaleLock(lockPath) {
|
|
1785
|
-
let pid = 0;
|
|
1786
|
-
try {
|
|
1787
|
-
pid = Number((await readFile2(lockPath, "utf8")).trim());
|
|
1788
|
-
} catch {
|
|
1789
|
-
return false;
|
|
1790
|
-
}
|
|
1791
|
-
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
1792
|
-
if (pidAlive(pid)) return false;
|
|
1793
|
-
await unlink(lockPath).catch(() => {
|
|
1794
|
-
});
|
|
1795
|
-
return true;
|
|
1796
|
-
}
|
|
1797
|
-
function pidAlive(pid) {
|
|
1798
|
-
try {
|
|
1799
|
-
process.kill(pid, 0);
|
|
1800
|
-
return true;
|
|
1801
|
-
} catch (error) {
|
|
1802
|
-
return error.code === "EPERM";
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
function renderRewindFixReport(result, render) {
|
|
1806
|
-
const parts = [];
|
|
1807
|
-
if (result.launcherHasMarkers) parts.push(render("rewindfix.launcherGuard"));
|
|
1808
|
-
parts.push(headline(result, render));
|
|
1809
|
-
result.sessions.forEach((outcome, i) => parts.push(...sessionLines(outcome, i + 1, result.scanned, result.apply, render)));
|
|
1810
|
-
return parts.join("\n");
|
|
1811
|
-
}
|
|
1812
|
-
function headline(result, render) {
|
|
1813
|
-
if (result.cancelled) return render("rewindfix.cancelled");
|
|
1814
|
-
const repaired = result.sessions.filter((o) => o.status === "repaired").length;
|
|
1815
|
-
const skipped = result.sessions.filter((o) => o.status === "skipped").length;
|
|
1816
|
-
const failed = result.sessions.filter((o) => o.status === "failed").length;
|
|
1817
|
-
const secs = (result.durationMs / 1e3).toFixed(1);
|
|
1818
|
-
if (!result.apply) {
|
|
1819
|
-
return render("rewindfix.dryRun", { scan: result.scanned, will: repaired, skip: skipped, fail: failed, secs });
|
|
1820
|
-
}
|
|
1821
|
-
return render("rewindfix.done", { repair: repaired, skip: skipped, fail: failed, secs });
|
|
1822
|
-
}
|
|
1823
|
-
function sessionLines(outcome, index, total, apply2, render) {
|
|
1824
|
-
const head = `[${index}/${total}] ${outcome.id}`;
|
|
1825
|
-
if (outcome.status === "repaired") {
|
|
1826
|
-
const counts = render("rewindfix.counts", { a: outcome.a, b: outcome.b, c: outcome.c });
|
|
1827
|
-
const argsNote = outcome.staleArgs > 0 ? " " + render("rewindfix.args", { n: outcome.staleArgs }) : "";
|
|
1828
|
-
if (!apply2) return [`${head} ${counts}${argsNote}`];
|
|
1829
|
-
return [`${head} ${counts}${argsNote}`, ` ${render("rewindfix.writeOk")}`];
|
|
1830
|
-
}
|
|
1831
|
-
if (outcome.status === "skipped") {
|
|
1832
|
-
return [`${head} ${skipLabel(outcome, render)}`];
|
|
1833
|
-
}
|
|
1834
|
-
return [`${head} ${failLabel(outcome, render)}`];
|
|
1835
|
-
}
|
|
1836
|
-
function skipLabel(outcome, render) {
|
|
1837
|
-
switch (outcome.reason) {
|
|
1838
|
-
case "no-markers":
|
|
1839
|
-
return render("rewindfix.skip.noMarkers");
|
|
1840
|
-
case "no-artifact":
|
|
1841
|
-
return render("rewindfix.skip.noArtifact");
|
|
1842
|
-
case "loaded-between": {
|
|
1843
|
-
const label = render("rewindfix.skip.loadedNow");
|
|
1844
|
-
return outcome.error ? `${label} ${outcome.error}` : label;
|
|
1845
|
-
}
|
|
1846
|
-
case "loaded":
|
|
1847
|
-
default:
|
|
1848
|
-
return render("rewindfix.skip.loaded");
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
function failLabel(outcome, render) {
|
|
1852
|
-
switch (outcome.reason) {
|
|
1853
|
-
case "unreadable":
|
|
1854
|
-
return render("rewindfix.fail.unreadable", { error: outcome.error ?? "" });
|
|
1855
|
-
case "repair":
|
|
1856
|
-
return render("rewindfix.fail.repair", { error: outcome.error ?? "" });
|
|
1857
|
-
case "locked":
|
|
1858
|
-
return render("rewindfix.fail.locked", {
|
|
1859
|
-
error: `${render("rewindfix.locked")}${outcome.error ? ` (${outcome.error})` : ""}`
|
|
1860
|
-
});
|
|
1861
|
-
default:
|
|
1862
|
-
return render("rewindfix.fail.generic", { error: outcome.error ?? "" });
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
1305
|
// src/snapshot-cleanup.ts
|
|
1867
|
-
import { mkdir as mkdir2, readFile as
|
|
1306
|
+
import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1868
1307
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
1869
1308
|
import { resolveDshHome as resolveDshHome2 } from "@deepseek-ai/dsh-home-paths";
|
|
1870
1309
|
import z from "@deepseek-ai/schemastery";
|
|
1871
|
-
var CLEANUP_CONFIG_FILENAME = "snapshot-cleanup.json";
|
|
1872
1310
|
var DEFAULT_MAX_AGE_DAYS = 30;
|
|
1873
1311
|
var DEFAULT_CLEANUP_CONFIG = { enabled: false, maxAgeDays: DEFAULT_MAX_AGE_DAYS };
|
|
1874
1312
|
var CLEANUP_SETTINGS_NAMESPACE = "dsh-rewind-snapshot-cleanup";
|
|
@@ -1886,35 +1324,14 @@ function settingsCleanupStore(scope) {
|
|
|
1886
1324
|
}
|
|
1887
1325
|
};
|
|
1888
1326
|
}
|
|
1889
|
-
async function migrateLegacyCleanupConfig(legacyPath, scope, log) {
|
|
1890
|
-
const loaded = await loadCleanupConfig(legacyPath);
|
|
1891
|
-
if (!loaded.ok) {
|
|
1892
|
-
log(`[dsh-rewind] legacy snapshot-cleanup config invalid, migrating defaults and removing: ${loaded.error}`);
|
|
1893
|
-
await scope.update({ enabled: false, maxAgeDays: DEFAULT_MAX_AGE_DAYS });
|
|
1894
|
-
await unlink2(legacyPath).catch(() => void 0);
|
|
1895
|
-
return true;
|
|
1896
|
-
}
|
|
1897
|
-
if (!loaded.fromFile) return false;
|
|
1898
|
-
if (loaded.config.enabled === DEFAULT_CLEANUP_CONFIG.enabled && loaded.config.maxAgeDays === DEFAULT_CLEANUP_CONFIG.maxAgeDays) {
|
|
1899
|
-
await unlink2(legacyPath).catch(() => void 0);
|
|
1900
|
-
return true;
|
|
1901
|
-
}
|
|
1902
|
-
await scope.update({ enabled: loaded.config.enabled, maxAgeDays: loaded.config.maxAgeDays });
|
|
1903
|
-
await unlink2(legacyPath).catch(() => void 0);
|
|
1904
|
-
log(`[dsh-rewind] migrated legacy snapshot-cleanup config (enabled=${String(loaded.config.enabled)}, maxAgeDays=${String(loaded.config.maxAgeDays)})`);
|
|
1905
|
-
return true;
|
|
1906
|
-
}
|
|
1907
1327
|
var AUTO_SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1908
|
-
function resolveCleanupConfigPath(dshHome) {
|
|
1909
|
-
return join2(resolveDshHome2(dshHome), CLEANUP_CONFIG_FILENAME);
|
|
1910
|
-
}
|
|
1911
1328
|
var STATE_FILENAME = "snapshot-cleanup-last-sweep.json";
|
|
1912
1329
|
function resolveCleanupStatePath(dshHome) {
|
|
1913
|
-
return join2(
|
|
1330
|
+
return join2(resolveDshHome2(dshHome), STATE_FILENAME);
|
|
1914
1331
|
}
|
|
1915
1332
|
async function loadLastSweepAt(path) {
|
|
1916
1333
|
try {
|
|
1917
|
-
const raw = JSON.parse(await
|
|
1334
|
+
const raw = JSON.parse(await readFile2(path, "utf8"));
|
|
1918
1335
|
const value = raw["lastSweepAt"];
|
|
1919
1336
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1920
1337
|
} catch {
|
|
@@ -1924,8 +1341,8 @@ async function loadLastSweepAt(path) {
|
|
|
1924
1341
|
async function saveLastSweepAt(path, ms) {
|
|
1925
1342
|
const tmp = `${path}.tmp`;
|
|
1926
1343
|
await mkdir2(dirname2(path), { recursive: true });
|
|
1927
|
-
await
|
|
1928
|
-
await
|
|
1344
|
+
await writeFile2(tmp, JSON.stringify({ lastSweepAt: ms }), "utf8");
|
|
1345
|
+
await rename2(tmp, path);
|
|
1929
1346
|
}
|
|
1930
1347
|
async function runAutoCleanupCheck(deps, sessionId) {
|
|
1931
1348
|
try {
|
|
@@ -1962,26 +1379,6 @@ function parseCleanupConfig(raw) {
|
|
|
1962
1379
|
}
|
|
1963
1380
|
return { ok: true, config: { enabled, maxAgeDays } };
|
|
1964
1381
|
}
|
|
1965
|
-
async function loadCleanupConfig(path) {
|
|
1966
|
-
let text;
|
|
1967
|
-
try {
|
|
1968
|
-
text = await readFile3(path, "utf8");
|
|
1969
|
-
} catch (error) {
|
|
1970
|
-
if (error.code === "ENOENT") {
|
|
1971
|
-
return { ok: true, config: { ...DEFAULT_CLEANUP_CONFIG }, fromFile: false };
|
|
1972
|
-
}
|
|
1973
|
-
return { ok: false, error: `config file unreadable: ${error instanceof Error ? error.message : String(error)}` };
|
|
1974
|
-
}
|
|
1975
|
-
let raw;
|
|
1976
|
-
try {
|
|
1977
|
-
raw = JSON.parse(text);
|
|
1978
|
-
} catch (error) {
|
|
1979
|
-
return { ok: false, error: `config file is not valid JSON: ${error instanceof Error ? error.message : String(error)}` };
|
|
1980
|
-
}
|
|
1981
|
-
const parsed = parseCleanupConfig(raw);
|
|
1982
|
-
if (!parsed.ok) return { ok: false, error: parsed.error };
|
|
1983
|
-
return { ok: true, config: parsed.config, fromFile: true };
|
|
1984
|
-
}
|
|
1985
1382
|
function parseCleanupCommand(rawInput) {
|
|
1986
1383
|
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
1987
1384
|
if (parts.length === 0) return { action: "status" };
|
|
@@ -2025,8 +1422,7 @@ function shouldRunAutoSweep(lastAtMs, nowMs) {
|
|
|
2025
1422
|
// src/index.ts
|
|
2026
1423
|
var name = "dsh-rewind";
|
|
2027
1424
|
var inject = ["commands", "tools"];
|
|
2028
|
-
var TRACKED_TOOLS = /* @__PURE__ */ new Set(["write", "edit"
|
|
2029
|
-
var MUTATING_EDITOR_COMMANDS = /* @__PURE__ */ new Set(["create", "str_replace", "insert"]);
|
|
1425
|
+
var TRACKED_TOOLS = /* @__PURE__ */ new Set(["write", "edit"]);
|
|
2030
1426
|
var activeLocale = "en";
|
|
2031
1427
|
var cleanupStore;
|
|
2032
1428
|
function t(key, params) {
|
|
@@ -2045,10 +1441,6 @@ function mutationPathOf(exec) {
|
|
|
2045
1441
|
if (exec.name === "write" || exec.name === "edit") {
|
|
2046
1442
|
return typeof args.file_path === "string" ? args.file_path : void 0;
|
|
2047
1443
|
}
|
|
2048
|
-
if (exec.name === "str_replace_editor") {
|
|
2049
|
-
if (typeof args.command !== "string" || !MUTATING_EDITOR_COMMANDS.has(args.command)) return void 0;
|
|
2050
|
-
return typeof args.path === "string" ? args.path : void 0;
|
|
2051
|
-
}
|
|
2052
1444
|
return void 0;
|
|
2053
1445
|
}
|
|
2054
1446
|
function anchorSeqOf(session, cache) {
|
|
@@ -2119,10 +1511,11 @@ async function commitEntry(store, pending, anchorCache, trackedBySession, exec,
|
|
|
2119
1511
|
}
|
|
2120
1512
|
tracked.add(capture.path);
|
|
2121
1513
|
}
|
|
2122
|
-
var
|
|
1514
|
+
var REWIND_MARKER_SOURCE = { kind: "plugin", plugin: "dsh-rewind" };
|
|
1515
|
+
var REWIND_MARKER_CONTENT = [];
|
|
2123
1516
|
function buildMarker() {
|
|
2124
1517
|
return createUserMessage({
|
|
2125
|
-
content:
|
|
1518
|
+
content: REWIND_MARKER_CONTENT,
|
|
2126
1519
|
source: REWIND_MARKER_SOURCE
|
|
2127
1520
|
});
|
|
2128
1521
|
}
|
|
@@ -2250,7 +1643,7 @@ async function executeRewind(ctx, store, fs, invocation, rawTarget, mode, inflig
|
|
|
2250
1643
|
}
|
|
2251
1644
|
let restore = "";
|
|
2252
1645
|
if (mode === "both") {
|
|
2253
|
-
const outcome = await store.restoreAfter(agent.session.id, plan.targetSeq, (path) =>
|
|
1646
|
+
const outcome = await store.restoreAfter(agent.session.id, plan.targetSeq, (path) => unlink(path));
|
|
2254
1647
|
await syncRestoreObservations(ctx, fs, agent, outcome);
|
|
2255
1648
|
const parts = [];
|
|
2256
1649
|
if (outcome.restored.length > 0) parts.push(t("restore.count", { count: outcome.restored.length }));
|
|
@@ -2461,13 +1854,6 @@ function apply(ctx, config) {
|
|
|
2461
1854
|
{ base: DEFAULT_CLEANUP_CONFIG }
|
|
2462
1855
|
);
|
|
2463
1856
|
cleanupStore = settingsCleanupStore(cleanupScope);
|
|
2464
|
-
void migrateLegacyCleanupConfig(
|
|
2465
|
-
resolveCleanupConfigPath(dshHome),
|
|
2466
|
-
cleanupScope,
|
|
2467
|
-
(msg) => ctx.logger.warn(msg)
|
|
2468
|
-
).catch((error) => {
|
|
2469
|
-
ctx.logger.warn(`[dsh-rewind] snapshot cleanup migration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2470
|
-
});
|
|
2471
1857
|
});
|
|
2472
1858
|
ctx.effect(function* () {
|
|
2473
1859
|
const rewindHandler = (invocation) => handleRewind(ctx, store, fsService, invocation, inflight);
|
|
@@ -2488,7 +1874,6 @@ function apply(ctx, config) {
|
|
|
2488
1874
|
handler: (invocation) => handleSnapshotCleanup(store, invocation, dshHome, trackedBySession)
|
|
2489
1875
|
});
|
|
2490
1876
|
}, "dsh-rewind command");
|
|
2491
|
-
registerRewindFix(ctx, store, (key, params) => t(key, params));
|
|
2492
1877
|
ctx.on("session/event", (session, event) => {
|
|
2493
1878
|
if (event.type !== "user/message") return;
|
|
2494
1879
|
const header = session.header;
|