@botlearn-course/daemon 0.0.19 → 0.0.20-beta.10
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 +7 -0
- package/dist/agent-service-sandbox.d.ts +9 -1
- package/dist/agent-service-sandbox.js +523 -39
- package/dist/agent-service-ws-protocol.d.ts +3 -3
- package/dist/agent-service-ws-protocol.js +7 -2
- package/dist/cli.js +19 -1
- package/dist/file-candidates.d.ts +28 -1
- package/dist/file-candidates.js +102 -49
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/run-dispatcher.d.ts +3 -2
- package/dist/run-dispatcher.js +71 -11
- package/dist/runtime-env.js +4 -4
- package/dist/runtime-quiescence.d.ts +19 -0
- package/dist/runtime-quiescence.js +130 -0
- package/dist/runtimes/deepseek-tui.js +11 -0
- package/dist/runtimes/engine.js +1 -1
- package/dist/tool-observation.d.ts +9 -5
- package/dist/tool-observation.js +42 -19
- package/dist/trace-projection.d.ts +21 -0
- package/dist/trace-projection.js +56 -0
- package/dist/types.d.ts +3 -3
- package/dist/workspace-entry-set.d.ts +31 -0
- package/dist/workspace-entry-set.js +164 -0
- package/dist/workspace-filesystem.d.ts +20 -0
- package/dist/workspace-filesystem.js +120 -0
- package/dist/workspace-materialization.d.ts +16 -0
- package/dist/workspace-materialization.js +136 -0
- package/dist/workspace-quota.d.ts +4 -0
- package/dist/workspace-quota.js +42 -0
- package/dist/workspace-restore.d.ts +42 -0
- package/dist/workspace-restore.js +347 -0
- package/dist/workspace-snapshot-control.d.ts +29 -0
- package/dist/workspace-snapshot-control.js +169 -0
- package/dist/workspace-snapshot-policy.d.ts +24 -0
- package/dist/workspace-snapshot-policy.js +45 -0
- package/dist/workspace-snapshot-staging.d.ts +27 -0
- package/dist/workspace-snapshot-staging.js +275 -0
- package/dist/workspace.d.ts +60 -4
- package/dist/workspace.js +576 -9
- package/package.json +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFileSync, statfsSync, statSync } from "node:fs";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { assertRuntimeWorkspaceQuota } from "./workspace-quota.js";
|
|
5
|
+
export const WORKSPACE_IMAGE_FORMAT = "botlearn-workspace-image/1";
|
|
6
|
+
export const WORKSPACE_IMAGE_LOGICAL_BYTES = 3 * 1024 * 1024 * 1024;
|
|
7
|
+
const RUNTIME_LAUNCHER = "/opt/botlearn/bin/botlearn-runtime-launcher";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const defaultCapacityProbes = {
|
|
10
|
+
statfsBytes(workspaceRoot) {
|
|
11
|
+
const filesystem = statfsSync(workspaceRoot, { bigint: true });
|
|
12
|
+
return filesystem.blocks * filesystem.bsize;
|
|
13
|
+
},
|
|
14
|
+
backingFileBytes(imagePath) {
|
|
15
|
+
return BigInt(statSync(imagePath).size);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
function decodeMountInfoPath(value) {
|
|
19
|
+
return value.replace(/\\([0-7]{3})/gu, (_match, octal) => String.fromCharCode(Number.parseInt(octal, 8)));
|
|
20
|
+
}
|
|
21
|
+
export function parseWorkspaceMountInfo(text) {
|
|
22
|
+
return text.split("\n").filter(Boolean).map((line) => {
|
|
23
|
+
const [left, right] = line.split(" - ", 2);
|
|
24
|
+
const fields = left?.split(" ") ?? [];
|
|
25
|
+
const filesystem = right?.split(" ") ?? [];
|
|
26
|
+
if (fields.length < 6 || filesystem.length < 3) {
|
|
27
|
+
throw new Error("workspace_mount_proof_invalid");
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
target: decodeMountInfoPath(fields[4]),
|
|
31
|
+
filesystemType: filesystem[0],
|
|
32
|
+
source: decodeMountInfoPath(filesystem[1]),
|
|
33
|
+
mountOptions: new Set(fields[5].split(",")),
|
|
34
|
+
superOptions: new Set(filesystem[2].split(",")),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function nasWorkspaceEnabled(env = process.env) {
|
|
39
|
+
return env.BOTLEARN_WORKSPACE_IMAGE_FORMAT !== undefined;
|
|
40
|
+
}
|
|
41
|
+
export function workspaceFilesystemFlushCommand(env = process.env) {
|
|
42
|
+
if (!nasWorkspaceEnabled(env))
|
|
43
|
+
return null;
|
|
44
|
+
if (env.BOTLEARN_RUNTIME_LAUNCH_MODE === "direct-uid") {
|
|
45
|
+
return [RUNTIME_LAUNCHER, ["--workspace-filesystem-flush"]];
|
|
46
|
+
}
|
|
47
|
+
if (env.BOTLEARN_RUNTIME_LAUNCH_MODE === "sudo") {
|
|
48
|
+
return [
|
|
49
|
+
"/usr/bin/sudo",
|
|
50
|
+
["-n", "--", RUNTIME_LAUNCHER, "--workspace-filesystem-flush"],
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
if (env.BOTLEARN_DAEMON_ENABLE_FAKE_RUNTIME === "1" || env.NODE_ENV === "test") {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
throw new Error("workspace_nas_flush_failed");
|
|
57
|
+
}
|
|
58
|
+
export async function flushWorkspaceFilesystem(env = process.env) {
|
|
59
|
+
const command = workspaceFilesystemFlushCommand(env);
|
|
60
|
+
if (command === null)
|
|
61
|
+
return;
|
|
62
|
+
try {
|
|
63
|
+
await execFileAsync(command[0], command[1], { timeout: 30_000 });
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new Error("workspace_nas_flush_failed");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function isWorkspaceQuotaExceededError(error) {
|
|
70
|
+
if (!(error instanceof Error))
|
|
71
|
+
return false;
|
|
72
|
+
const candidate = error;
|
|
73
|
+
const code = String(candidate.code ?? candidate.failure?.error_code ?? "");
|
|
74
|
+
const detail = [
|
|
75
|
+
candidate.message,
|
|
76
|
+
candidate.failure?.error_message,
|
|
77
|
+
candidate.failure?.stderr_tail,
|
|
78
|
+
].map((value) => String(value ?? "")).join(" ");
|
|
79
|
+
return code === "ENOSPC"
|
|
80
|
+
|| code === "EDQUOT"
|
|
81
|
+
|| /\b(?:ENOSPC|EDQUOT|no space left on device|disk quota exceeded)\b/iu.test(detail)
|
|
82
|
+
|| (candidate.cause !== undefined && isWorkspaceQuotaExceededError(candidate.cause));
|
|
83
|
+
}
|
|
84
|
+
export function assertWorkspaceFilesystem(workspaceRoot, env = process.env, mountInfo, capacityProbes) {
|
|
85
|
+
if (!nasWorkspaceEnabled(env)) {
|
|
86
|
+
assertRuntimeWorkspaceQuota(workspaceRoot);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (env.BOTLEARN_WORKSPACE_IMAGE_FORMAT !== WORKSPACE_IMAGE_FORMAT
|
|
90
|
+
|| env.BOTLEARN_WORKSPACE_LOGICAL_BYTES !== String(WORKSPACE_IMAGE_LOGICAL_BYTES)
|
|
91
|
+
|| !/^[0-9a-f]{64}$/u.test(env.BOTLEARN_WORKSPACE_SCOPE_DIGEST ?? "")) {
|
|
92
|
+
throw new Error("workspace_quota_mismatch");
|
|
93
|
+
}
|
|
94
|
+
if (env.NODE_ENV === "test" && mountInfo === undefined)
|
|
95
|
+
return;
|
|
96
|
+
const mount = parseWorkspaceMountInfo(mountInfo ?? readFileSync("/proc/self/mountinfo", "utf8"))
|
|
97
|
+
.find((candidate) => candidate.target === workspaceRoot);
|
|
98
|
+
const validFilesystem = mount?.filesystemType === "ext4" || (mount?.filesystemType === "fuse.ext4"
|
|
99
|
+
&& mount.source === "/mnt/botlearn-workspace-store/workspace.ext4"
|
|
100
|
+
&& mount.superOptions.has("allow_other"));
|
|
101
|
+
if (!mount
|
|
102
|
+
|| !validFilesystem
|
|
103
|
+
|| !mount.mountOptions.has("rw")
|
|
104
|
+
|| !mount.mountOptions.has("nodev")
|
|
105
|
+
|| !mount.mountOptions.has("nosuid")) {
|
|
106
|
+
throw new Error("workspace_loop_mount_failed");
|
|
107
|
+
}
|
|
108
|
+
if (env.NODE_ENV === "test" && capacityProbes === undefined)
|
|
109
|
+
return;
|
|
110
|
+
const probes = capacityProbes ?? defaultCapacityProbes;
|
|
111
|
+
// fuse2fs reports a zero-sized statfs view in veFaaS even though the exact backing
|
|
112
|
+
// image is mounted and enforces the filesystem boundary. The mount proof above pins
|
|
113
|
+
// that image, so its validated file size is the authoritative capacity for FUSE.
|
|
114
|
+
const boundedBytes = mount.filesystemType === "fuse.ext4"
|
|
115
|
+
? probes.backingFileBytes(mount.source)
|
|
116
|
+
: probes.statfsBytes(workspaceRoot);
|
|
117
|
+
if (boundedBytes !== BigInt(WORKSPACE_IMAGE_LOGICAL_BYTES)) {
|
|
118
|
+
throw new Error("workspace_quota_mismatch");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const WORKSPACE_MATERIALIZATION_SCHEMA: "agent-workspace-materialization/1";
|
|
2
|
+
export interface WorkspaceMaterializationMarker {
|
|
3
|
+
schemaVersion: typeof WORKSPACE_MATERIALIZATION_SCHEMA;
|
|
4
|
+
sandboxId: string;
|
|
5
|
+
sandboxGeneration: number;
|
|
6
|
+
runtimeSessionId: string;
|
|
7
|
+
workspaceContinuityState: "healthy" | "degraded";
|
|
8
|
+
snapshotId: string | null;
|
|
9
|
+
revision: number;
|
|
10
|
+
contentSha256: string | null;
|
|
11
|
+
writtenAt: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function workspaceMaterializationMarkerPath(sandboxId: string, runtimeSessionId: string): string;
|
|
14
|
+
export declare function writeWorkspaceMaterializationMarker(marker: WorkspaceMaterializationMarker): void;
|
|
15
|
+
export declare function readWorkspaceMaterializationMarker(sandboxId: string, runtimeSessionId: string): WorkspaceMaterializationMarker | null;
|
|
16
|
+
export declare function removeWorkspaceMaterializationMarker(sandboxId: string, runtimeSessionId: string): boolean;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { ensureDaemonHome } from "./auth-store.js";
|
|
5
|
+
export const WORKSPACE_MATERIALIZATION_SCHEMA = "agent-workspace-materialization/1";
|
|
6
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
8
|
+
function assertUuid(value, label) {
|
|
9
|
+
if (!UUID.test(value))
|
|
10
|
+
throw new Error(label + " must be a UUID");
|
|
11
|
+
}
|
|
12
|
+
function markerDirectory(sandboxId) {
|
|
13
|
+
assertUuid(sandboxId, "sandboxId");
|
|
14
|
+
return path.join(ensureDaemonHome(), "agent-service-sandboxes", sandboxId, "workspace-materializations");
|
|
15
|
+
}
|
|
16
|
+
export function workspaceMaterializationMarkerPath(sandboxId, runtimeSessionId) {
|
|
17
|
+
assertUuid(runtimeSessionId, "runtimeSessionId");
|
|
18
|
+
return path.join(markerDirectory(sandboxId), runtimeSessionId + ".json");
|
|
19
|
+
}
|
|
20
|
+
function validateMarker(raw) {
|
|
21
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
22
|
+
throw new Error("workspace materialization marker must be an object");
|
|
23
|
+
}
|
|
24
|
+
const marker = raw;
|
|
25
|
+
const expectedKeys = [
|
|
26
|
+
"schemaVersion",
|
|
27
|
+
"sandboxId",
|
|
28
|
+
"sandboxGeneration",
|
|
29
|
+
"runtimeSessionId",
|
|
30
|
+
"workspaceContinuityState",
|
|
31
|
+
"snapshotId",
|
|
32
|
+
"revision",
|
|
33
|
+
"contentSha256",
|
|
34
|
+
"writtenAt",
|
|
35
|
+
].sort();
|
|
36
|
+
if (Object.keys(marker).sort().some((key, index) => key !== expectedKeys[index]) ||
|
|
37
|
+
Object.keys(marker).length !== expectedKeys.length)
|
|
38
|
+
throw new Error("workspace materialization marker fields are invalid");
|
|
39
|
+
if (marker.schemaVersion !== WORKSPACE_MATERIALIZATION_SCHEMA) {
|
|
40
|
+
throw new Error("workspace materialization marker schema is unsupported");
|
|
41
|
+
}
|
|
42
|
+
if (typeof marker.sandboxId !== "string")
|
|
43
|
+
throw new Error("sandboxId is required");
|
|
44
|
+
if (typeof marker.runtimeSessionId !== "string") {
|
|
45
|
+
throw new Error("runtimeSessionId is required");
|
|
46
|
+
}
|
|
47
|
+
assertUuid(marker.sandboxId, "sandboxId");
|
|
48
|
+
assertUuid(marker.runtimeSessionId, "runtimeSessionId");
|
|
49
|
+
if (!Number.isSafeInteger(marker.sandboxGeneration) ||
|
|
50
|
+
marker.sandboxGeneration < 1 ||
|
|
51
|
+
!Number.isSafeInteger(marker.revision) ||
|
|
52
|
+
marker.revision < 0 ||
|
|
53
|
+
typeof marker.writtenAt !== "string" ||
|
|
54
|
+
Number.isNaN(Date.parse(marker.writtenAt)))
|
|
55
|
+
throw new Error("workspace materialization marker values are invalid");
|
|
56
|
+
if (marker.workspaceContinuityState !== "healthy" &&
|
|
57
|
+
marker.workspaceContinuityState !== "degraded")
|
|
58
|
+
throw new Error("workspace materialization continuity is invalid");
|
|
59
|
+
if (marker.snapshotId !== null) {
|
|
60
|
+
if (typeof marker.snapshotId !== "string")
|
|
61
|
+
throw new Error("snapshotId is invalid");
|
|
62
|
+
assertUuid(marker.snapshotId, "snapshotId");
|
|
63
|
+
}
|
|
64
|
+
const revisionZero = marker.revision === 0;
|
|
65
|
+
if (revisionZero !== (marker.snapshotId === null) ||
|
|
66
|
+
revisionZero !== (marker.contentSha256 === null) ||
|
|
67
|
+
(!revisionZero &&
|
|
68
|
+
(typeof marker.contentSha256 !== "string" || !SHA256.test(marker.contentSha256)))) {
|
|
69
|
+
throw new Error("workspace materialization marker pointer is inconsistent");
|
|
70
|
+
}
|
|
71
|
+
return marker;
|
|
72
|
+
}
|
|
73
|
+
export function writeWorkspaceMaterializationMarker(marker) {
|
|
74
|
+
validateMarker(marker);
|
|
75
|
+
const directory = markerDirectory(marker.sandboxId);
|
|
76
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
77
|
+
try {
|
|
78
|
+
chmodSync(directory, 0o700);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Windows best effort.
|
|
82
|
+
}
|
|
83
|
+
const target = workspaceMaterializationMarkerPath(marker.sandboxId, marker.runtimeSessionId);
|
|
84
|
+
const staging = target + ".tmp-" + process.pid + "-" + randomBytes(4).toString("hex");
|
|
85
|
+
try {
|
|
86
|
+
writeFileSync(staging, JSON.stringify(marker), { encoding: "utf8", mode: 0o600 });
|
|
87
|
+
const file = openSync(staging, "r");
|
|
88
|
+
try {
|
|
89
|
+
fsyncSync(file);
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
closeSync(file);
|
|
93
|
+
}
|
|
94
|
+
renameSync(staging, target);
|
|
95
|
+
try {
|
|
96
|
+
chmodSync(target, 0o600);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Windows best effort.
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const parent = openSync(directory, "r");
|
|
103
|
+
try {
|
|
104
|
+
fsyncSync(parent);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
closeSync(parent);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Some platforms cannot fsync a directory.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
try {
|
|
116
|
+
unlinkSync(staging);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Staging may not have been created.
|
|
120
|
+
}
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function readWorkspaceMaterializationMarker(sandboxId, runtimeSessionId) {
|
|
125
|
+
const file = workspaceMaterializationMarkerPath(sandboxId, runtimeSessionId);
|
|
126
|
+
if (!existsSync(file))
|
|
127
|
+
return null;
|
|
128
|
+
return validateMarker(JSON.parse(readFileSync(file, "utf8")));
|
|
129
|
+
}
|
|
130
|
+
export function removeWorkspaceMaterializationMarker(sandboxId, runtimeSessionId) {
|
|
131
|
+
const file = workspaceMaterializationMarkerPath(sandboxId, runtimeSessionId);
|
|
132
|
+
if (!existsSync(file))
|
|
133
|
+
return false;
|
|
134
|
+
unlinkSync(file);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const RUNTIME_WORKSPACE_QUOTA_BYTES: number;
|
|
2
|
+
export declare const RUNTIME_WORKSPACE_INODE_QUOTA = 2000;
|
|
3
|
+
export declare function assertRuntimeWorkspaceQuota(workspaceRoot: string): void;
|
|
4
|
+
export declare function applyRuntimeWorkspaceQuota(runtimeSessionId: string, sandboxGeneration: number): Promise<void>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFileSync, statfsSync, statSync } from "node:fs";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
export const RUNTIME_WORKSPACE_QUOTA_BYTES = 512 * 1024 * 1024;
|
|
5
|
+
export const RUNTIME_WORKSPACE_INODE_QUOTA = 2_000;
|
|
6
|
+
const WORKSPACE_BACKING_BYTES = 3 * 1024 * 1024 * 1024;
|
|
7
|
+
const QUOTA_MARKER = "/var/lib/botlearn-runtime/workspace-quota.ready";
|
|
8
|
+
const RUNTIME_LAUNCHER = "/opt/botlearn/bin/botlearn-runtime-launcher";
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
export function assertRuntimeWorkspaceQuota(workspaceRoot) {
|
|
11
|
+
if (process.env.BOTLEARN_DAEMON_ENABLE_FAKE_RUNTIME === "1" || process.env.NODE_ENV === "test") {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (process.env.BOTLEARN_RUNTIME_WORKSPACE_QUOTA_BYTES !== String(RUNTIME_WORKSPACE_QUOTA_BYTES) ||
|
|
15
|
+
process.env.BOTLEARN_RUNTIME_WORKSPACE_INODE_QUOTA !== String(RUNTIME_WORKSPACE_INODE_QUOTA)) {
|
|
16
|
+
throw new Error("workspace_runtime_quota_unverified");
|
|
17
|
+
}
|
|
18
|
+
const filesystem = statfsSync(workspaceRoot, { bigint: true });
|
|
19
|
+
const marker = statSync(QUOTA_MARKER);
|
|
20
|
+
if (filesystem.blocks * filesystem.bsize < BigInt(WORKSPACE_BACKING_BYTES) ||
|
|
21
|
+
marker.uid !== 0 || (marker.mode & 0o777) !== 0o600 ||
|
|
22
|
+
readFileSync(QUOTA_MARKER, "utf8") !== "workspace-filesystem-quota/1\n") {
|
|
23
|
+
throw new Error("workspace_runtime_quota_probe_failed");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function applyRuntimeWorkspaceQuota(runtimeSessionId, sandboxGeneration) {
|
|
27
|
+
if (process.env.BOTLEARN_DAEMON_ENABLE_FAKE_RUNTIME === "1" || process.env.NODE_ENV === "test") {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const args = ["--workspace-quota-apply", runtimeSessionId, String(sandboxGeneration)];
|
|
31
|
+
if (process.env.BOTLEARN_RUNTIME_LAUNCH_MODE === "sudo") {
|
|
32
|
+
await execFileAsync("/usr/bin/sudo", ["-n", "--", RUNTIME_LAUNCHER, ...args], {
|
|
33
|
+
timeout: 10_000,
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (process.env.BOTLEARN_RUNTIME_LAUNCH_MODE === "direct-uid") {
|
|
38
|
+
await execFileAsync(RUNTIME_LAUNCHER, args, { timeout: 10_000 });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
throw new Error("workspace_runtime_quota_helper_unavailable");
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type WorkspaceEntry, type WorkspaceEntrySetLimits } from "./workspace-entry-set.js";
|
|
2
|
+
export declare class WorkspaceRestoreError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly retryable: boolean;
|
|
5
|
+
constructor(code: string, retryable?: boolean);
|
|
6
|
+
}
|
|
7
|
+
export interface WorkspaceRestoreDescriptor {
|
|
8
|
+
sandboxId: string;
|
|
9
|
+
sandboxGeneration: number;
|
|
10
|
+
runtimeSessionId: string;
|
|
11
|
+
continuityState: "healthy" | "degraded";
|
|
12
|
+
continuityErrorCode: string | null;
|
|
13
|
+
snapshotId: string | null;
|
|
14
|
+
revision: number;
|
|
15
|
+
contentSha256: string | null;
|
|
16
|
+
expectedFileCount: number;
|
|
17
|
+
expectedEntryCount: number;
|
|
18
|
+
expectedTotalBytes: number;
|
|
19
|
+
}
|
|
20
|
+
export interface WorkspaceRestoreOptions {
|
|
21
|
+
workspaceDirectory: string;
|
|
22
|
+
controlDirectory: string;
|
|
23
|
+
descriptor: WorkspaceRestoreDescriptor;
|
|
24
|
+
entrySet: unknown;
|
|
25
|
+
limits: WorkspaceEntrySetLimits;
|
|
26
|
+
requiredFreeBytes: number;
|
|
27
|
+
downloadFile(entry: WorkspaceEntry, partPath: string): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export interface WorkspaceRestoreCapacityOptions {
|
|
30
|
+
workspaceDirectory: string;
|
|
31
|
+
descriptor: WorkspaceRestoreDescriptor;
|
|
32
|
+
requiredFreeBytes: number;
|
|
33
|
+
entryCopies?: 1 | 2;
|
|
34
|
+
}
|
|
35
|
+
export declare function workspaceRestoreJournalPath(controlDirectory: string, runtimeSessionId: string): string;
|
|
36
|
+
export declare function recoverWorkspaceRestoreSwap(options: {
|
|
37
|
+
workspaceDirectory: string;
|
|
38
|
+
controlDirectory: string;
|
|
39
|
+
descriptor: WorkspaceRestoreDescriptor;
|
|
40
|
+
}): boolean;
|
|
41
|
+
export declare function restoreWorkspaceSnapshot(options: WorkspaceRestoreOptions): Promise<void>;
|
|
42
|
+
export declare function assertWorkspaceRestoreCapacity(options: WorkspaceRestoreCapacityOptions): void;
|