dsh-rewind-plugin 0.5.0 → 0.6.0
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 +17 -2
- package/README.md +3 -3
- package/SECURITY.md +9 -3
- package/docs/README.md +1 -0
- package/docs/compat/audit.md +134 -122
- package/docs/format.md +5 -4
- package/docs/harness-reference.md +1 -1
- package/docs/snapshot-auto-cleanup.md +75 -56
- package/docs/snapshot-auto-cleanup.zh.md +36 -21
- package/lib/client.js +7 -64
- package/lib/index.js +199 -39
- package/lib/types/client/index.d.ts +12 -12
- package/lib/types/client/locales.d.ts +0 -2
- package/lib/types/index.d.ts +6 -3
- package/lib/types/locales.d.ts +5 -0
- package/lib/types/snapshot-cleanup.d.ts +28 -8
- package/lib/types/snapshot.d.ts +80 -2
- package/package.json +6 -1
package/lib/types/index.d.ts
CHANGED
|
@@ -29,10 +29,12 @@ export { SnapshotStore } from './snapshot.ts';
|
|
|
29
29
|
export type { CheckpointEntry, FileImpact, PruneStaleReport, RestoreOutcome, RestoreJournal, RestoreJournalState, RestoreReconcileReport } from './snapshot.ts';
|
|
30
30
|
export declare const name = "dsh-rewind";
|
|
31
31
|
export declare const inject: string[];
|
|
32
|
-
/** Plugin config
|
|
32
|
+
/** Plugin config. */
|
|
33
33
|
export interface RewindConfig {
|
|
34
|
-
/** Checkpoint store root (
|
|
34
|
+
/** Checkpoint store root (exact path; beats `DSH_REWIND_SNAPSHOT_DIR` and the harness-home default). */
|
|
35
35
|
readonly snapshotDir?: string;
|
|
36
|
+
/** Harness home override (`config.dshHome` > `$DSH_HOME` > `~/.dsh`); feeds the default snapshot/cleanup paths. */
|
|
37
|
+
readonly dshHome?: string;
|
|
36
38
|
/** In-place content dedup (identical before-content → link). Default `true`. */
|
|
37
39
|
readonly dedup?: boolean;
|
|
38
40
|
}
|
|
@@ -55,6 +57,7 @@ export interface RewindConfig {
|
|
|
55
57
|
* disk at `tools/post-execute` under the turn's anchor message seq.
|
|
56
58
|
*
|
|
57
59
|
* @param ctx - context carrying `commands`, `tools`, and an optional `fs`.
|
|
58
|
-
* @param config - optional
|
|
60
|
+
* @param config - optional plugin config: `snapshotDir` (exact store-root override),
|
|
61
|
+
* `dshHome` (harness-home override feeding the default paths), `dedup`.
|
|
59
62
|
*/
|
|
60
63
|
export declare function apply(ctx: Context, config?: RewindConfig): void;
|
package/lib/types/locales.d.ts
CHANGED
|
@@ -65,6 +65,11 @@ export declare const en: {
|
|
|
65
65
|
'cleanup.runApply': string;
|
|
66
66
|
'cleanup.runFailed': string;
|
|
67
67
|
'cleanup.skipped': string;
|
|
68
|
+
'cleanup.clearDry': string;
|
|
69
|
+
'cleanup.clearApply': string;
|
|
70
|
+
'cleanup.clearActive': string;
|
|
71
|
+
'cleanup.clearCancelled': string;
|
|
72
|
+
'cleanup.clearFailed': string;
|
|
68
73
|
'cleanup.usage': string;
|
|
69
74
|
};
|
|
70
75
|
/** The host rewind dictionary key union. */
|
|
@@ -32,8 +32,13 @@ export declare const DEFAULT_MAX_AGE_DAYS = 30;
|
|
|
32
32
|
export declare const DEFAULT_CLEANUP_CONFIG: CleanupConfig;
|
|
33
33
|
/** Auto-sweep cadence (the user's hardcoded 24h rhythm — not user-set). */
|
|
34
34
|
export declare const AUTO_SWEEP_INTERVAL_MS: number;
|
|
35
|
-
/**
|
|
36
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the config file path (highest first): the `DSH_SNAPSHOT_CLEANUP_CONFIG`
|
|
37
|
+
* env override, else `<harness home>/snapshot-cleanup.json` — derived from
|
|
38
|
+
* `dshHome` (config.dshHome > `$DSH_HOME` > `~/.dsh`) so the plugin follows the
|
|
39
|
+
* harness home instead of hardcoding `~/.dsh`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveCleanupConfigPath(dshHome?: string): string;
|
|
37
42
|
/** The state file that records the last automatic-sweep wall-clock time. */
|
|
38
43
|
export declare const STATE_FILENAME = "snapshot-cleanup-last-sweep.json";
|
|
39
44
|
/**
|
|
@@ -41,7 +46,7 @@ export declare const STATE_FILENAME = "snapshot-cleanup-last-sweep.json";
|
|
|
41
46
|
* 24h cadence SURVIVES a host restart (a real deployment is rarely up 24/7,
|
|
42
47
|
* so an in-memory timestamp would reset on every boot and re-sweep too often).
|
|
43
48
|
*/
|
|
44
|
-
export declare function resolveCleanupStatePath(): string;
|
|
49
|
+
export declare function resolveCleanupStatePath(dshHome?: string): string;
|
|
45
50
|
/**
|
|
46
51
|
* Read the persisted last-sweep time (epoch ms). A missing or corrupt file
|
|
47
52
|
* reads as `0` ("never swept"), so the next activity runs the sweep — which is
|
|
@@ -105,16 +110,31 @@ export declare function loadCleanupConfig(path: string): Promise<{
|
|
|
105
110
|
*/
|
|
106
111
|
export declare function saveCleanupConfig(path: string, config: CleanupConfig): Promise<void>;
|
|
107
112
|
/** The `/snapshot-auto-cleanup` sub-command the parser can resolve to. */
|
|
108
|
-
export type CleanupCommandAction = 'status' | 'on' | 'off' | 'max-age' | 'run'
|
|
113
|
+
export type CleanupCommandAction = 'status' | 'on' | 'off' | 'max-age' | 'run';
|
|
114
|
+
/** A parsed `/snapshot-auto-cleanup` command (excludes the error branch). */
|
|
115
|
+
export type CleanupCommand = {
|
|
116
|
+
action: 'status' | 'on' | 'off';
|
|
117
|
+
} | {
|
|
118
|
+
action: 'max-age';
|
|
119
|
+
value: number;
|
|
120
|
+
} | {
|
|
121
|
+
action: 'run';
|
|
122
|
+
target: 'rules' | 'current';
|
|
123
|
+
apply: boolean;
|
|
124
|
+
};
|
|
109
125
|
/**
|
|
110
126
|
* Parse the free-form text after `/snapshot-auto-cleanup`. Pure so it is
|
|
111
127
|
* unit-testable; `src/index.ts` maps the resolved action onto the store / the
|
|
112
128
|
* config file. `max-age` returns the validated positive day count.
|
|
129
|
+
*
|
|
130
|
+
* The `run` verb is the single manual-cleanup action. `--apply` is the ONLY
|
|
131
|
+
* execute-vs-dry-run switch (position-independent): without it the action is a
|
|
132
|
+
* dry-run preview. `--current` re-targets the action to the ACTIVE session's
|
|
133
|
+
* snapshots (the manual "clear this session now"); without it, `run` keeps its
|
|
134
|
+
* age-based stale-session sweep semantics. The old `run-apply` abbreviation is
|
|
135
|
+
* gone — use `run --apply`.
|
|
113
136
|
*/
|
|
114
|
-
export declare function parseCleanupCommand(rawInput: string): {
|
|
115
|
-
action: CleanupCommandAction;
|
|
116
|
-
value?: number;
|
|
117
|
-
} | {
|
|
137
|
+
export declare function parseCleanupCommand(rawInput: string): CleanupCommand | {
|
|
118
138
|
error: string;
|
|
119
139
|
};
|
|
120
140
|
/**
|
package/lib/types/snapshot.d.ts
CHANGED
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
* kept), and restores read/write the real file system with plain `node:fs`
|
|
21
21
|
* — independent of the fs service.
|
|
22
22
|
*
|
|
23
|
+
* Security note: this `node:fs` authority is the DSH host authority every host
|
|
24
|
+
* plugin holds — the model-facing fences constrain the model's tools, not this
|
|
25
|
+
* code. The store stays bounded to the model-touched paths, so excluding a
|
|
26
|
+
* file (e.g. `.env`) is a model-permission concern (see `SECURITY.md`).
|
|
27
|
+
*
|
|
23
28
|
* Crash safety (this module's own engineering asset):
|
|
24
29
|
* - Checkpoint commits are ATOMIC: the entry JSON is written to a sibling
|
|
25
30
|
* temp file and renamed over the target, so a host crash mid-write can
|
|
@@ -48,7 +53,12 @@
|
|
|
48
53
|
*
|
|
49
54
|
* @module dsh-rewind/snapshot
|
|
50
55
|
*/
|
|
51
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Default store root: `<harness home>/rewind-snapshots`. Resolved through
|
|
58
|
+
* {@link resolveDshHome} so the plugin follows `$DSH_HOME` (or a configured
|
|
59
|
+
* harness home) rather than hardcoding `~/.dsh` — matching the other
|
|
60
|
+
* first-party DSH packages. See `SECURITY.md` "Sensitive files".
|
|
61
|
+
*/
|
|
52
62
|
export declare const DEFAULT_SNAPSHOT_ROOT: string;
|
|
53
63
|
/** Environment variable overriding the store root (tests, exotic homes). */
|
|
54
64
|
export declare const SNAPSHOT_ROOT_ENV = "DSH_REWIND_SNAPSHOT_DIR";
|
|
@@ -249,12 +259,34 @@ export interface PruneStaleReport {
|
|
|
249
259
|
/** Whether nothing was really removed (the sweep only reported). */
|
|
250
260
|
readonly dryRun: boolean;
|
|
251
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Result of a manual whole-session clear ({@link SnapshotStore.clearSession}).
|
|
264
|
+
*
|
|
265
|
+
* Unlike the age-based sweep, a clear removes EVERY snapshot of ONE session
|
|
266
|
+
* (all anchor groups, all checkpoint entries, all restore journals) on demand —
|
|
267
|
+
* the active session the user is driving, to drop the rewind overhead or to
|
|
268
|
+
* archive a conversation immediately. `dryRun` reports what would be removed
|
|
269
|
+
* without touching disk or memory.
|
|
270
|
+
*/
|
|
271
|
+
export interface ClearSessionReport {
|
|
272
|
+
/** The session whose records were (or would be) cleared. */
|
|
273
|
+
readonly sessionId: string;
|
|
274
|
+
/** Number of anchor-group (user-message) directories present. */
|
|
275
|
+
readonly anchorGroups: number;
|
|
276
|
+
/** Number of committed checkpoint entries (full backups + dedup links). */
|
|
277
|
+
readonly entries: number;
|
|
278
|
+
/** Number of restore-journal files (terminal + pending). */
|
|
279
|
+
readonly journals: number;
|
|
280
|
+
/** Bytes occupied by the session directory (the amount freed). */
|
|
281
|
+
readonly bytes: number;
|
|
282
|
+
/** Whether nothing was really removed (the clear only reported). */
|
|
283
|
+
readonly dryRun: boolean;
|
|
284
|
+
}
|
|
252
285
|
/**
|
|
253
286
|
* On-disk checkpoint store. Every write goes straight through `node:fs`, so a
|
|
254
287
|
* restore reliably lands on the real file system.
|
|
255
288
|
*/
|
|
256
289
|
export declare class SnapshotStore {
|
|
257
|
-
readonly root: string;
|
|
258
290
|
/** Debounce window for the per-commit prune (keeps the readdir+sort off the hot path). */
|
|
259
291
|
private static readonly PRUNE_INTERVAL_MS;
|
|
260
292
|
private lastPruneAt;
|
|
@@ -273,6 +305,8 @@ export declare class SnapshotStore {
|
|
|
273
305
|
private lastEntryTime;
|
|
274
306
|
/** Store options; `dedup` toggles in-place content dedup (default on). */
|
|
275
307
|
private readonly dedup;
|
|
308
|
+
/** Resolved checkpoint store root (absolute); see the constructor's fallback. */
|
|
309
|
+
readonly root: string;
|
|
276
310
|
/**
|
|
277
311
|
* In-memory per-path "most recent entry" for content dedup, keyed by
|
|
278
312
|
* `<sessionId>\0<path>`. Each value holds the entry's effective `before`
|
|
@@ -285,6 +319,7 @@ export declare class SnapshotStore {
|
|
|
285
319
|
private readonly seededSessions;
|
|
286
320
|
constructor(root?: string, opts?: {
|
|
287
321
|
readonly dedup?: boolean;
|
|
322
|
+
readonly dshHome?: string;
|
|
288
323
|
});
|
|
289
324
|
/** Absolute path of one session's snapshot directory (id sanitized). */
|
|
290
325
|
sessionDir(sessionId: string): string;
|
|
@@ -537,6 +572,49 @@ export declare class SnapshotStore {
|
|
|
537
572
|
* entries so no extra persistence is needed.
|
|
538
573
|
*/
|
|
539
574
|
trackedPaths(sessionId: string): Promise<Set<string>>;
|
|
575
|
+
/**
|
|
576
|
+
* Summarize a session's on-disk footprint for a clear dry-run: anchor-group
|
|
577
|
+
* count, committed checkpoint-entry count, restore-journal count, and total
|
|
578
|
+
* bytes. Walks with `lstat` (never follows a symlink, so a hostile symlink
|
|
579
|
+
* cannot escape the store root or inflate the measurement) and skips
|
|
580
|
+
* dot-prefixed temp leftovers and non-`.json` members — they are never
|
|
581
|
+
* checkpoint entries.
|
|
582
|
+
*/
|
|
583
|
+
private sessionStats;
|
|
584
|
+
/**
|
|
585
|
+
* Remove a session's ENTIRE snapshot directory — every anchor group, every
|
|
586
|
+
* checkpoint entry, and every restore journal — and reset the store's
|
|
587
|
+
* in-memory dedup state so the session starts recording fresh from the
|
|
588
|
+
* current workspace state. This is the manual "get rid of this session's
|
|
589
|
+
* records NOW" action on the ACTIVE session the user is driving (it is never
|
|
590
|
+
* targetable by id; that is a directory-manipulation concern the user can do
|
|
591
|
+
* directly).
|
|
592
|
+
*
|
|
593
|
+
* SEMANTICS — clearing is an explicit abandonment: issuing the command means
|
|
594
|
+
* the user accepts that this session's snapshot archive goes away. It is
|
|
595
|
+
* therefore NOT gated on the state of any restore journal. A clear and a
|
|
596
|
+
* restore are both slash commands the host runs to completion for an agent,
|
|
597
|
+
* so they never interleave — any non-terminal journal present on disk is a
|
|
598
|
+
* stale orphan from a previous (crashed) process, and discarding it is the
|
|
599
|
+
* correct, safe resolution of that abandoned restore.
|
|
600
|
+
*
|
|
601
|
+
* SAFETY (this module's real concern is the plugin's ongoing BEHAVIOR, not
|
|
602
|
+
* losing snapshots):
|
|
603
|
+
* - Only the session dir is removed; dedup refs are session-relative, so
|
|
604
|
+
* there is no cross-session dangling to materialize (the same rationale as
|
|
605
|
+
* {@link pruneStale}'s whole-dir removal).
|
|
606
|
+
* - The in-memory dedup state (`lastEntry` / `seededSessions`) is ALWAYS
|
|
607
|
+
* reset on an apply — even when the dir was already empty. A stale
|
|
608
|
+
* in-memory entry (e.g. a session whose dir was removed out-of-band) would
|
|
609
|
+
* otherwise link a later `recordEntry` to a deleted prior entry, leaving a
|
|
610
|
+
* dangling ref that breaks restore resolution. This is the primary
|
|
611
|
+
* correctness guarantee.
|
|
612
|
+
*
|
|
613
|
+
* `dryRun` computes the report without touching disk or memory.
|
|
614
|
+
*/
|
|
615
|
+
clearSession(sessionId: string, opts?: {
|
|
616
|
+
readonly dryRun?: boolean;
|
|
617
|
+
}): Promise<ClearSessionReport>;
|
|
540
618
|
}
|
|
541
619
|
/**
|
|
542
620
|
* Re-check every tracked file at a user-message boundary and record the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-rewind-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "DSH 插件:真正便捷无感的同窗口内对话回退,从不新建分支;自带轻量工作区备份,可一并还原文件(完整 Claude Code /rewind 语义)。 · DSH plugin: genuinely effortless in-window conversation rewind — never forking a new session; ships a lightweight workspace backup that restores files together with the rewind (full Claude Code /rewind semantics).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6 || ^0.1.1-rc.2",
|
|
79
79
|
"@deepseek-ai/dsh-commands": "^0.1.0-rc.6 || ^0.1.1-rc.2",
|
|
80
80
|
"@deepseek-ai/dsh-fs": "^0.1.0-rc.6 || ^0.1.1-rc.2",
|
|
81
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.1-rc.2",
|
|
81
82
|
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.2",
|
|
82
83
|
"@deepseek-ai/dsh-sandbox": "^0.1.0-rc.6 || ^0.1.1-rc.2",
|
|
83
84
|
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-rc.2",
|
|
@@ -106,6 +107,9 @@
|
|
|
106
107
|
"@deepseek-ai/dsh-fs": {
|
|
107
108
|
"optional": true
|
|
108
109
|
},
|
|
110
|
+
"@deepseek-ai/dsh-home-paths": {
|
|
111
|
+
"optional": true
|
|
112
|
+
},
|
|
109
113
|
"@deepseek-ai/dsh-llm": {
|
|
110
114
|
"optional": true
|
|
111
115
|
},
|
|
@@ -136,6 +140,7 @@
|
|
|
136
140
|
"@deepseek-ai/dsh-compaction-basic": "^0.1.1-rc.2",
|
|
137
141
|
"@deepseek-ai/dsh-fs": "^0.1.1-rc.2",
|
|
138
142
|
"@deepseek-ai/dsh-goal": "^0.1.1-rc.2",
|
|
143
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.1-rc.2",
|
|
139
144
|
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
140
145
|
"@deepseek-ai/dsh-sandbox": "^0.1.1-rc.2",
|
|
141
146
|
"@deepseek-ai/dsh-session": "^0.1.1-rc.2",
|