@botlearn-course/daemon 0.0.20-beta.1 → 0.0.20-beta.2
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/dist/agent-service-sandbox.d.ts +7 -1
- package/dist/agent-service-sandbox.js +384 -16
- package/dist/agent-service-ws-protocol.d.ts +3 -3
- package/dist/agent-service-ws-protocol.js +4 -2
- package/dist/cli.js +19 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/run-dispatcher.d.ts +1 -1
- package/dist/run-dispatcher.js +1 -1
- package/dist/runtime-env.js +4 -4
- package/dist/runtime-quiescence.d.ts +16 -0
- package/dist/runtime-quiescence.js +42 -0
- package/dist/workspace-quota.d.ts +4 -0
- package/dist/workspace-quota.js +42 -0
- package/dist/workspace-restore.d.ts +9 -1
- package/dist/workspace-restore.js +30 -16
- 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 +1 -8
- package/dist/workspace-snapshot-staging.js +53 -19
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { chmodSync, closeSync, constants, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readdirSync, readSync, realpathSync, rmSync, writeSync, } from "node:fs";
|
|
2
|
+
import { chmodSync, closeSync, constants, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readdirSync, readSync, realpathSync, rmSync, writeSync, } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { isWorkspaceQuiescenceProof, } from "./runtime-quiescence.js";
|
|
4
5
|
import { defaultCaseFold, validateWorkspaceEntrySet, WORKSPACE_ENTRY_SET_SCHEMA, } from "./workspace-entry-set.js";
|
|
5
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;
|
|
6
7
|
const EXCLUDED_NAMES = new Set(["node_modules"]);
|
|
@@ -107,30 +108,52 @@ function fsyncDirectory(directory) {
|
|
|
107
108
|
closeSync(handle);
|
|
108
109
|
}
|
|
109
110
|
}
|
|
111
|
+
function descriptorPath(handle, fallback) {
|
|
112
|
+
if (existsSync("/proc/self/fd")) {
|
|
113
|
+
const value = path.join("/proc/self/fd", String(handle));
|
|
114
|
+
if (!existsSync(value))
|
|
115
|
+
fail("workspace_snapshot_fd_traversal_unavailable");
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
// Darwin has no procfs open-file path that supports relative child lookup. It is used
|
|
119
|
+
// only by local tests/BYOA; managed durable sandboxes are Linux and fail closed above.
|
|
120
|
+
return fallback;
|
|
121
|
+
}
|
|
110
122
|
function stageOnce(options, paths) {
|
|
111
123
|
const stagingDirectory = path.join(paths.checkpointRoot, randomUUID());
|
|
112
124
|
mkdirSync(stagingDirectory, { mode: 0o700 });
|
|
113
125
|
chmodSync(stagingDirectory, 0o700);
|
|
114
126
|
try {
|
|
115
|
-
const
|
|
116
|
-
|
|
127
|
+
const rootHandle = openSync(paths.workspace, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
128
|
+
const rootStats = fstatSync(rootHandle, { bigint: true });
|
|
129
|
+
if (!rootStats.isDirectory()) {
|
|
130
|
+
closeSync(rootHandle);
|
|
117
131
|
fail("workspace_snapshot_root_invalid");
|
|
132
|
+
}
|
|
118
133
|
const entries = [];
|
|
119
134
|
const files = [];
|
|
120
135
|
let fileCount = 0;
|
|
121
136
|
let totalBytes = 0;
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
let estimatedEntrySetBytes = Buffer.byteLength('{"schemaVersion":"agent-workspace-entry-set/1","entries":[]}', "utf8");
|
|
138
|
+
function appendEntry(entry) {
|
|
139
|
+
estimatedEntrySetBytes += Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
|
|
140
|
+
if (estimatedEntrySetBytes > options.limits.maxEntrySetBytes) {
|
|
141
|
+
fail("workspace_snapshot_limit_exceeded");
|
|
142
|
+
}
|
|
143
|
+
entries.push(entry);
|
|
144
|
+
}
|
|
145
|
+
function walk(sourceHandle, sourceFallback, destinationDirectory, segments) {
|
|
146
|
+
const directoryBefore = fstatSync(sourceHandle, { bigint: true });
|
|
124
147
|
assertSourceNode(directoryBefore, {
|
|
125
148
|
rootDevice: rootStats.dev,
|
|
126
149
|
expected: "directory",
|
|
127
150
|
});
|
|
151
|
+
const sourceDirectory = descriptorPath(sourceHandle, sourceFallback);
|
|
128
152
|
const children = readdirSync(sourceDirectory, {
|
|
129
|
-
withFileTypes: true,
|
|
130
153
|
encoding: "buffer",
|
|
131
|
-
}).sort((left, right) => Buffer.compare(left
|
|
154
|
+
}).sort((left, right) => Buffer.compare(left, right));
|
|
132
155
|
for (const child of children) {
|
|
133
|
-
const name = decodeName(child
|
|
156
|
+
const name = decodeName(child);
|
|
134
157
|
if (excludedName(name))
|
|
135
158
|
continue;
|
|
136
159
|
const relativeSegments = [...segments, name];
|
|
@@ -145,13 +168,21 @@ function stageOnce(options, paths) {
|
|
|
145
168
|
fail("workspace_snapshot_unsupported_file_type");
|
|
146
169
|
if (stats.isDirectory()) {
|
|
147
170
|
assertSourceNode(stats, { rootDevice: rootStats.dev, expected: "directory" });
|
|
171
|
+
const childHandle = openSync(source, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
172
|
+
const openedDirectory = fstatSync(childHandle, { bigint: true });
|
|
173
|
+
if (!sameIdentity(stats, openedDirectory)) {
|
|
174
|
+
closeSync(childHandle);
|
|
175
|
+
fail("workspace_snapshot_source_changed", true);
|
|
176
|
+
}
|
|
148
177
|
mkdirSync(destination, { mode: 0o700 });
|
|
149
178
|
chmodSync(destination, 0o700);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
179
|
+
appendEntry({ type: "directory", path: relativePath, mode: "0700" });
|
|
180
|
+
try {
|
|
181
|
+
walk(childHandle, source, destination, relativeSegments);
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
closeSync(childHandle);
|
|
153
185
|
}
|
|
154
|
-
walk(source, destination, relativeSegments);
|
|
155
186
|
continue;
|
|
156
187
|
}
|
|
157
188
|
const copied = copyStableFile(source, destination, {
|
|
@@ -163,7 +194,7 @@ function stageOnce(options, paths) {
|
|
|
163
194
|
if (fileCount > options.limits.maxFileCount ||
|
|
164
195
|
totalBytes > options.limits.maxTotalBytes)
|
|
165
196
|
fail("workspace_snapshot_limit_exceeded");
|
|
166
|
-
|
|
197
|
+
appendEntry({
|
|
167
198
|
type: "file",
|
|
168
199
|
path: relativePath,
|
|
169
200
|
mode: copied.mode,
|
|
@@ -176,17 +207,19 @@ function stageOnce(options, paths) {
|
|
|
176
207
|
sizeBytes: copied.sizeBytes,
|
|
177
208
|
sha256: copied.sha256,
|
|
178
209
|
});
|
|
179
|
-
if (entries.length > options.limits.maxEntrySetBytes) {
|
|
180
|
-
fail("workspace_snapshot_limit_exceeded");
|
|
181
|
-
}
|
|
182
210
|
}
|
|
183
|
-
const directoryAfter =
|
|
211
|
+
const directoryAfter = fstatSync(sourceHandle, { bigint: true });
|
|
184
212
|
if (!sameIdentity(directoryBefore, directoryAfter)) {
|
|
185
213
|
fail("workspace_snapshot_source_changed", true);
|
|
186
214
|
}
|
|
187
215
|
fsyncDirectory(destinationDirectory);
|
|
188
216
|
}
|
|
189
|
-
|
|
217
|
+
try {
|
|
218
|
+
walk(rootHandle, paths.workspace, stagingDirectory, []);
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
closeSync(rootHandle);
|
|
222
|
+
}
|
|
190
223
|
entries.sort((left, right) => Buffer.compare(Buffer.from(left.path, "utf8"), Buffer.from(right.path, "utf8")));
|
|
191
224
|
files.sort((left, right) => Buffer.compare(Buffer.from(left.path, "utf8"), Buffer.from(right.path, "utf8")));
|
|
192
225
|
const entrySet = validateWorkspaceEntrySet({ schemaVersion: WORKSPACE_ENTRY_SET_SCHEMA, entries }, options.limits);
|
|
@@ -200,7 +233,8 @@ function stageOnce(options, paths) {
|
|
|
200
233
|
export function stageWorkspaceSnapshot(options) {
|
|
201
234
|
if (!UUID.test(options.checkpointId))
|
|
202
235
|
fail("workspace_snapshot_checkpoint_invalid");
|
|
203
|
-
if (options.quiescenceProof
|
|
236
|
+
if (!isWorkspaceQuiescenceProof(options.quiescenceProof) ||
|
|
237
|
+
options.quiescenceProof.proofVersion !== "workspace-quiescence-proof/1" ||
|
|
204
238
|
!UUID.test(options.quiescenceProof.sandboxId) ||
|
|
205
239
|
!UUID.test(options.quiescenceProof.runtimeSessionId) ||
|
|
206
240
|
options.quiescenceProof.checkpointId !== options.checkpointId ||
|
package/package.json
CHANGED