@neta-art/cohub-cli 7.0.0 → 7.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.
- package/README.md +17 -9
- package/dist/commands/apps.js +2 -2
- package/dist/commands/desktop.d.ts +9 -0
- package/dist/commands/desktop.js +13 -5
- package/dist/commands/run.d.ts +11 -0
- package/dist/commands/run.js +4 -4
- package/dist/commands/runtime.js +269 -58
- package/dist/commands/sandboxd-binary.d.ts +2 -0
- package/dist/commands/sandboxd-binary.js +37 -16
- package/dist/index.js +4 -2
- package/dist/runtime/archive-store.d.ts +2 -0
- package/dist/runtime/archive-store.js +31 -25
- package/dist/runtime/connection.d.ts +3 -0
- package/dist/runtime/connection.js +251 -42
- package/dist/runtime/diagnostics.d.ts +104 -0
- package/dist/runtime/diagnostics.js +382 -0
- package/dist/runtime/harness.d.ts +3 -2
- package/dist/runtime/harness.js +39 -8
- package/dist/runtime/json-rpc.d.ts +2 -0
- package/dist/runtime/json-rpc.js +12 -1
- package/dist/runtime/native-archive.js +3 -3
- package/dist/runtime/process-group.js +1 -1
- package/dist/runtime/projection-store.d.ts +34 -0
- package/dist/runtime/projection-store.js +103 -0
- package/dist/runtime/session-store.d.ts +28 -5
- package/dist/runtime/session-store.js +272 -67
- package/dist/runtime/space-binding.d.ts +45 -0
- package/dist/runtime/space-binding.js +305 -0
- package/dist/runtime/turn-projection.d.ts +43 -0
- package/dist/runtime/turn-projection.js +127 -0
- package/dist/space.d.ts +11 -3
- package/dist/space.js +18 -6
- package/package.json +3 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { createReadStream, createWriteStream } from "node:fs";
|
|
4
|
-
import { chmod, copyFile, mkdir, mkdtemp, rename, rm, stat } from "node:fs/promises";
|
|
4
|
+
import { chmod, copyFile, mkdir, mkdtemp, rename, rm, stat, lstat } from "node:fs/promises";
|
|
5
5
|
import { homedir, tmpdir } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { pipeline } from "node:stream/promises";
|
|
@@ -40,9 +40,13 @@ const cacheDir = (version) => join(homedir(), ".cache", "cohub", "sandboxd", ver
|
|
|
40
40
|
const cachedBinaryPath = (version) => join(cacheDir(version), BINARY_NAME);
|
|
41
41
|
const archiveName = (version, target) => `${BINARY_NAME}_${version}_${target.goos}_${target.goarch}.tar.gz`;
|
|
42
42
|
const isExecutableFile = async (path) => {
|
|
43
|
-
const info = await
|
|
43
|
+
const info = await lstat(path).catch(() => null);
|
|
44
44
|
return Boolean(info?.isFile());
|
|
45
45
|
};
|
|
46
|
+
const isSafeArchiveFile = async (path) => {
|
|
47
|
+
const info = await lstat(path).catch(() => null);
|
|
48
|
+
return Boolean(info?.isFile() && info.nlink === 1);
|
|
49
|
+
};
|
|
46
50
|
const sha256File = async (path) => {
|
|
47
51
|
const hash = createHash("sha256");
|
|
48
52
|
await pipeline(createReadStream(path), hash);
|
|
@@ -87,11 +91,18 @@ const fetchText = (url, accept) => withTimeout(`Download of ${url}`, async (sign
|
|
|
87
91
|
throw new SandboxdDownloadError(`Download failed (${response.status}) for ${url}`);
|
|
88
92
|
return (await response.text()).trim();
|
|
89
93
|
});
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
94
|
+
// v1.82.4 is already public as a binary-only archive. The native watcher
|
|
95
|
+
// release adds the two notices; the old shape is accepted only for this pin.
|
|
96
|
+
const CURRENT_BINARY_ONLY_VERSION = "v1.82.4";
|
|
97
|
+
export function validSandboxdArchiveEntries(entries, version = SANDBOXD_VERSION) {
|
|
98
|
+
const binaryOnly = version === CURRENT_BINARY_ONLY_VERSION && entries.length === 1 && entries[0] === BINARY_NAME;
|
|
99
|
+
const expected = [BINARY_NAME, "LICENSE", "NOTICE"];
|
|
100
|
+
const withNotices = entries.length === expected.length && expected.every((entry) => entries.includes(entry));
|
|
101
|
+
return binaryOnly || withNotices;
|
|
102
|
+
}
|
|
103
|
+
// Reject unexpected paths before extracting the checksum-verified release.
|
|
104
|
+
const listTarGz = (archivePath, verbose = false) => new Promise((res, rej) => {
|
|
105
|
+
const child = spawn("tar", [verbose ? "-tvzf" : "-tzf", archivePath], { stdio: ["ignore", "pipe", "pipe"] });
|
|
95
106
|
let stdout = "";
|
|
96
107
|
let stderr = "";
|
|
97
108
|
child.stdout.on("data", (chunk) => {
|
|
@@ -105,7 +116,18 @@ const listTarGz = (archivePath) => new Promise((res, rej) => {
|
|
|
105
116
|
? res(stdout.split("\n").map((line) => line.trim()).filter(Boolean))
|
|
106
117
|
: rej(new SandboxdDownloadError(`tar listing failed: ${stderr.trim() || `exit ${code}`}`)));
|
|
107
118
|
});
|
|
108
|
-
|
|
119
|
+
export async function validateSandboxdArchive(archivePath, version = SANDBOXD_VERSION) {
|
|
120
|
+
const entries = await listTarGz(archivePath);
|
|
121
|
+
if (!validSandboxdArchiveEntries(entries, version)) {
|
|
122
|
+
throw new SandboxdDownloadError(`Unexpected sandbox archive contents: ${entries.join(", ") || "(empty)"}`);
|
|
123
|
+
}
|
|
124
|
+
const details = await listTarGz(archivePath, true);
|
|
125
|
+
if (details.length !== entries.length || details.some((line) => !line.startsWith("-") || /(?: link to | -> | == )/.test(line))) {
|
|
126
|
+
throw new SandboxdDownloadError("Sandbox archive must contain only regular files");
|
|
127
|
+
}
|
|
128
|
+
return entries;
|
|
129
|
+
}
|
|
130
|
+
// Extract the verified `.tar.gz` using the system tar (universally present on
|
|
109
131
|
// macOS and Linux), keeping the CLI free of native archive dependencies.
|
|
110
132
|
const extractTarGz = (archivePath, cwd) => new Promise((res, rej) => {
|
|
111
133
|
const child = spawn("tar", ["-xzf", archivePath, "-C", cwd], { stdio: ["ignore", "ignore", "pipe"] });
|
|
@@ -163,16 +185,15 @@ const downloadAndVerify = async (version, target) => {
|
|
|
163
185
|
if (actual !== expected) {
|
|
164
186
|
throw new SandboxdDownloadError(`Checksum mismatch for ${name} (expected ${expected}, got ${actual})`);
|
|
165
187
|
}
|
|
166
|
-
|
|
167
|
-
// guarding against path traversal / unexpected entries from a tampered CDN.
|
|
168
|
-
const entries = await listTarGz(archivePath);
|
|
169
|
-
if (entries.length !== 1 || entries[0] !== BINARY_NAME) {
|
|
170
|
-
throw new SandboxdDownloadError(`Unexpected archive contents for ${name}: ${entries.join(", ") || "(empty)"}`);
|
|
171
|
-
}
|
|
172
|
-
// Extract the single binary from the archive.
|
|
188
|
+
const entries = await validateSandboxdArchive(archivePath, version);
|
|
173
189
|
await extractTarGz(archivePath, tempDir);
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
if (!(await isSafeArchiveFile(join(tempDir, entry)))) {
|
|
192
|
+
throw new SandboxdDownloadError(`Unsafe sandbox archive entry: ${entry}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
174
195
|
const extractedBinary = join(tempDir, BINARY_NAME);
|
|
175
|
-
if (!(await
|
|
196
|
+
if (!(await isSafeArchiveFile(extractedBinary))) {
|
|
176
197
|
throw new SandboxdDownloadError(`Archive ${name} did not contain ${BINARY_NAME}`);
|
|
177
198
|
}
|
|
178
199
|
// Atomically move into the version cache (fall back to copy across devices).
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ program
|
|
|
37
37
|
.summary("Work with Cohub from your terminal")
|
|
38
38
|
.description("Send prompts, manage Space files, and publish public output.")
|
|
39
39
|
.version(VERSION, "-v, --version", "Show version")
|
|
40
|
-
.option("-s, --space <id>", "Target Space ID
|
|
40
|
+
.option("-s, --space <id>", "Target Space ID")
|
|
41
41
|
.option("--json", "Print machine-readable JSON when supported")
|
|
42
42
|
.helpOption("-h, --help", "Show help")
|
|
43
43
|
.addHelpText("after", `
|
|
@@ -54,6 +54,7 @@ Common commands:
|
|
|
54
54
|
cohub completion "Summarize AGENTS.md" --system-prompt AGENTS.md --stream
|
|
55
55
|
cohub run -- git status
|
|
56
56
|
cohub runtime up ./my-project
|
|
57
|
+
cohub runtime logs --follow
|
|
57
58
|
cohub search "release notes"
|
|
58
59
|
cohub -s <space-id> boards inspect <board-id>
|
|
59
60
|
cohub -s <space-id> spaces turns ls --author others
|
|
@@ -68,13 +69,14 @@ Common commands:
|
|
|
68
69
|
cohub generate "A calm lake at sunrise" --model <model> --output lake.png
|
|
69
70
|
|
|
70
71
|
Target space:
|
|
71
|
-
-s <space-id>, then COHUB_SPACE_ID, then
|
|
72
|
+
-s <space-id>, then COHUB_SPACE_ID, then the current directory Runtime binding, then Home
|
|
72
73
|
|
|
73
74
|
Environment:
|
|
74
75
|
COHUB_SPACE_ID Target Space ID when -s is omitted
|
|
75
76
|
COHUB_EXECUTION_TOKEN Use this token instead of the stored Logto session
|
|
76
77
|
ENV=dev Use the development Cohub environment
|
|
77
78
|
HTTPS_PROXY Honored for API and uploads (also HTTP_PROXY, NO_PROXY)
|
|
79
|
+
Runtime logs ~/.local/state/cohub/runtime/<space-id>/diagnostics
|
|
78
80
|
`);
|
|
79
81
|
registerAuth(program);
|
|
80
82
|
registerBoards(program);
|
|
@@ -23,7 +23,9 @@ export declare class RuntimeArchiveStore {
|
|
|
23
23
|
private readonly transport?;
|
|
24
24
|
private flushing;
|
|
25
25
|
private readonly capturing;
|
|
26
|
+
private errorReporter;
|
|
26
27
|
constructor(root: string, transport?: ArchiveTransport | undefined);
|
|
28
|
+
setErrorReporter(reporter: ((error: unknown, index?: HarnessArchiveIndex) => void) | null): void;
|
|
27
29
|
pendingCount(): Promise<number>;
|
|
28
30
|
failedCaptureCount(): Promise<number>;
|
|
29
31
|
hasCapture(turnId: string): Promise<boolean>;
|
|
@@ -44,10 +44,14 @@ export class RuntimeArchiveStore {
|
|
|
44
44
|
transport;
|
|
45
45
|
flushing = null;
|
|
46
46
|
capturing = new Map();
|
|
47
|
+
errorReporter = null;
|
|
47
48
|
constructor(root, transport) {
|
|
48
49
|
this.root = root;
|
|
49
50
|
this.transport = transport;
|
|
50
51
|
}
|
|
52
|
+
setErrorReporter(reporter) {
|
|
53
|
+
this.errorReporter = reporter;
|
|
54
|
+
}
|
|
51
55
|
async pendingCount() {
|
|
52
56
|
const pending = new Set();
|
|
53
57
|
for (const directory of ["pending", "captures"]) {
|
|
@@ -110,7 +114,7 @@ export class RuntimeArchiveStore {
|
|
|
110
114
|
const saved = await this.readIndex(this.version(turnId));
|
|
111
115
|
if (saved) {
|
|
112
116
|
if (saved.sessionId !== state.sessionId || saved.harness !== state.harness)
|
|
113
|
-
throw new Error("Archive identity mismatch
|
|
117
|
+
throw new Error("Archive identity mismatch");
|
|
114
118
|
const committed = await stat(join(this.root, "ready", `${turnId}.json`)).catch((error) => { if (missing(error))
|
|
115
119
|
return null; throw error; });
|
|
116
120
|
if (!committed)
|
|
@@ -125,7 +129,7 @@ export class RuntimeArchiveStore {
|
|
|
125
129
|
try {
|
|
126
130
|
const before = await file.stat();
|
|
127
131
|
if (!before.isFile() || !before.size)
|
|
128
|
-
throw new Error("Native archive is empty
|
|
132
|
+
throw new Error("Native archive is empty");
|
|
129
133
|
const buffer = Buffer.alloc(RUNTIME_ARCHIVE_SEGMENT_BYTES);
|
|
130
134
|
let offset = 0;
|
|
131
135
|
let digest = createHash("sha256");
|
|
@@ -135,7 +139,7 @@ export class RuntimeArchiveStore {
|
|
|
135
139
|
while (offset < previous.sizeBytes) {
|
|
136
140
|
const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, previous.sizeBytes - offset), offset);
|
|
137
141
|
if (!bytesRead)
|
|
138
|
-
throw new Error("Native file changed during capture
|
|
142
|
+
throw new Error("Native file changed during capture");
|
|
139
143
|
digest.update(buffer.subarray(0, bytesRead));
|
|
140
144
|
offset += bytesRead;
|
|
141
145
|
}
|
|
@@ -151,7 +155,7 @@ export class RuntimeArchiveStore {
|
|
|
151
155
|
while (offset < before.size) {
|
|
152
156
|
const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, before.size - offset), offset);
|
|
153
157
|
if (!bytesRead)
|
|
154
|
-
throw new Error("Native file changed during capture
|
|
158
|
+
throw new Error("Native file changed during capture");
|
|
155
159
|
const bytes = buffer.subarray(0, bytesRead);
|
|
156
160
|
digest.update(bytes);
|
|
157
161
|
const segment = { offset, sizeBytes: bytesRead, sha256: hash(bytes), md5: hash(bytes, "md5") };
|
|
@@ -161,7 +165,7 @@ export class RuntimeArchiveStore {
|
|
|
161
165
|
}
|
|
162
166
|
const after = await stat(state.path);
|
|
163
167
|
if (before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs)
|
|
164
|
-
throw new Error("Native file changed during capture
|
|
168
|
+
throw new Error("Native file changed during capture");
|
|
165
169
|
if (process.platform !== "win32") {
|
|
166
170
|
const directory = await open(join(this.root, "objects"), "r");
|
|
167
171
|
try {
|
|
@@ -219,7 +223,7 @@ export class RuntimeArchiveStore {
|
|
|
219
223
|
queue.push(index);
|
|
220
224
|
}
|
|
221
225
|
if (pending.size && !queue.length)
|
|
222
|
-
throw new Error("Cyclic archive outbox
|
|
226
|
+
throw new Error("Cyclic archive outbox");
|
|
223
227
|
for (let cursor = 0; cursor < queue.length; cursor++) {
|
|
224
228
|
signal.throwIfAborted();
|
|
225
229
|
const index = queue[cursor];
|
|
@@ -228,19 +232,19 @@ export class RuntimeArchiveStore {
|
|
|
228
232
|
try {
|
|
229
233
|
const { uploads } = await transport.prepareRuntimeArchive(index, { signal: AbortSignal.any([signal, AbortSignal.timeout(30_000)]) });
|
|
230
234
|
if (uploads.length > index.segments.length)
|
|
231
|
-
throw new Error("Upload plan mismatch
|
|
235
|
+
throw new Error("Upload plan mismatch");
|
|
232
236
|
const expected = new Set(index.segments.map((segment) => JSON.stringify(segment)));
|
|
233
237
|
for (const { segment, uploadUrl, headers } of uploads) {
|
|
234
238
|
if (!expected.has(JSON.stringify(segment)))
|
|
235
|
-
throw new Error("Upload segment mismatch
|
|
239
|
+
throw new Error("Upload segment mismatch");
|
|
236
240
|
signal.throwIfAborted();
|
|
237
241
|
const bytes = await readFile(this.blob(segment.sha256));
|
|
238
242
|
if (bytes.length !== segment.sizeBytes || hash(bytes) !== segment.sha256)
|
|
239
|
-
throw new Error("Local archive segment is corrupt
|
|
243
|
+
throw new Error("Local archive segment is corrupt");
|
|
240
244
|
const response = await (transport.fetchObject ?? fetch)(uploadUrl, { method: "PUT", headers, body: bytes, redirect: "error", signal: AbortSignal.any([signal, AbortSignal.timeout(60_000)]) });
|
|
241
245
|
// An immutable segment may already exist after a lost acknowledgement. Commit verifies it.
|
|
242
246
|
if (!response.ok && ![409, 412].includes(response.status))
|
|
243
|
-
throw new Error(`Archive upload failed
|
|
247
|
+
throw new Error(`Archive upload failed: ${response.status}`);
|
|
244
248
|
}
|
|
245
249
|
await transport.commitRuntimeArchive(index, { signal: AbortSignal.any([signal, AbortSignal.timeout(30_000)]) });
|
|
246
250
|
await atomicRuntimeJson(join(this.root, "ready", `${index.turnId}.json`), { turnId: index.turnId, sha256: index.sha256 });
|
|
@@ -248,14 +252,16 @@ export class RuntimeArchiveStore {
|
|
|
248
252
|
queue.push(...children.get(index.turnId) ?? []);
|
|
249
253
|
}
|
|
250
254
|
catch (error) {
|
|
251
|
-
if (!signal.aborted)
|
|
252
|
-
|
|
255
|
+
if (!signal.aborted) {
|
|
256
|
+
this.errorReporter?.(error, index);
|
|
257
|
+
console.error("Archive pending; native segments retained:", error);
|
|
258
|
+
}
|
|
253
259
|
}
|
|
254
260
|
}
|
|
255
261
|
}
|
|
256
262
|
async restore(reference, target, signal) {
|
|
257
263
|
if (!this.transport)
|
|
258
|
-
throw new Error("Archive transport unavailable
|
|
264
|
+
throw new Error("Archive transport unavailable");
|
|
259
265
|
const timeout = (ms) => signal ? AbortSignal.any([signal, AbortSignal.timeout(ms)]) : AbortSignal.timeout(ms);
|
|
260
266
|
const pages = [];
|
|
261
267
|
const visited = new Set();
|
|
@@ -263,18 +269,18 @@ export class RuntimeArchiveStore {
|
|
|
263
269
|
while (turnId) {
|
|
264
270
|
signal?.throwIfAborted();
|
|
265
271
|
if (visited.has(turnId))
|
|
266
|
-
throw new Error("Cyclic archive
|
|
272
|
+
throw new Error("Cyclic archive");
|
|
267
273
|
visited.add(turnId);
|
|
268
274
|
const page = await this.transport.getRuntimeArchive(reference.sessionId, turnId, { signal: timeout(30_000) });
|
|
269
275
|
const index = harnessArchiveIndexSchema.parse(page.index);
|
|
270
276
|
if (index.turnId !== turnId || index.sessionId !== reference.sessionId || index.harness !== reference.harness)
|
|
271
|
-
throw new Error("Archive identity mismatch
|
|
277
|
+
throw new Error("Archive identity mismatch");
|
|
272
278
|
pages.push({ ...page, index });
|
|
273
279
|
turnId = index.parentTurnId;
|
|
274
280
|
}
|
|
275
281
|
const head = pages[0]?.index;
|
|
276
282
|
if (!head)
|
|
277
|
-
throw new Error("Archive missing
|
|
283
|
+
throw new Error("Archive missing");
|
|
278
284
|
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
|
|
279
285
|
const temporary = `${target}.${randomUUID()}.restoring`;
|
|
280
286
|
const file = await open(temporary, "wx", 0o600);
|
|
@@ -284,53 +290,53 @@ export class RuntimeArchiveStore {
|
|
|
284
290
|
for (const page of pages.reverse()) {
|
|
285
291
|
validateArchiveBoundary(page.index, parent);
|
|
286
292
|
if (page.segments.length !== page.index.segments.length)
|
|
287
|
-
throw new Error("Missing archive segments
|
|
293
|
+
throw new Error("Missing archive segments");
|
|
288
294
|
for (const [ordinal, expected] of page.index.segments.entries()) {
|
|
289
295
|
signal?.throwIfAborted();
|
|
290
296
|
const cached = await readFile(this.blob(expected.sha256)).catch((error) => { if (missing(error))
|
|
291
297
|
return null; throw error; });
|
|
292
298
|
if (cached) {
|
|
293
299
|
if (cached.length !== expected.sizeBytes || hash(cached) !== expected.sha256)
|
|
294
|
-
throw new Error("Cached archive segment is corrupt
|
|
300
|
+
throw new Error("Cached archive segment is corrupt");
|
|
295
301
|
digest.update(cached);
|
|
296
302
|
await file.writeFile(cached);
|
|
297
303
|
continue;
|
|
298
304
|
}
|
|
299
305
|
let link = page.segments[ordinal];
|
|
300
306
|
if (!link || JSON.stringify(link.segment) !== JSON.stringify(expected))
|
|
301
|
-
throw new Error("Archive segment identity mismatch
|
|
307
|
+
throw new Error("Archive segment identity mismatch");
|
|
302
308
|
let response = await (this.transport.fetchObject ?? fetch)(link.downloadUrl, { redirect: "error", signal: timeout(60_000) });
|
|
303
309
|
if ([401, 403].includes(response.status)) {
|
|
304
310
|
const refreshed = await this.transport.getRuntimeArchive(reference.sessionId, page.index.turnId, { signal: timeout(30_000) });
|
|
305
311
|
link = refreshed.segments[ordinal];
|
|
306
312
|
if (!link || JSON.stringify(link.segment) !== JSON.stringify(expected))
|
|
307
|
-
throw new Error("Archive segment missing
|
|
313
|
+
throw new Error("Archive segment missing");
|
|
308
314
|
response = await (this.transport.fetchObject ?? fetch)(link.downloadUrl, { redirect: "error", signal: timeout(60_000) });
|
|
309
315
|
}
|
|
310
316
|
if (!response.ok || !response.body)
|
|
311
|
-
throw new Error(`Archive download failed
|
|
317
|
+
throw new Error(`Archive download failed: ${response.status}`);
|
|
312
318
|
const segmentHash = createHash("sha256");
|
|
313
319
|
let size = 0;
|
|
314
320
|
const chunks = [];
|
|
315
321
|
for await (const chunk of response.body) {
|
|
316
322
|
size += chunk.length;
|
|
317
323
|
if (size > expected.sizeBytes)
|
|
318
|
-
throw new Error("Archive size mismatch
|
|
324
|
+
throw new Error("Archive size mismatch");
|
|
319
325
|
chunks.push(chunk);
|
|
320
326
|
segmentHash.update(chunk);
|
|
321
327
|
digest.update(chunk);
|
|
322
328
|
await file.writeFile(chunk);
|
|
323
329
|
}
|
|
324
330
|
if (size !== expected.sizeBytes || segmentHash.digest("hex") !== expected.sha256)
|
|
325
|
-
throw new Error("Archive checksum mismatch
|
|
331
|
+
throw new Error("Archive checksum mismatch");
|
|
326
332
|
await this.saveBlob(expected.sha256, Buffer.concat(chunks));
|
|
327
333
|
}
|
|
328
334
|
if (digest.copy().digest("hex") !== page.index.sha256)
|
|
329
|
-
throw new Error("Archive version checksum mismatch
|
|
335
|
+
throw new Error("Archive version checksum mismatch");
|
|
330
336
|
parent = page.index;
|
|
331
337
|
}
|
|
332
338
|
if ((await file.stat()).size !== head.sizeBytes)
|
|
333
|
-
throw new Error("Archive length mismatch
|
|
339
|
+
throw new Error("Archive length mismatch");
|
|
334
340
|
await file.sync();
|
|
335
341
|
await file.close();
|
|
336
342
|
await link(temporary, target);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type RuntimeCapabilities } from "@neta-art/cohub";
|
|
2
2
|
import { type HarnessOptions } from "./harness.js";
|
|
3
3
|
import { type RuntimeSessionStore } from "./session-store.js";
|
|
4
|
+
import { type RuntimeDiagnostics } from "./diagnostics.js";
|
|
4
5
|
export type RuntimeConnectionOptions = {
|
|
5
6
|
spaceId: string;
|
|
6
7
|
cwd: string;
|
|
@@ -11,6 +12,8 @@ export type RuntimeConnectionOptions = {
|
|
|
11
12
|
signal: AbortSignal;
|
|
12
13
|
store: RuntimeSessionStore;
|
|
13
14
|
onReady: () => void;
|
|
15
|
+
runtimeId?: string;
|
|
16
|
+
diagnostics?: RuntimeDiagnostics;
|
|
14
17
|
leaseConflictTimeoutMs?: number;
|
|
15
18
|
};
|
|
16
19
|
export declare function serveRuntime(options: RuntimeConnectionOptions): Promise<void>;
|