@wangjiehu/sandbox 0.1.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.
@@ -0,0 +1,33 @@
1
+ interface FileBackup {
2
+ path: string;
3
+ originalContent: string | null;
4
+ originalHash?: string;
5
+ }
6
+ interface Checkpoint {
7
+ id: string;
8
+ sessionId: string;
9
+ timestamp: string;
10
+ toolCallId: string;
11
+ backups: FileBackup[];
12
+ }
13
+
14
+ declare class CheckpointManager {
15
+ private cwd;
16
+ private sessionId;
17
+ private checkpoints;
18
+ constructor(cwd: string, sessionId: string);
19
+ captureBeforeState(toolCallId: string, filePath: string): Promise<Checkpoint>;
20
+ getCheckpoints(): Checkpoint[];
21
+ }
22
+
23
+ declare class RollbackManager {
24
+ private cwd;
25
+ constructor(cwd: string);
26
+ rollback(checkpoint: Checkpoint): {
27
+ success: boolean;
28
+ error?: string;
29
+ restored: string[];
30
+ };
31
+ }
32
+
33
+ export { type Checkpoint, CheckpointManager, type FileBackup, RollbackManager };
package/dist/index.js ADDED
@@ -0,0 +1,98 @@
1
+ // src/CheckpointManager.ts
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
3
+ import { join } from "path";
4
+ import { generateId, resolveSafePath } from "@wangjiehu/shared";
5
+ var CheckpointManager = class {
6
+ constructor(cwd, sessionId) {
7
+ this.cwd = cwd;
8
+ this.sessionId = sessionId;
9
+ }
10
+ cwd;
11
+ sessionId;
12
+ checkpoints = [];
13
+ async captureBeforeState(toolCallId, filePath) {
14
+ let originalContent = null;
15
+ try {
16
+ const safePath = resolveSafePath(this.cwd, filePath);
17
+ if (existsSync(safePath)) {
18
+ originalContent = readFileSync(safePath, "utf8");
19
+ }
20
+ } catch (e) {
21
+ }
22
+ const backup = {
23
+ path: filePath,
24
+ originalContent
25
+ };
26
+ const checkpoint = {
27
+ id: generateId("cp"),
28
+ sessionId: this.sessionId,
29
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
30
+ toolCallId,
31
+ backups: [backup]
32
+ };
33
+ this.checkpoints.push(checkpoint);
34
+ const checkpointDir = join(
35
+ this.cwd,
36
+ ".orbit",
37
+ "checkpoints",
38
+ this.sessionId,
39
+ checkpoint.id
40
+ );
41
+ mkdirSync(checkpointDir, { recursive: true });
42
+ if (originalContent !== null) {
43
+ writeFileSync(
44
+ join(checkpointDir, "backup_content.txt"),
45
+ originalContent,
46
+ "utf8"
47
+ );
48
+ }
49
+ writeFileSync(
50
+ join(checkpointDir, "meta.json"),
51
+ JSON.stringify({
52
+ id: checkpoint.id,
53
+ timestamp: checkpoint.timestamp,
54
+ toolCallId,
55
+ filePath,
56
+ exists: originalContent !== null
57
+ }),
58
+ "utf8"
59
+ );
60
+ return checkpoint;
61
+ }
62
+ getCheckpoints() {
63
+ return this.checkpoints;
64
+ }
65
+ };
66
+
67
+ // src/RollbackManager.ts
68
+ import { existsSync as existsSync2, writeFileSync as writeFileSync2, unlinkSync } from "fs";
69
+ import { resolveSafePath as resolveSafePath2 } from "@wangjiehu/shared";
70
+ var RollbackManager = class {
71
+ constructor(cwd) {
72
+ this.cwd = cwd;
73
+ }
74
+ cwd;
75
+ rollback(checkpoint) {
76
+ const restored = [];
77
+ for (const backup of checkpoint.backups) {
78
+ const safePath = resolveSafePath2(this.cwd, backup.path);
79
+ if (backup.originalContent === null) {
80
+ if (existsSync2(safePath)) {
81
+ unlinkSync(safePath);
82
+ restored.push(backup.path);
83
+ }
84
+ } else {
85
+ writeFileSync2(safePath, backup.originalContent, "utf8");
86
+ restored.push(backup.path);
87
+ }
88
+ }
89
+ return {
90
+ success: true,
91
+ restored
92
+ };
93
+ }
94
+ };
95
+ export {
96
+ CheckpointManager,
97
+ RollbackManager
98
+ };
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@wangjiehu/sandbox",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "dependencies": {
8
+ "@wangjiehu/config": "0.1.0",
9
+ "@wangjiehu/shared": "0.1.0",
10
+ "@wangjiehu/tools": "0.1.0"
11
+ },
12
+ "scripts": {
13
+ "build": "tsup src/index.ts --format esm --dts"
14
+ }
15
+ }
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { existsSync, writeFileSync, readFileSync, rmSync, mkdirSync } from "fs";
3
+ import { join } from "path";
4
+ import { tmpdir } from "os";
5
+ import { CheckpointManager } from "./CheckpointManager.js";
6
+ import { RollbackManager } from "./RollbackManager.js";
7
+
8
+ describe("Sandbox Checkpoints and Rollbacks", () => {
9
+ let tempDir: string;
10
+
11
+ beforeEach(() => {
12
+ tempDir = join(tmpdir(), `orbit-sandbox-test-${Date.now()}`);
13
+ mkdirSync(tempDir, { recursive: true });
14
+ });
15
+
16
+ afterEach(() => {
17
+ rmSync(tempDir, { recursive: true, force: true });
18
+ });
19
+
20
+ it("should capture JIT snapshot before edit and rollback successfully", async () => {
21
+ const filePath = "test.txt";
22
+ const absPath = join(tempDir, filePath);
23
+
24
+ writeFileSync(absPath, "initial-content", "utf8");
25
+
26
+ const cpManager = new CheckpointManager(tempDir, "session-123");
27
+ const rbManager = new RollbackManager(tempDir);
28
+
29
+ const checkpoint = await cpManager.captureBeforeState("call-1", filePath);
30
+
31
+ writeFileSync(absPath, "modified-content", "utf8");
32
+ expect(readFileSync(absPath, "utf8")).toBe("modified-content");
33
+
34
+ rbManager.rollback(checkpoint);
35
+ expect(readFileSync(absPath, "utf8")).toBe("initial-content");
36
+ });
37
+
38
+ it("should delete newly created files on rollback", async () => {
39
+ const filePath = "new-file.txt";
40
+ const absPath = join(tempDir, filePath);
41
+
42
+ const cpManager = new CheckpointManager(tempDir, "session-123");
43
+ const rbManager = new RollbackManager(tempDir);
44
+
45
+ const checkpoint = await cpManager.captureBeforeState("call-1", filePath);
46
+
47
+ writeFileSync(absPath, "brand-new-file", "utf8");
48
+ expect(existsSync(absPath)).toBe(true);
49
+
50
+ rbManager.rollback(checkpoint);
51
+ expect(existsSync(absPath)).toBe(false);
52
+ });
53
+ });
@@ -0,0 +1,77 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { generateId, resolveSafePath } from "@wangjiehu/shared";
4
+ import { FileBackup, Checkpoint } from "./types.js";
5
+
6
+ export class CheckpointManager {
7
+ private checkpoints: Checkpoint[] = [];
8
+
9
+ constructor(
10
+ private cwd: string,
11
+ private sessionId: string,
12
+ ) {}
13
+
14
+ public async captureBeforeState(
15
+ toolCallId: string,
16
+ filePath: string,
17
+ ): Promise<Checkpoint> {
18
+ let originalContent: string | null = null;
19
+ try {
20
+ const safePath = resolveSafePath(this.cwd, filePath);
21
+ if (existsSync(safePath)) {
22
+ originalContent = readFileSync(safePath, "utf8");
23
+ }
24
+ } catch (e) {
25
+ // File does not exist yet or read failed
26
+ }
27
+
28
+ const backup: FileBackup = {
29
+ path: filePath,
30
+ originalContent,
31
+ };
32
+
33
+ const checkpoint: Checkpoint = {
34
+ id: generateId("cp"),
35
+ sessionId: this.sessionId,
36
+ timestamp: new Date().toISOString(),
37
+ toolCallId,
38
+ backups: [backup],
39
+ };
40
+
41
+ this.checkpoints.push(checkpoint);
42
+
43
+ const checkpointDir = join(
44
+ this.cwd,
45
+ ".orbit",
46
+ "checkpoints",
47
+ this.sessionId,
48
+ checkpoint.id,
49
+ );
50
+ mkdirSync(checkpointDir, { recursive: true });
51
+
52
+ if (originalContent !== null) {
53
+ writeFileSync(
54
+ join(checkpointDir, "backup_content.txt"),
55
+ originalContent,
56
+ "utf8",
57
+ );
58
+ }
59
+ writeFileSync(
60
+ join(checkpointDir, "meta.json"),
61
+ JSON.stringify({
62
+ id: checkpoint.id,
63
+ timestamp: checkpoint.timestamp,
64
+ toolCallId,
65
+ filePath,
66
+ exists: originalContent !== null,
67
+ }),
68
+ "utf8",
69
+ );
70
+
71
+ return checkpoint;
72
+ }
73
+
74
+ public getCheckpoints(): Checkpoint[] {
75
+ return this.checkpoints;
76
+ }
77
+ }
@@ -0,0 +1,36 @@
1
+ import { existsSync, writeFileSync, unlinkSync } from "fs";
2
+ import { resolveSafePath } from "@wangjiehu/shared";
3
+ import { Checkpoint } from "./types.js";
4
+
5
+ export class RollbackManager {
6
+ constructor(private cwd: string) {}
7
+
8
+ public rollback(checkpoint: Checkpoint): {
9
+ success: boolean;
10
+ error?: string;
11
+ restored: string[];
12
+ } {
13
+ const restored: string[] = [];
14
+
15
+ for (const backup of checkpoint.backups) {
16
+ const safePath = resolveSafePath(this.cwd, backup.path);
17
+
18
+ if (backup.originalContent === null) {
19
+ // File did not exist before the tool execution, so delete it on rollback
20
+ if (existsSync(safePath)) {
21
+ unlinkSync(safePath);
22
+ restored.push(backup.path);
23
+ }
24
+ } else {
25
+ // Restore previous content
26
+ writeFileSync(safePath, backup.originalContent, "utf8");
27
+ restored.push(backup.path);
28
+ }
29
+ }
30
+
31
+ return {
32
+ success: true,
33
+ restored,
34
+ };
35
+ }
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export * from "./CheckpointManager.js";
3
+ export * from "./RollbackManager.js";
package/src/types.ts ADDED
@@ -0,0 +1,13 @@
1
+ export interface FileBackup {
2
+ path: string;
3
+ originalContent: string | null; // null if the file did not exist before
4
+ originalHash?: string;
5
+ }
6
+
7
+ export interface Checkpoint {
8
+ id: string;
9
+ sessionId: string;
10
+ timestamp: string;
11
+ toolCallId: string;
12
+ backups: FileBackup[];
13
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }