@anionex/dsh-turn-rewind 0.1.1 → 0.2.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.md +19 -6
- package/README.zh.md +19 -6
- package/SECURITY.md +3 -1
- package/docs/LARGE_REPOSITORY_CHECKPOINT_DESIGN.md +340 -0
- package/docs/assets/turn-rewind-dialog.png +0 -0
- package/lib/client.js +291 -22
- package/lib/client.js.map +1 -1
- package/lib/deadline.js +13 -0
- package/lib/engine.js +622 -121
- package/lib/git-checkpoint.js +739 -0
- package/lib/git.js +295 -22
- package/lib/index.js +18 -2
- package/lib/path-utils.js +9 -0
- package/lib/rewind-host.js +250 -48
- package/lib/settings.js +58 -0
- package/lib/snapshot.js +113 -13
- package/lib/store.js +1361 -119
- package/lib/types/client/index.d.ts +74 -7
- package/lib/types/deadline.d.ts +6 -0
- package/lib/types/engine.d.ts +48 -2
- package/lib/types/git-checkpoint.d.ts +46 -0
- package/lib/types/git.d.ts +31 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/path-utils.d.ts +2 -0
- package/lib/types/rewind-host.d.ts +8 -1
- package/lib/types/settings.d.ts +44 -0
- package/lib/types/snapshot.d.ts +6 -1
- package/lib/types/store.d.ts +90 -6
- package/lib/types/types.d.ts +63 -1
- package/lib/types.js +2 -0
- package/lib/validate.js +68 -9
- package/package.json +21 -3
- package/scripts/benchmark-git-checkpoint.mjs +149 -0
- package/scripts/wrap-client.mjs +18 -0
- package/src/client/index.tsx +536 -41
- package/src/deadline.ts +18 -0
- package/src/engine.ts +695 -126
- package/src/git-checkpoint.ts +853 -0
- package/src/git.ts +337 -22
- package/src/index.ts +21 -2
- package/src/path-utils.ts +10 -0
- package/src/rewind-host.ts +278 -50
- package/src/settings.ts +93 -0
- package/src/snapshot.ts +122 -12
- package/src/store.ts +1502 -104
- package/src/types.ts +69 -1
- package/src/validate.ts +67 -7
|
@@ -37,14 +37,14 @@ interface RewindPortalBridgeProps {
|
|
|
37
37
|
}
|
|
38
38
|
interface SlotsLike {
|
|
39
39
|
inject(name: string, install: () => unknown): void;
|
|
40
|
-
register(entry: {
|
|
40
|
+
register<I, P>(entry: {
|
|
41
41
|
readonly name: string;
|
|
42
|
-
readonly id
|
|
43
|
-
readonly
|
|
44
|
-
readonly
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}, component: (props:
|
|
42
|
+
readonly id?: string;
|
|
43
|
+
readonly key?: string;
|
|
44
|
+
readonly order?: number;
|
|
45
|
+
readonly locale?: string;
|
|
46
|
+
readonly inject?: () => I;
|
|
47
|
+
}, component: (props: P) => ReactNode): () => void;
|
|
48
48
|
}
|
|
49
49
|
interface ClientContextLike {
|
|
50
50
|
readonly slots: SlotsLike;
|
|
@@ -59,9 +59,69 @@ interface ClientContextLike {
|
|
|
59
59
|
};
|
|
60
60
|
};
|
|
61
61
|
};
|
|
62
|
+
readonly settingsScope?: {
|
|
63
|
+
bind<T>(spec: {
|
|
64
|
+
readonly namespace: string;
|
|
65
|
+
}): SettingsScopeLike<T>;
|
|
66
|
+
};
|
|
62
67
|
effect(setup: () => (() => void), label?: string): unknown;
|
|
63
68
|
}
|
|
64
69
|
type ChangeKind = 'added' | 'deleted' | 'modified' | 'mode-changed' | 'type-changed';
|
|
70
|
+
/** Runtime-tunable Turn Rewind settings mirrored from the `turn-rewind` namespace. */
|
|
71
|
+
export interface TurnRewindSettingsValue {
|
|
72
|
+
readonly maxRestorePoints: number;
|
|
73
|
+
readonly maxTurnCheckpointsPerSession: number;
|
|
74
|
+
readonly maxFiles: number;
|
|
75
|
+
readonly maxFileBytes: number;
|
|
76
|
+
readonly maxSnapshotBytes: number;
|
|
77
|
+
readonly planTtlMs: number;
|
|
78
|
+
readonly staleLockMs: number;
|
|
79
|
+
readonly turnCheckpointMode: 'off' | 'git-native' | 'legacy';
|
|
80
|
+
readonly turnCheckpointTimeoutMs: number;
|
|
81
|
+
readonly turnCheckpointMaxNewBytes: number;
|
|
82
|
+
readonly turnCheckpointTrust: 'fast' | 'strict';
|
|
83
|
+
}
|
|
84
|
+
/** Browser mirror of one settings namespace, as bound by `ctx.settingsScope`. */
|
|
85
|
+
export interface SettingsScopeLike<T> {
|
|
86
|
+
getSnapshot(): SettingsScopeSnapshotLike<T>;
|
|
87
|
+
subscribe(listener: () => void): () => void;
|
|
88
|
+
set(field: string, value: unknown): Promise<void>;
|
|
89
|
+
unset(field: string): Promise<void>;
|
|
90
|
+
}
|
|
91
|
+
/** Sync snapshot shape shared by every settings scope. */
|
|
92
|
+
export interface SettingsScopeSnapshotLike<T> {
|
|
93
|
+
readonly status: 'loading' | 'ready' | 'unavailable';
|
|
94
|
+
readonly value: T | undefined;
|
|
95
|
+
readonly base: unknown;
|
|
96
|
+
readonly user: unknown;
|
|
97
|
+
readonly revision: number | undefined;
|
|
98
|
+
readonly writable: boolean;
|
|
99
|
+
readonly mode: 'host' | 'memory';
|
|
100
|
+
}
|
|
101
|
+
/** One checkpoint row in the storage-management overview. */
|
|
102
|
+
export interface ManageRestorePoint {
|
|
103
|
+
readonly id: string;
|
|
104
|
+
readonly kind: string;
|
|
105
|
+
readonly format: number;
|
|
106
|
+
readonly createdAt: number;
|
|
107
|
+
readonly totalBytes: number;
|
|
108
|
+
readonly fileCount: number;
|
|
109
|
+
readonly sessionId?: string;
|
|
110
|
+
readonly label?: string;
|
|
111
|
+
}
|
|
112
|
+
/** One workspace group in the storage-management overview. */
|
|
113
|
+
export interface ManageWorkspace {
|
|
114
|
+
readonly workspace: string;
|
|
115
|
+
readonly totalBytes: number;
|
|
116
|
+
readonly recoveryCount: number;
|
|
117
|
+
readonly restorePoints: readonly ManageRestorePoint[];
|
|
118
|
+
}
|
|
119
|
+
/** Storage-management overview served by `/turn-rewind/manage`. */
|
|
120
|
+
export interface ManageOverview {
|
|
121
|
+
readonly storageDir: string;
|
|
122
|
+
readonly totalBytes: number;
|
|
123
|
+
readonly workspaces: readonly ManageWorkspace[];
|
|
124
|
+
}
|
|
65
125
|
/** Return the rewind anchor and editable text owned by one direct user message. */
|
|
66
126
|
export declare function selectRewindMessage(node: ConversationNodeLike): RewindMatch | null;
|
|
67
127
|
/** Browser plugin entry: bridge every direct user-message action row to the rewind UI. */
|
|
@@ -71,6 +131,13 @@ export declare function apply(ctx: ClientContextLike): void;
|
|
|
71
131
|
export declare function RewindMessagePortals({ sessionId, openRestoredSession, useSession }: RewindPortalBridgeProps): ReactNode;
|
|
72
132
|
/** User-message action and its review-first file/conversation restore dialog. */
|
|
73
133
|
export declare function RewindMessageAction({ matched, sessionId, openRestoredSession }: RewindMessageActionProps): ReactNode;
|
|
134
|
+
interface TurnRewindSettingsCardProps {
|
|
135
|
+
readonly scope: SettingsScopeLike<TurnRewindSettingsValue> | undefined;
|
|
136
|
+
}
|
|
137
|
+
/** Settings card for the `turn-rewind` namespace: runtime options plus checkpoint management. */
|
|
138
|
+
export declare function TurnRewindSettingsCard({ scope }: TurnRewindSettingsCardProps): ReactNode;
|
|
139
|
+
/** Format one byte count with human-friendly units. */
|
|
140
|
+
export declare function formatBytes(bytes: number): string;
|
|
74
141
|
/** Resolve one conversation node to its DOM row key and rewind match. */
|
|
75
142
|
export declare function selectRewindMessageTarget(value: RewindNodeLike): {
|
|
76
143
|
readonly matched: RewindMatch;
|
package/lib/types/engine.d.ts
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import { LedgerStore } from './store.js';
|
|
2
|
-
import { type ChangeLedgerConfig, type RecoverySummary, type ResolvedChangeLedgerConfig, type RestorePlan, type RestorePointInspection, type RestorePointManifest, type RestorePointSummary, type RestoreResult } from './types.js';
|
|
2
|
+
import { type ChangeLedgerConfig, type RecoverySummary, type ResolvedChangeLedgerConfig, type RestorePlan, type RestorePointInspection, type RestorePointManifest, type RestorePointSummary, type RestoreResult, type WorkspaceOverview, type WorkspacePurgeReport } from './types.js';
|
|
3
3
|
/** Persistent workspace change-set engine, independent of the DSH tool adapter. */
|
|
4
4
|
export declare class ChangeLedgerEngine {
|
|
5
|
-
readonly
|
|
5
|
+
private readonly currentConfig;
|
|
6
6
|
readonly store: LedgerStore;
|
|
7
7
|
private readonly plans;
|
|
8
8
|
private readonly activePlans;
|
|
9
9
|
private readonly ready;
|
|
10
10
|
/** Build an engine and start crash-journal reconciliation. */
|
|
11
11
|
constructor(config?: ChangeLedgerConfig);
|
|
12
|
+
/** Currently resolved configuration; runtime-tunable fields update in place. */
|
|
13
|
+
get config(): ResolvedChangeLedgerConfig;
|
|
14
|
+
/**
|
|
15
|
+
* Swap runtime-tunable configuration (limits, modes, timeouts, trust) in place.
|
|
16
|
+
* The storage root must never move once the engine owns locks and journals.
|
|
17
|
+
*/
|
|
18
|
+
updateConfig(config: ChangeLedgerConfig): void;
|
|
12
19
|
/** Wait for startup reconciliation and return the number of interrupted journals found. */
|
|
13
20
|
initialize(): Promise<number>;
|
|
21
|
+
private initializeStore;
|
|
22
|
+
private acquireWorkspace;
|
|
23
|
+
private reconcileGitCheckpointJournals;
|
|
24
|
+
private publishGitManifest;
|
|
25
|
+
private deleteManifestWithGit;
|
|
26
|
+
private tryReadManifest;
|
|
14
27
|
/** Create a durable restore point for the current Git worktree. */
|
|
15
28
|
create(options: {
|
|
16
29
|
readonly cwd: string;
|
|
@@ -26,6 +39,25 @@ export declare class ChangeLedgerEngine {
|
|
|
26
39
|
readonly turnStartSeq: number;
|
|
27
40
|
readonly signal?: AbortSignal;
|
|
28
41
|
}): Promise<RestorePointSummary>;
|
|
42
|
+
/** Persist one bounded automatic-checkpoint skip without blocking a later turn retry. */
|
|
43
|
+
recordTurnCheckpointSkip(options: {
|
|
44
|
+
readonly cwd: string;
|
|
45
|
+
readonly sessionId: string;
|
|
46
|
+
readonly turn: number;
|
|
47
|
+
readonly turnStartSeq: number;
|
|
48
|
+
readonly reason: string;
|
|
49
|
+
readonly signal?: AbortSignal;
|
|
50
|
+
}): Promise<void>;
|
|
51
|
+
/** Read a durable skip marker for one exact prompt boundary. */
|
|
52
|
+
findTurnCheckpointSkip(options: {
|
|
53
|
+
readonly cwd: string;
|
|
54
|
+
readonly sessionId: string;
|
|
55
|
+
readonly turn: number;
|
|
56
|
+
readonly turnStartSeq: number;
|
|
57
|
+
readonly signal?: AbortSignal;
|
|
58
|
+
}): Promise<{
|
|
59
|
+
readonly reason: string;
|
|
60
|
+
} | undefined>;
|
|
29
61
|
/** Find the prompt-anchored checkpoint captured before one session turn. */
|
|
30
62
|
findTurnCheckpoint(options: {
|
|
31
63
|
readonly cwd: string;
|
|
@@ -80,9 +112,23 @@ export declare class ChangeLedgerEngine {
|
|
|
80
112
|
readonly cwd: string;
|
|
81
113
|
readonly signal?: AbortSignal;
|
|
82
114
|
}): Promise<RecoverySummary[]>;
|
|
115
|
+
/** Inventory every workspace this storage root has ever persisted state for. */
|
|
116
|
+
listWorkspaces(options?: {
|
|
117
|
+
readonly signal?: AbortSignal;
|
|
118
|
+
}): Promise<WorkspaceOverview[]>;
|
|
119
|
+
/** Delete unprotected restore points recorded for one workspace and collect unused blobs. */
|
|
120
|
+
purgeWorkspace(options: {
|
|
121
|
+
readonly workspace: string;
|
|
122
|
+
/** Restrict the purge to these restore points; protection rules still apply. */
|
|
123
|
+
readonly restorePointIds?: readonly string[];
|
|
124
|
+
readonly signal?: AbortSignal;
|
|
125
|
+
}): Promise<WorkspacePurgeReport>;
|
|
126
|
+
private purgeWorkspaceDir;
|
|
83
127
|
private createLocked;
|
|
84
128
|
private restorePaths;
|
|
129
|
+
private assertPathFresh;
|
|
85
130
|
private verifyPaths;
|
|
131
|
+
private captureSelectedEntries;
|
|
86
132
|
private assertStorageSeparated;
|
|
87
133
|
private expirePlans;
|
|
88
134
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type ResolvedChangeLedgerConfig, type RestorePointManifestV2 } from './types.js';
|
|
2
|
+
interface CacheRecord {
|
|
3
|
+
readonly kind: 'file' | 'symlink';
|
|
4
|
+
readonly mode: number;
|
|
5
|
+
readonly size: string;
|
|
6
|
+
readonly mtimeNs: string;
|
|
7
|
+
readonly ctimeNs: string;
|
|
8
|
+
readonly dev: string;
|
|
9
|
+
readonly ino: string;
|
|
10
|
+
readonly indexOid?: string;
|
|
11
|
+
readonly attributes: string;
|
|
12
|
+
readonly oid: string;
|
|
13
|
+
readonly target?: string;
|
|
14
|
+
}
|
|
15
|
+
interface GitCache {
|
|
16
|
+
readonly version: 2;
|
|
17
|
+
readonly storeId: string;
|
|
18
|
+
readonly worktreeId: string;
|
|
19
|
+
readonly paths: Readonly<Record<string, CacheRecord>>;
|
|
20
|
+
readonly checksum: string;
|
|
21
|
+
}
|
|
22
|
+
export interface GitCheckpointCapture {
|
|
23
|
+
readonly manifest: RestorePointManifestV2;
|
|
24
|
+
readonly cache: GitCache;
|
|
25
|
+
}
|
|
26
|
+
/** Create a complete Git-native turn checkpoint without writing the real index. */
|
|
27
|
+
export declare function captureGitTurnCheckpoint(options: {
|
|
28
|
+
readonly cwd: string;
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly sessionId: string;
|
|
31
|
+
readonly turn: number;
|
|
32
|
+
readonly turnStartSeq: number;
|
|
33
|
+
readonly config: ResolvedChangeLedgerConfig;
|
|
34
|
+
readonly signal?: AbortSignal;
|
|
35
|
+
}): Promise<GitCheckpointCapture>;
|
|
36
|
+
/** Publish a prepared checkpoint under its exact private ref. */
|
|
37
|
+
export declare function publishGitCheckpoint(manifest: RestorePointManifestV2, storageDir: string, signal?: AbortSignal): Promise<void>;
|
|
38
|
+
/** Verify persisted Git metadata before exposing or restoring a v2 point. */
|
|
39
|
+
export declare function verifyGitCheckpoint(manifest: RestorePointManifestV2, signal?: AbortSignal): Promise<void>;
|
|
40
|
+
/** Compare-and-swap delete a v2 private ref. */
|
|
41
|
+
export declare function deleteGitCheckpoint(manifest: RestorePointManifestV2, storageDir: string, signal?: AbortSignal): Promise<void>;
|
|
42
|
+
/** Delete an exact private ref target, including during startup journal reconciliation. */
|
|
43
|
+
export declare function deleteGitCheckpointRef(workspace: string, ref: string, commit: string, storageDir: string, signal?: AbortSignal): Promise<void>;
|
|
44
|
+
/** Read one v2 Git blob for selective restore. */
|
|
45
|
+
export declare function readGitBlob(workspace: string, oid: string, expectedSize: number, signal?: AbortSignal): Promise<Buffer>;
|
|
46
|
+
export {};
|
package/lib/types/git.d.ts
CHANGED
|
@@ -4,11 +4,42 @@ export interface RepositorySnapshotSource {
|
|
|
4
4
|
readonly state: RepositoryState;
|
|
5
5
|
readonly paths: readonly string[];
|
|
6
6
|
}
|
|
7
|
+
/** Durable identity and shared lock location for one concrete Git worktree. */
|
|
8
|
+
export interface GitWorktreeIdentity {
|
|
9
|
+
readonly root: string;
|
|
10
|
+
readonly gitDir: string;
|
|
11
|
+
readonly commonDir: string;
|
|
12
|
+
readonly worktreeId: string;
|
|
13
|
+
readonly lockPath: string;
|
|
14
|
+
}
|
|
7
15
|
/** Discover the Git worktree owning `cwd` and enumerate tracked/non-ignored paths. */
|
|
8
16
|
export declare function discoverRepository(cwd: string, signal?: AbortSignal): Promise<RepositorySnapshotSource>;
|
|
9
17
|
/** Resolve the canonical Git worktree root owning `cwd` without inventorying its files. */
|
|
10
18
|
export declare function discoverRepositoryRoot(cwd: string, signal?: AbortSignal): Promise<string>;
|
|
19
|
+
/** Create or read the per-worktree identity used by refs and cross-store locking. */
|
|
20
|
+
export declare function ensureGitWorktreeIdentity(cwd: string, signal?: AbortSignal): Promise<GitWorktreeIdentity>;
|
|
21
|
+
/** Resolve a moved linked worktree from its common directory and durable identity. */
|
|
22
|
+
export declare function resolveGitWorktreeRoot(commonDir: string, worktreeId: string, fallback: string, signal?: AbortSignal): Promise<string>;
|
|
11
23
|
/** Return true when two repository fences refer to the same checkout state. */
|
|
12
24
|
export declare function sameRepositoryFence(left: RepositoryState, right: RepositoryState): boolean;
|
|
25
|
+
/** Run Git with stable non-interactive defaults and return UTF-8 output. */
|
|
26
|
+
export declare function runGit(cwd: string, args: readonly string[], signal?: AbortSignal, options?: {
|
|
27
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
28
|
+
readonly maxBuffer?: number;
|
|
29
|
+
}): Promise<string>;
|
|
30
|
+
/** Run Git and return undefined for its ordinary "not found" exit status. */
|
|
31
|
+
export declare function runGitOptional(cwd: string, args: readonly string[], signal?: AbortSignal, options?: {
|
|
32
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
33
|
+
readonly maxBuffer?: number;
|
|
34
|
+
}): Promise<string | undefined>;
|
|
35
|
+
/** Run Git with binary stdin and return UTF-8 stdout. */
|
|
36
|
+
export declare function runGitInput(cwd: string, args: readonly string[], input: Buffer | string, signal?: AbortSignal, options?: {
|
|
37
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
38
|
+
}): Promise<string>;
|
|
39
|
+
/** Run Git and return raw stdout bytes. */
|
|
40
|
+
export declare function runGitBuffer(cwd: string, args: readonly string[], signal?: AbortSignal, options?: {
|
|
41
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
42
|
+
readonly maxBuffer?: number;
|
|
43
|
+
}): Promise<Buffer>;
|
|
13
44
|
/** Return the Git metadata directory for diagnostics. */
|
|
14
45
|
export declare function gitMetadataParent(state: RepositoryState): string;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { ChangeLedgerConfig } from './types.js';
|
|
|
8
8
|
export * from './engine.js';
|
|
9
9
|
export * from './errors.js';
|
|
10
10
|
export * from './rewind-host.js';
|
|
11
|
+
export * from './settings.js';
|
|
11
12
|
export * from './types.js';
|
|
12
13
|
declare module '@deepseek-ai/cordis' {
|
|
13
14
|
interface Context {
|
|
@@ -39,5 +40,11 @@ export declare class ChangeLedgerService {
|
|
|
39
40
|
delete(options: Parameters<ChangeLedgerEngine['delete']>[0]): ReturnType<ChangeLedgerEngine['delete']>;
|
|
40
41
|
/** List interrupted restore operations and their rescue points. */
|
|
41
42
|
listRecovery(options: Parameters<ChangeLedgerEngine['listRecovery']>[0]): ReturnType<ChangeLedgerEngine['listRecovery']>;
|
|
43
|
+
/** Inventory every workspace this storage root has persisted state for. */
|
|
44
|
+
listWorkspaces(options?: Parameters<ChangeLedgerEngine['listWorkspaces']>[0]): ReturnType<ChangeLedgerEngine['listWorkspaces']>;
|
|
45
|
+
/** Delete unprotected restore points recorded for one workspace. */
|
|
46
|
+
purgeWorkspace(options: Parameters<ChangeLedgerEngine['purgeWorkspace']>[0]): ReturnType<ChangeLedgerEngine['purgeWorkspace']>;
|
|
47
|
+
/** Swap runtime-tunable configuration; the storage root must stay fixed. */
|
|
48
|
+
updateConfig(config: ChangeLedgerConfig): void;
|
|
42
49
|
}
|
|
43
50
|
export default ChangeLedgerService;
|
|
@@ -16,6 +16,8 @@ export declare function readJson(path: string): Promise<unknown>;
|
|
|
16
16
|
export declare function pathExists(path: string): Promise<boolean>;
|
|
17
17
|
/** Ensure every existing parent below `root` is a real directory, never a symlink. */
|
|
18
18
|
export declare function ensureSafeParents(root: string, target: string): Promise<void>;
|
|
19
|
+
/** Validate existing parents without creating missing directories. */
|
|
20
|
+
export declare function assertSafeParents(root: string, target: string): Promise<void>;
|
|
19
21
|
/** Replace a path with a regular file using a sibling temporary file. */
|
|
20
22
|
export declare function replaceRegularFile(path: string, content: Buffer, mode: number): Promise<void>;
|
|
21
23
|
/** Replace a path with a symbolic link using a sibling temporary name. */
|
|
@@ -115,14 +115,16 @@ export declare class TurnCheckpointCoordinator {
|
|
|
115
115
|
private readonly captures;
|
|
116
116
|
private readonly pending;
|
|
117
117
|
private readonly failures;
|
|
118
|
+
private readonly skips;
|
|
118
119
|
private readonly workspaceTails;
|
|
119
120
|
constructor(engine: ChangeLedgerEngine);
|
|
120
121
|
/** Install the first-step gate; checkpoint failures are recorded but never reject the user turn. */
|
|
121
122
|
install(ctx: Context): void;
|
|
122
123
|
/** Current capture state for a session turn when no durable checkpoint exists yet. */
|
|
123
124
|
state(sessionId: string, turn: number): {
|
|
124
|
-
readonly status: 'pending' | 'failed' | 'missing';
|
|
125
|
+
readonly status: 'pending' | 'failed' | 'skipped' | 'missing';
|
|
125
126
|
readonly error?: string;
|
|
127
|
+
readonly reason?: string;
|
|
126
128
|
};
|
|
127
129
|
private capture;
|
|
128
130
|
private serializeWorkspace;
|
|
@@ -130,6 +132,11 @@ export declare class TurnCheckpointCoordinator {
|
|
|
130
132
|
}
|
|
131
133
|
/** Register the same-origin preview/apply endpoint consumed by the browser half. */
|
|
132
134
|
export declare function installRewindHttp(ctx: Context, engine: ChangeLedgerEngine, coordinator: TurnCheckpointCoordinator): void;
|
|
135
|
+
export declare const MANAGE_HTTP_PATH = "/turn-rewind/manage";
|
|
136
|
+
/** Register the same-origin storage-management endpoint consumed by the settings card. */
|
|
137
|
+
export declare function installManageHttp(ctx: Context, engine: ChangeLedgerEngine): void;
|
|
138
|
+
/** Build the storage-management route as a testable unit. */
|
|
139
|
+
export declare function createManageHttpHandler(engine: ChangeLedgerEngine): (request: HttpRequestLike, response: HttpResponseLike) => Promise<void>;
|
|
133
140
|
/** Build the exact-route handler as a testable unit. */
|
|
134
141
|
export declare function createRewindHttpHandler(ctx: Pick<Context, 'sessions' | 'sessionQuery' | 'apiProxy'> & {
|
|
135
142
|
readonly agents?: AgentsLike;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime settings surface for Turn Rewind on the DSH web settings page.
|
|
3
|
+
* @module @anionex/dsh-turn-rewind
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
import z from '@deepseek-ai/schemastery';
|
|
7
|
+
import { type ChangeLedgerEngine } from './engine.js';
|
|
8
|
+
import type { ChangeLedgerConfig } from './types.js';
|
|
9
|
+
/** Namespace join key shared by the host section and the browser settings card. */
|
|
10
|
+
export declare const TURN_REWIND_SETTINGS_NAMESPACE: Branded<"SettingsNamespace">;
|
|
11
|
+
/** Every runtime-tunable field of {@link ChangeLedgerConfig}; `storageDir` stays config-layer only. */
|
|
12
|
+
export interface TurnRewindSettings {
|
|
13
|
+
/** Maximum user and rescue restore points retained per workspace. */
|
|
14
|
+
maxRestorePoints: number;
|
|
15
|
+
/** Maximum automatic turn checkpoints retained per session. */
|
|
16
|
+
maxTurnCheckpointsPerSession: number;
|
|
17
|
+
/** Maximum number of files in one restore point. */
|
|
18
|
+
maxFiles: number;
|
|
19
|
+
/** Maximum bytes read from one regular file. */
|
|
20
|
+
maxFileBytes: number;
|
|
21
|
+
/** Maximum aggregate regular-file bytes in one restore point. */
|
|
22
|
+
maxSnapshotBytes: number;
|
|
23
|
+
/** Restore-plan lifetime in milliseconds. */
|
|
24
|
+
planTtlMs: number;
|
|
25
|
+
/** Age after which a lock whose owner is gone may be reclaimed. */
|
|
26
|
+
staleLockMs: number;
|
|
27
|
+
/** Automatic turn-checkpoint implementation; `off` records durable skips instead. */
|
|
28
|
+
turnCheckpointMode: 'off' | 'git-native' | 'legacy';
|
|
29
|
+
/** Maximum time one automatic checkpoint may block the first Agent step. */
|
|
30
|
+
turnCheckpointTimeoutMs: number;
|
|
31
|
+
/** Maximum uncached worktree bytes read by one automatic Git-native checkpoint. */
|
|
32
|
+
turnCheckpointMaxNewBytes: number;
|
|
33
|
+
/** Fast trusts fenced Git/stat metadata; strict rereads every eligible path. */
|
|
34
|
+
turnCheckpointTrust: 'fast' | 'strict';
|
|
35
|
+
}
|
|
36
|
+
/** Schemastery schema for the `turn-rewind` settings namespace. */
|
|
37
|
+
export declare const TurnRewindSettingsSchema: z<TurnRewindSettings>;
|
|
38
|
+
/**
|
|
39
|
+
* Register the `turn-rewind` settings namespace and apply its resolved value to the
|
|
40
|
+
* running engine. The composition entry (from `cordis.patch.yml`) is the base layer;
|
|
41
|
+
* the user layer persists through the DSH settings provider. `storageDir` is never
|
|
42
|
+
* carried by the namespace: the storage root must not move while the engine runs.
|
|
43
|
+
*/
|
|
44
|
+
export declare function installTurnRewindSettings(ctx: Context, config: ChangeLedgerConfig, engine: ChangeLedgerEngine): void;
|
package/lib/types/snapshot.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ResolvedChangeLedgerConfig, SnapshotEntry, WorkspaceChange } from
|
|
|
5
5
|
export interface CapturedTree {
|
|
6
6
|
readonly source: RepositorySnapshotSource;
|
|
7
7
|
readonly entries: Readonly<Record<string, SnapshotEntry>>;
|
|
8
|
+
readonly gitEntries?: Readonly<Record<string, SnapshotEntry>>;
|
|
8
9
|
readonly treeHash: string;
|
|
9
10
|
readonly fileCount: number;
|
|
10
11
|
readonly totalBytes: number;
|
|
@@ -14,6 +15,7 @@ export declare function captureTree(options: {
|
|
|
14
15
|
readonly cwd: string;
|
|
15
16
|
readonly config: ResolvedChangeLedgerConfig;
|
|
16
17
|
readonly store?: LedgerStore;
|
|
18
|
+
readonly gitObjectFormat?: 'sha1' | 'sha256';
|
|
17
19
|
readonly signal?: AbortSignal;
|
|
18
20
|
}): Promise<CapturedTree>;
|
|
19
21
|
/**
|
|
@@ -25,11 +27,14 @@ export declare function captureStableTree(options: {
|
|
|
25
27
|
readonly cwd: string;
|
|
26
28
|
readonly config: ResolvedChangeLedgerConfig;
|
|
27
29
|
readonly store?: LedgerStore;
|
|
30
|
+
readonly gitObjectFormat?: 'sha1' | 'sha256';
|
|
28
31
|
readonly signal?: AbortSignal;
|
|
29
32
|
}): Promise<CapturedTree>;
|
|
30
33
|
/** Compute stable path-level differences between two captured trees. */
|
|
31
|
-
export declare function diffTrees(before: Readonly<Record<string, SnapshotEntry>>, after: Readonly<Record<string, SnapshotEntry>>): WorkspaceChange[];
|
|
34
|
+
export declare function diffTrees(before: Readonly<Record<string, SnapshotEntry>>, after: Readonly<Record<string, SnapshotEntry>>, comparisonBefore?: Readonly<Record<string, SnapshotEntry>>, comparisonAfter?: Readonly<Record<string, SnapshotEntry>>): WorkspaceChange[];
|
|
32
35
|
/** Return whether two snapshot entries are byte/type/mode equivalent. */
|
|
33
36
|
export declare function entriesEqual(left: SnapshotEntry | undefined, right: SnapshotEntry | undefined): boolean;
|
|
34
37
|
/** Hash a complete path map into a deterministic tree identity. */
|
|
35
38
|
export declare function hashTree(entries: Readonly<Record<string, SnapshotEntry>>): string;
|
|
39
|
+
/** Byte-verify one workspace-relative path without following a final symlink. */
|
|
40
|
+
export declare function captureSnapshotEntry(root: string, path: string, maxFileBytes: number, signal?: AbortSignal, gitObjectFormat?: 'sha1' | 'sha256'): Promise<SnapshotEntry | undefined>;
|
package/lib/types/store.d.ts
CHANGED
|
@@ -1,12 +1,43 @@
|
|
|
1
|
-
import type { ResolvedChangeLedgerConfig, RestoreOperation, RestorePointManifest } from './types.js';
|
|
1
|
+
import type { ResolvedChangeLedgerConfig, RestoreOperation, RestorePointManifest, RestorePointManifestV2 } from './types.js';
|
|
2
|
+
export interface GitCheckpointJournal {
|
|
3
|
+
readonly version: 1;
|
|
4
|
+
readonly action: 'publish' | 'delete';
|
|
5
|
+
readonly storeId: string;
|
|
6
|
+
readonly workspace: string;
|
|
7
|
+
readonly commonDir: string;
|
|
8
|
+
readonly worktreeId: string;
|
|
9
|
+
readonly restorePointId: string;
|
|
10
|
+
readonly ref: string;
|
|
11
|
+
readonly commit: string;
|
|
12
|
+
readonly createdAt: number;
|
|
13
|
+
}
|
|
14
|
+
export interface TurnCheckpointSkip {
|
|
15
|
+
readonly version: 1;
|
|
16
|
+
readonly sessionId: string;
|
|
17
|
+
readonly turn: number;
|
|
18
|
+
readonly turnStartSeq: number;
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
readonly createdAt: number;
|
|
21
|
+
}
|
|
2
22
|
/** Durable content-addressed storage and per-workspace locking. */
|
|
3
23
|
export declare class LedgerStore {
|
|
4
24
|
readonly config: ResolvedChangeLedgerConfig;
|
|
25
|
+
private readonly workspaceKeys;
|
|
26
|
+
private storeIdValue;
|
|
5
27
|
constructor(config: ResolvedChangeLedgerConfig);
|
|
6
28
|
/** Create the state root and reconcile crash-interrupted operations. */
|
|
7
29
|
initialize(): Promise<number>;
|
|
30
|
+
/** Durable owner of this storage root's private Git namespace. */
|
|
31
|
+
get storeId(): string;
|
|
8
32
|
/** Acquire the exclusive lock for one canonical workspace. */
|
|
9
|
-
acquire(workspace: string
|
|
33
|
+
acquire(workspace: string, sharedLockPath?: string, binding?: {
|
|
34
|
+
readonly commonDir: string;
|
|
35
|
+
readonly gitDir: string;
|
|
36
|
+
readonly worktreeId: string;
|
|
37
|
+
}, signal?: AbortSignal): Promise<() => Promise<void>>;
|
|
38
|
+
/** Acquire only the durable directory lock when the recorded worktree is no longer available. */
|
|
39
|
+
acquireWorkspaceDir(workspaceDir: string, workspace: string, signal?: AbortSignal): Promise<() => Promise<void>>;
|
|
40
|
+
private acquireOne;
|
|
10
41
|
/** Persist a blob if it is not already present, and verify existing content. */
|
|
11
42
|
putBlob(workspace: string, hash: string, content: Buffer): Promise<void>;
|
|
12
43
|
/** Read and verify one content-addressed blob. */
|
|
@@ -16,24 +47,77 @@ export declare class LedgerStore {
|
|
|
16
47
|
/** Load and validate one restore-point manifest. */
|
|
17
48
|
readManifest(workspace: string, id: string): Promise<RestorePointManifest>;
|
|
18
49
|
/** List all validated restore points for one workspace, newest first. */
|
|
19
|
-
listManifests(workspace: string): Promise<RestorePointManifest[]>;
|
|
50
|
+
listManifests(workspace: string, signal?: AbortSignal): Promise<RestorePointManifest[]>;
|
|
51
|
+
/** List validated manifests directly from one storage directory, newest first. */
|
|
52
|
+
listManifestsInDir(workspaceDir: string, signal?: AbortSignal): Promise<RestorePointManifest[]>;
|
|
20
53
|
/** Delete one restore-point manifest. Blobs remain until garbage collection succeeds. */
|
|
21
54
|
deleteManifest(workspace: string, id: string): Promise<void>;
|
|
55
|
+
/** Delete one restore-point manifest directly from its storage directory. */
|
|
56
|
+
deleteManifestInDir(workspaceDir: string, id: string): Promise<void>;
|
|
57
|
+
/** Persist the intent needed to finish or roll back a Git ref/manifest transition after a crash. */
|
|
58
|
+
writeGitCheckpointJournal(action: GitCheckpointJournal['action'], manifest: RestorePointManifestV2): Promise<void>;
|
|
59
|
+
/** List every persisted workspace directory under this storage root. */
|
|
60
|
+
listWorkspaceDirs(signal?: AbortSignal): Promise<string[]>;
|
|
61
|
+
/** List every pending Git checkpoint transition across this storage root. */
|
|
62
|
+
listGitCheckpointJournals(): Promise<GitCheckpointJournal[]>;
|
|
63
|
+
/** List pending Git checkpoint transitions directly from one storage directory. */
|
|
64
|
+
listGitCheckpointJournalsInDir(workspaceDir: string, signal?: AbortSignal): Promise<GitCheckpointJournal[]>;
|
|
65
|
+
/** Remove one completed Git checkpoint transition journal. */
|
|
66
|
+
deleteGitCheckpointJournal(workspace: string, id: string): Promise<void>;
|
|
67
|
+
/** Persist one explicit automatic-checkpoint skip so the UI survives restarts. */
|
|
68
|
+
writeTurnCheckpointSkip(workspace: string, skip: TurnCheckpointSkip): Promise<void>;
|
|
69
|
+
/** Read one durable automatic-checkpoint skip. */
|
|
70
|
+
readTurnCheckpointSkip(workspace: string, sessionId: string, turn: number, turnStartSeq: number): Promise<TurnCheckpointSkip | undefined>;
|
|
71
|
+
/** Remove a stale skip after the same turn obtains a ready checkpoint. */
|
|
72
|
+
deleteTurnCheckpointSkip(workspace: string, sessionId: string, turn: number, turnStartSeq: number): Promise<void>;
|
|
73
|
+
/** Mark a legacy capture whose unreferenced blobs need crash recovery. */
|
|
74
|
+
writeSnapshotCleanup(workspace: string): Promise<void>;
|
|
75
|
+
/** Clear a completed legacy-capture cleanup marker. */
|
|
76
|
+
deleteSnapshotCleanup(workspace: string): Promise<void>;
|
|
22
77
|
/** Persist one restore-operation journal. */
|
|
23
78
|
writeOperation(operation: RestoreOperation): Promise<void>;
|
|
24
79
|
/** List validated restore operations for one workspace. */
|
|
25
80
|
listOperations(workspace: string): Promise<RestoreOperation[]>;
|
|
81
|
+
/** List validated restore operations directly from one storage directory. */
|
|
82
|
+
listOperationsInDir(workspaceDir: string, signal?: AbortSignal): Promise<RestoreOperation[]>;
|
|
26
83
|
/** Return whether an incomplete operation still references a restore point. */
|
|
27
84
|
isReferencedByRecovery(workspace: string, restorePointId: string): Promise<boolean>;
|
|
28
85
|
/** Delete blobs not referenced by any remaining manifest. */
|
|
29
|
-
collectGarbage(workspace: string, additionalReferenced?: Iterable<string
|
|
86
|
+
collectGarbage(workspace: string, additionalReferenced?: Iterable<string>, signal?: AbortSignal): Promise<{
|
|
87
|
+
deletedBlobs: number;
|
|
88
|
+
retainedBlobs: number;
|
|
89
|
+
}>;
|
|
90
|
+
/** Delete blobs under one storage directory that no manifest in it references. */
|
|
91
|
+
collectGarbageInDir(workspaceDir: string, signal?: AbortSignal): Promise<{
|
|
30
92
|
deletedBlobs: number;
|
|
31
93
|
retainedBlobs: number;
|
|
32
94
|
}>;
|
|
95
|
+
/** Remove every persisted automatic-checkpoint skip marker under one storage directory. */
|
|
96
|
+
clearTurnOutcomesInDir(workspaceDir: string): Promise<number>;
|
|
97
|
+
private sweepBlobs;
|
|
98
|
+
/** Finish a prior bounded legacy capture's orphan cleanup, if one is pending. */
|
|
99
|
+
reconcileSnapshotCleanup(workspace: string, signal?: AbortSignal): Promise<boolean>;
|
|
33
100
|
private workspaceDir;
|
|
101
|
+
private assertManifestOwned;
|
|
102
|
+
private assertGitCheckpointJournalOwned;
|
|
103
|
+
private assertManifestWorkspaceIdentity;
|
|
104
|
+
private assertGitCheckpointJournalWorkspaceIdentity;
|
|
34
105
|
private manifestPath;
|
|
35
106
|
private operationPath;
|
|
107
|
+
private gitCheckpointJournalPath;
|
|
108
|
+
private turnCheckpointSkipPath;
|
|
36
109
|
private blobPath;
|
|
37
|
-
private
|
|
38
|
-
private
|
|
110
|
+
private bindGitWorkspace;
|
|
111
|
+
private reconcileWorkspaceMigrations;
|
|
112
|
+
private completeWorkspaceMigration;
|
|
113
|
+
private assertNoUnresolvedWorkspaceOwnership;
|
|
114
|
+
private ensureWorktreeClaim;
|
|
115
|
+
private completeWorkspaceRebind;
|
|
116
|
+
private rewriteWorkspaceRoot;
|
|
117
|
+
private prepareLockReclaim;
|
|
118
|
+
private completeLockReclaims;
|
|
119
|
+
private acquireLockReclaimOwnership;
|
|
120
|
+
private lockIsReclaimable;
|
|
39
121
|
}
|
|
122
|
+
/** Create or read the durable owner of one storage root's private Git namespace. */
|
|
123
|
+
export declare function ensureStoreId(storageDir: string): Promise<string>;
|