humanish 0.124.0 → 0.126.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/CHANGELOG.md +31 -0
- package/CONTEXT.md +1 -1
- package/README.md +2 -2
- package/dist/actors/computer-use/actor.d.ts +2 -0
- package/dist/actors/computer-use/actor.js +1 -0
- package/dist/actors/computer-use/loop/clock.d.ts +9 -8
- package/dist/actors/computer-use/loop/clock.js +27 -17
- package/dist/actors/computer-use/loop/debrief.d.ts +1 -1
- package/dist/actors/computer-use/loop/debrief.js +15 -5
- package/dist/actors/computer-use/loop/ending.d.ts +8 -5
- package/dist/actors/computer-use/loop/ending.js +8 -5
- package/dist/actors/computer-use/loop/observation.d.ts +6 -0
- package/dist/actors/computer-use/loop/observation.js +19 -1
- package/dist/actors/computer-use/loop/session.d.ts +4 -2
- package/dist/actors/computer-use/loop/session.js +4 -1
- package/dist/actors/computer-use/loop/trace.js +4 -2
- package/dist/actors/computer-use/loop/types.d.ts +22 -8
- package/dist/actors/computer-use/loop.js +5 -5
- package/dist/actors/contract.d.ts +31 -8
- package/dist/actors/contract.js +3 -0
- package/dist/actors/stop-cause.js +1 -0
- package/dist/analysis/evidence.js +2 -2
- package/dist/cli/commands/study.js +8 -0
- package/dist/evidence/redaction.d.ts +1 -0
- package/dist/evidence/redaction.js +1 -0
- package/dist/evidence/trace-page.d.ts +6 -0
- package/dist/evidence/trace-page.js +90 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/observer/feed-cursors.d.ts +14 -0
- package/dist/observer/feed-cursors.js +61 -0
- package/dist/observer/participant-items.d.ts +7 -0
- package/dist/observer/pinned-files.d.ts +4 -1
- package/dist/observer/pinned-files.js +10 -7
- package/dist/observer/run-routes.d.ts +1 -1
- package/dist/observer/run-routes.js +39 -17
- package/dist/observer-app.html +7 -7
- package/dist/routes/computer-use/bundle.js +4 -0
- package/dist/routes/computer-use/diagnostics.d.ts +1 -1
- package/dist/routes/computer-use/diagnostics.js +3 -3
- package/dist/routes/computer-use/e2b-desktop/desktop.js +2 -1
- package/dist/routes/computer-use/e2b-desktop/prepare.d.ts +6 -0
- package/dist/routes/computer-use/e2b-desktop/prepare.js +47 -1
- package/dist/routes/computer-use/e2b-desktop/start.d.ts +2 -1
- package/dist/routes/computer-use/e2b-desktop/start.js +15 -3
- package/dist/routes/computer-use/e2b-desktop/state.d.ts +3 -1
- package/dist/routes/computer-use/e2b-desktop/state.js +2 -0
- package/dist/routes/computer-use/fanout-records.js +4 -0
- package/dist/routes/computer-use/live-flush.d.ts +9 -4
- package/dist/routes/computer-use/live-flush.js +16 -3
- package/dist/routes/computer-use/participant-deps.d.ts +1 -1
- package/dist/routes/computer-use/participant-desktop.d.ts +1 -1
- package/dist/routes/computer-use/participant-model.js +16 -6
- package/dist/routes/computer-use/participant-runs.d.ts +2 -2
- package/dist/routes/computer-use/participant-runs.js +11 -7
- package/dist/routes/computer-use/plan.js +39 -1
- package/dist/routes/computer-use/setup.d.ts +1 -0
- package/dist/routes/computer-use/setup.js +28 -0
- package/dist/routes/computer-use/single-bundle.d.ts +5 -1
- package/dist/routes/computer-use/single-bundle.js +2 -0
- package/dist/routes/computer-use/types.d.ts +16 -10
- package/dist/routes/shared-world/bundle-records.js +3 -0
- package/dist/routes/shared-world/participant-specs.js +3 -2
- package/dist/routes/shared-world/plan.js +5 -0
- package/dist/run/bundle-shape.js +2 -2
- package/dist/run/bundle.d.ts +10 -3
- package/dist/run/bundle.js +11 -2
- package/dist/run/contained-output.d.ts +26 -0
- package/dist/run/contained-output.js +101 -1
- package/dist/run/evidence-files.d.ts +12 -1
- package/dist/run/evidence-files.js +70 -40
- package/dist/run/judge.d.ts +7 -4
- package/dist/run/judge.js +13 -8
- package/dist/run/live-items.d.ts +67 -0
- package/dist/run/live-items.js +262 -0
- package/dist/run/locate.d.ts +10 -0
- package/dist/run/locate.js +10 -0
- package/dist/run/run.d.ts +3 -2
- package/dist/run/run.js +29 -8
- package/dist/run/sandbox-ids.d.ts +0 -5
- package/dist/run/sandbox-ids.js +0 -7
- package/dist/run/stream-trace.d.ts +40 -10
- package/dist/run/stream-trace.js +151 -22
- package/dist/run/streams.d.ts +33 -2
- package/dist/study/arrivals.d.ts +32 -14
- package/dist/study/arrivals.js +47 -21
- package/dist/study/bundle-estimate.d.ts +6 -5
- package/dist/study/bundle-estimate.js +12 -7
- package/dist/study/desktop-files.d.ts +37 -0
- package/dist/study/desktop-files.js +174 -0
- package/dist/study/keys.d.ts +8 -0
- package/dist/study/keys.js +3 -0
- package/dist/study/migrate/v2.js +12 -3
- package/dist/study/parse/actors.d.ts +1 -1
- package/dist/study/parse/actors.js +51 -26
- package/dist/study/parse/clock.d.ts +18 -0
- package/dist/study/parse/clock.js +36 -0
- package/dist/study/parse/execution.js +6 -0
- package/dist/study/plan-base.js +4 -1
- package/dist/study/plan-participants.d.ts +19 -5
- package/dist/study/plan-participants.js +17 -1
- package/dist/study/plan.js +24 -13
- package/dist/study/preflight.js +4 -0
- package/dist/study/types.d.ts +34 -5
- package/dist/study/validation.js +3 -0
- package/dist/study/warnings.d.ts +2 -2
- package/dist/study/warnings.js +7 -0
- package/dist/subject/local-tree-archive.d.ts +7 -0
- package/dist/subject/local-tree-archive.js +12 -0
- package/dist/substrates/e2b/desktop-browser.d.ts +4 -1
- package/dist/substrates/e2b/desktop-browser.js +6 -2
- package/dist/substrates/e2b/desktop-clock.d.ts +10 -0
- package/dist/substrates/e2b/desktop-clock.js +29 -0
- package/dist/substrates/e2b/sdk.d.ts +6 -0
- package/dist/verify/artifacts.js +39 -51
- package/dist/verify/pages.d.ts +7 -0
- package/dist/verify/pages.js +50 -0
- package/dist/verify/screenshot-reads.d.ts +21 -0
- package/dist/verify/screenshot-reads.js +46 -0
- package/dist/verify/verify.js +17 -9
- package/docs/architecture/actor-contract.md +2 -0
- package/docs/architecture/external-public-shared-world.md +4 -1
- package/docs/architecture/github-feedback-loop.md +1 -1
- package/docs/architecture/observer.md +10 -1
- package/docs/contracts/README.md +1 -1
- package/docs/contracts/policy.md +7 -0
- package/docs/contracts/run-bundle.md +106 -14
- package/docs/contracts/schemas.md +110 -40
- package/package.json +4 -2
package/dist/run/stream-trace.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
// Where a participant's trace lives. A v1 run.json holds every stream's trace items and screenshot
|
|
2
2
|
// entries inline. From v2 the run writer moves them into one file per stream and leaves an index
|
|
3
|
-
// in run.json, so run.json stays a summary whatever the length of the sessions.
|
|
4
|
-
//
|
|
3
|
+
// in run.json, so run.json stays a summary whatever the length of the sessions. From v3 a running
|
|
4
|
+
// participant's items so far are in a live file run.json indexes too (src/run/live-items.ts).
|
|
5
|
+
// Every reader that needs the items loads them here, which accepts every version and checks each
|
|
6
|
+
// file against its index.
|
|
5
7
|
import { createHash } from "node:crypto";
|
|
6
|
-
import { RUN_BUNDLE_SCHEMA } from "./bundle.js";
|
|
7
|
-
import {
|
|
8
|
+
import { RUN_BUNDLE_FILE, RUN_BUNDLE_SCHEMA, RUN_BUNDLE_V2_SCHEMA, } from "./bundle.js";
|
|
9
|
+
import { isRunBundle } from "./bundle-shape.js";
|
|
10
|
+
import { readContainedPrefix, readContainedRegularFile, refusalText, RUN_ARTIFACT_MAX_BYTES, } from "./contained-output.js";
|
|
11
|
+
import { isLiveItemsIndex, LIVE_ACTOR_SCHEMA, LIVE_ACTOR_V1_SCHEMA, liveItemsAfter, liveItemsFromBytes, } from "./live-items.js";
|
|
8
12
|
import { isRunStreamArtifact, isStreamTraceIndex, isStreamTracePath } from "./stream-shape.js";
|
|
9
13
|
import { isRecord } from "./type-guards.js";
|
|
10
14
|
/** A trace that does not match its index, or cannot be read. The message names the stream. */
|
|
@@ -83,8 +87,10 @@ export function splitStreamTraces(bundle) {
|
|
|
83
87
|
return { bundle: { ...bundle, schema: RUN_BUNDLE_SCHEMA, streams }, files };
|
|
84
88
|
}
|
|
85
89
|
function containedReader(runPaths) {
|
|
86
|
-
return async (relativePath, maxBytes) => {
|
|
87
|
-
const read =
|
|
90
|
+
return async (relativePath, maxBytes, length, start) => {
|
|
91
|
+
const read = length === undefined
|
|
92
|
+
? await readContainedRegularFile(runPaths, relativePath, maxBytes)
|
|
93
|
+
: await readContainedPrefix(runPaths, relativePath, length, start);
|
|
88
94
|
if (read.status === "missing")
|
|
89
95
|
return { refused: `${relativePath} is missing` };
|
|
90
96
|
if (read.status === "refused")
|
|
@@ -136,15 +142,94 @@ function assertIndex(stream) {
|
|
|
136
142
|
if (stream.actor.items !== undefined)
|
|
137
143
|
throw new StreamTraceRefusedError(stream.id, "run.json holds items beside its trace index");
|
|
138
144
|
}
|
|
139
|
-
/**
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
/** A live actor whose items run.json holds inline: humanish.live-actor.v1, or one with no schema. */
|
|
146
|
+
const isInlineLiveActor = (live) => live.schema === LIVE_ACTOR_V1_SCHEMA || live.schema === undefined;
|
|
147
|
+
/** A live file that could not be read, which a writer that moved on may have removed. */
|
|
148
|
+
class LiveFileUnreadableError extends StreamTraceRefusedError {
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The stream with its live actor's items joined from the indexed prefix of its live file, and the
|
|
152
|
+
* file's path and those bytes. A live actor with inline items (humanish.live-actor.v1) is returned
|
|
153
|
+
* as it is; bundleRefusal refuses one in a v3 run.json.
|
|
154
|
+
*/
|
|
155
|
+
async function readLiveItems(read, stream, maxBytes, liveAfter) {
|
|
156
|
+
const live = stream.liveActor;
|
|
157
|
+
if (live === undefined || isInlineLiveActor(live))
|
|
142
158
|
return { stream };
|
|
143
|
-
|
|
144
|
-
|
|
159
|
+
const refuse = (detail) => {
|
|
160
|
+
throw new StreamTraceRefusedError(stream.id, detail);
|
|
161
|
+
};
|
|
162
|
+
if (live.schema !== LIVE_ACTOR_SCHEMA)
|
|
163
|
+
refuse(`run.json records live actor schema ${String(live.schema)}, which this version does not read`);
|
|
164
|
+
const index = live.itemsFile;
|
|
165
|
+
if (!isLiveItemsIndex(index))
|
|
166
|
+
return refuse("run.json records a malformed live items index");
|
|
167
|
+
if (live.items !== undefined)
|
|
168
|
+
refuse("run.json holds in-progress items beside their live file");
|
|
169
|
+
if (index.bytes > maxBytes)
|
|
170
|
+
refuse(`${index.path} is indexed at ${index.bytes} bytes, more than the ${maxBytes} read`);
|
|
171
|
+
const { itemsFile: _index, ...summary } = live;
|
|
172
|
+
const after = liveAfter?.(index);
|
|
173
|
+
const tail = after === undefined ? undefined : await readLiveTail(read, stream, index, after);
|
|
174
|
+
if (tail !== undefined)
|
|
175
|
+
return {
|
|
176
|
+
stream: { ...stream, liveActor: { ...summary, items: tail.items } },
|
|
177
|
+
file: { path: index.path, bytes: tail.bytes },
|
|
178
|
+
read: { streamId: stream.id, index, after: after },
|
|
179
|
+
};
|
|
180
|
+
const file = await read(index.path, maxBytes, index.bytes);
|
|
145
181
|
if ("refused" in file)
|
|
146
|
-
throw new
|
|
147
|
-
|
|
182
|
+
throw new LiveFileUnreadableError(stream.id, file.refused);
|
|
183
|
+
// Only the indexed prefix: a reader that ignores `length` hands over the bytes appended since.
|
|
184
|
+
const joined = liveItemsFromBytes(file.bytes.subarray(0, index.bytes), index);
|
|
185
|
+
if ("refused" in joined)
|
|
186
|
+
return refuse(joined.refused);
|
|
187
|
+
return {
|
|
188
|
+
stream: { ...stream, liveActor: { ...summary, items: joined.items } },
|
|
189
|
+
file: { path: index.path, bytes: file.bytes.subarray(0, index.bytes) },
|
|
190
|
+
read: { streamId: stream.id, index },
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The items of the live file `index` describes past `after`, and the bytes they were read from;
|
|
195
|
+
* undefined when the cursor does not fit the index or the file's lines, so the caller reads the
|
|
196
|
+
* whole prefix. A cursor at the indexed end reads nothing. A file the reader cannot read throws
|
|
197
|
+
* LiveFileUnreadableError, as a whole read does.
|
|
198
|
+
*/
|
|
199
|
+
async function readLiveTail(read, stream, index, after) {
|
|
200
|
+
if (after.bytes === 0 ||
|
|
201
|
+
after.items > index.items ||
|
|
202
|
+
after.bytes > index.bytes ||
|
|
203
|
+
(after.items === index.items) !== (after.bytes === index.bytes))
|
|
204
|
+
return undefined;
|
|
205
|
+
if (after.bytes === index.bytes)
|
|
206
|
+
return { items: [], bytes: Buffer.alloc(0) };
|
|
207
|
+
const file = await read(index.path, index.bytes - after.bytes + 1, index.bytes, after.bytes - 1);
|
|
208
|
+
if ("refused" in file)
|
|
209
|
+
throw new LiveFileUnreadableError(stream.id, file.refused);
|
|
210
|
+
const tail = file.bytes.subarray(0, index.bytes - after.bytes + 1);
|
|
211
|
+
const joined = liveItemsAfter(tail, index, after);
|
|
212
|
+
return "refused" in joined ? undefined : { items: joined.items, bytes: tail.subarray(1) };
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* The stream with its trace and live items joined, and each file's path and the bytes it was
|
|
216
|
+
* joined from.
|
|
217
|
+
*/
|
|
218
|
+
async function readStreamTrace(read, stream, maxBytes, liveAfter) {
|
|
219
|
+
const files = [];
|
|
220
|
+
let joined = stream;
|
|
221
|
+
if (stream.trace !== undefined) {
|
|
222
|
+
assertIndex(stream);
|
|
223
|
+
const file = await read(stream.trace.path, maxBytes);
|
|
224
|
+
if ("refused" in file)
|
|
225
|
+
throw new StreamTraceRefusedError(stream.id, file.refused);
|
|
226
|
+
joined = joinTrace(stream, file.bytes);
|
|
227
|
+
files.push({ path: stream.trace.path, bytes: file.bytes });
|
|
228
|
+
}
|
|
229
|
+
const live = await readLiveItems(read, joined, maxBytes, liveAfter);
|
|
230
|
+
if (live.file !== undefined)
|
|
231
|
+
files.push(live.file);
|
|
232
|
+
return { stream: live.stream, files, ...(live.read === undefined ? {} : { live: live.read }) };
|
|
148
233
|
}
|
|
149
234
|
/**
|
|
150
235
|
* The stream as a v1 run.json held it: its actor with every item and every artifact entry. A
|
|
@@ -163,8 +248,10 @@ export async function loadStreamTrace(runPaths, stream, maxBytes = RUN_ARTIFACT_
|
|
|
163
248
|
* case-insensitive file system compares them, is refused. In v2 the writer
|
|
164
249
|
* indexes every actor on every write, mid-run flushes included, so a v2 actor without an index lost
|
|
165
250
|
* its trace file, and inline items never come from the writer. A run still writing carries its
|
|
166
|
-
* participants' items so far as `liveActor
|
|
167
|
-
*
|
|
251
|
+
* participants' items so far as `liveActor`: inline in v2 (humanish.live-actor.v1), in a live file
|
|
252
|
+
* from v3 (humanish.live-actor.v2), so a v3 run.json with inline live items did not come from the
|
|
253
|
+
* writer. A run.json whose outcome says the run finished never carries `liveActor`, since the
|
|
254
|
+
* final write replaces it.
|
|
168
255
|
*/
|
|
169
256
|
function bundleRefusal(bundle, stream, owners) {
|
|
170
257
|
const shared = isRecord(stream.trace)
|
|
@@ -172,12 +259,16 @@ function bundleRefusal(bundle, stream, owners) {
|
|
|
172
259
|
: undefined;
|
|
173
260
|
if (shared !== undefined && shared.length > 1)
|
|
174
261
|
return `run.json indexes ${String(stream.trace.path)} for participants ${shared.join(", ")} (paths that differ only in case name one file on some file systems), and a trace file belongs to one participant`;
|
|
175
|
-
if (bundle.schema !== RUN_BUNDLE_SCHEMA)
|
|
262
|
+
if (bundle.schema !== RUN_BUNDLE_SCHEMA && bundle.schema !== RUN_BUNDLE_V2_SCHEMA)
|
|
176
263
|
return undefined;
|
|
177
264
|
if (stream.actor !== undefined && stream.trace === undefined)
|
|
178
265
|
return "run.json holds its actor and names no trace file";
|
|
179
266
|
if (stream.liveActor !== undefined && bundle.outcome?.state === "finished")
|
|
180
267
|
return "run.json records a finished run and still holds in-progress items";
|
|
268
|
+
if (bundle.schema === RUN_BUNDLE_SCHEMA &&
|
|
269
|
+
stream.liveActor !== undefined &&
|
|
270
|
+
isInlineLiveActor(stream.liveActor))
|
|
271
|
+
return "run.json v3 holds in-progress items inline, which the writer keeps in a live file";
|
|
181
272
|
return undefined;
|
|
182
273
|
}
|
|
183
274
|
/**
|
|
@@ -185,11 +276,46 @@ function bundleRefusal(bundle, stream, owners) {
|
|
|
185
276
|
* of each stream that could not be (see bundleRefusal for the rules that need the whole bundle).
|
|
186
277
|
* Each refused stream is kept as run.json holds it; a reader that shows the run passes the result
|
|
187
278
|
* to displayedTraces. `source` is the run's prepared paths, or a TraceFileReader. `onTrace`
|
|
188
|
-
* receives each trace file's path and the bytes the stream was joined from, after they
|
|
189
|
-
* the index, so a caller scans exactly what it reads.
|
|
279
|
+
* receives each trace and live file's path and the bytes the stream was joined from, after they
|
|
280
|
+
* matched the index, so a caller scans exactly what it reads.
|
|
281
|
+
*
|
|
282
|
+
* `liveAfter` names, for a live file's index, the prefix the caller holds already (the Observer
|
|
283
|
+
* feed's cursor). The loader then reads and joins only the items past it, checked by liveItemsAfter
|
|
284
|
+
* without the indexed sha256, and `live` records the cursor; a cursor past the index or off the
|
|
285
|
+
* file's lines gets the whole prefix, checked as always.
|
|
286
|
+
*
|
|
287
|
+
* A live file that cannot be read may have been removed by the writer after `bundle` was read: it
|
|
288
|
+
* removes a superseded generation once run.json names the next one, and every live file once the
|
|
289
|
+
* run finished. The loader then reads run.json again, once, through the same reader, and loads
|
|
290
|
+
* that bundle instead when it is a run bundle of the same run.
|
|
190
291
|
*/
|
|
191
292
|
export async function loadStreamTraces(source, bundle, options = {}) {
|
|
192
293
|
const read = typeof source === "function" ? source : containedReader(source);
|
|
294
|
+
const maxBytes = options.maxBytes ?? RUN_ARTIFACT_MAX_BYTES;
|
|
295
|
+
let loaded = await loadEveryStream(read, bundle, maxBytes, options.liveAfter);
|
|
296
|
+
if (loaded.refused.some((error) => error instanceof LiveFileUnreadableError)) {
|
|
297
|
+
const current = await rereadBundle(read, bundle.runId);
|
|
298
|
+
if (current !== undefined)
|
|
299
|
+
loaded = await loadEveryStream(read, current, maxBytes, options.liveAfter);
|
|
300
|
+
}
|
|
301
|
+
for (const file of loaded.files)
|
|
302
|
+
options.onTrace?.(file.path, file.bytes);
|
|
303
|
+
return { bundle: loaded.bundle, refused: loaded.refused, live: loaded.live };
|
|
304
|
+
}
|
|
305
|
+
/** run.json as it is now, when it is a run bundle of run `runId`. */
|
|
306
|
+
async function rereadBundle(read, runId) {
|
|
307
|
+
const file = await read(RUN_BUNDLE_FILE, RUN_ARTIFACT_MAX_BYTES);
|
|
308
|
+
if ("refused" in file)
|
|
309
|
+
return undefined;
|
|
310
|
+
try {
|
|
311
|
+
const value = JSON.parse(file.bytes.toString("utf8"));
|
|
312
|
+
return isRunBundle(value) && value.runId === runId ? value : undefined;
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async function loadEveryStream(read, bundle, maxBytes, liveAfter) {
|
|
193
319
|
const owners = new Map();
|
|
194
320
|
for (const stream of bundle.streams)
|
|
195
321
|
if (isRecord(stream.trace)) {
|
|
@@ -198,14 +324,17 @@ export async function loadStreamTraces(source, bundle, options = {}) {
|
|
|
198
324
|
}
|
|
199
325
|
const streams = [];
|
|
200
326
|
const refused = [];
|
|
327
|
+
const files = [];
|
|
328
|
+
const live = [];
|
|
201
329
|
for (const stream of bundle.streams) {
|
|
202
330
|
try {
|
|
203
331
|
const reason = bundleRefusal(bundle, stream, owners);
|
|
204
332
|
if (reason !== undefined)
|
|
205
333
|
throw new StreamTraceRefusedError(stream.id, reason);
|
|
206
|
-
const loaded = await readStreamTrace(read, stream,
|
|
207
|
-
|
|
208
|
-
|
|
334
|
+
const loaded = await readStreamTrace(read, stream, maxBytes, liveAfter);
|
|
335
|
+
files.push(...loaded.files);
|
|
336
|
+
if (loaded.live !== undefined)
|
|
337
|
+
live.push(loaded.live);
|
|
209
338
|
streams.push(loaded.stream);
|
|
210
339
|
}
|
|
211
340
|
catch (error) {
|
|
@@ -215,7 +344,7 @@ export async function loadStreamTraces(source, bundle, options = {}) {
|
|
|
215
344
|
streams.push(stream);
|
|
216
345
|
}
|
|
217
346
|
}
|
|
218
|
-
return { bundle: { ...bundle, streams }, refused };
|
|
347
|
+
return { bundle: { ...bundle, streams }, refused, files, live };
|
|
219
348
|
}
|
|
220
349
|
/**
|
|
221
350
|
* A loaded bundle as a reader that shows a run presents it. A refused stream keeps run.json's
|
package/dist/run/streams.d.ts
CHANGED
|
@@ -154,6 +154,18 @@ export interface StreamTraceIndex {
|
|
|
154
154
|
items: number;
|
|
155
155
|
screenshots: number;
|
|
156
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* A v3 run.json's index of a running participant's live file (src/run/live-items.ts): its path
|
|
159
|
+
* `live/<position>.<generation>.ndjson`, the generation, and the prefix written so far, as its item
|
|
160
|
+
* (line) count, byte count and sha256. The file only grows, so the prefix stays as indexed.
|
|
161
|
+
*/
|
|
162
|
+
export interface LiveItemsIndex {
|
|
163
|
+
path: string;
|
|
164
|
+
generation: number;
|
|
165
|
+
items: number;
|
|
166
|
+
bytes: number;
|
|
167
|
+
sha256: string;
|
|
168
|
+
}
|
|
157
169
|
/** The original participant-facing assignment, before runtime access/coordination details. */
|
|
158
170
|
export interface RunParticipantAssignment {
|
|
159
171
|
mission: string;
|
|
@@ -163,6 +175,15 @@ export interface RunParticipantAssignment {
|
|
|
163
175
|
goal: string;
|
|
164
176
|
}>;
|
|
165
177
|
}
|
|
178
|
+
/** One file humanish copied onto a participant's desktop before it started (participants[].files). */
|
|
179
|
+
export interface RunDesktopFile {
|
|
180
|
+
/** Where it landed, relative to the desktop user's home folder. */
|
|
181
|
+
as: string;
|
|
182
|
+
/** Its size in bytes, confirmed on the desktop. */
|
|
183
|
+
size: number;
|
|
184
|
+
/** The sha256 of the bytes humanish copied, 64 hex characters. */
|
|
185
|
+
sha256: string;
|
|
186
|
+
}
|
|
166
187
|
export interface RunStream {
|
|
167
188
|
id: string;
|
|
168
189
|
simId: string;
|
|
@@ -220,6 +241,13 @@ export interface RunStream {
|
|
|
220
241
|
};
|
|
221
242
|
/** Truthful screen/window/viewport evidence for hosted desktop browser participants. */
|
|
222
243
|
desktopGeometry?: RunDesktopGeometry;
|
|
244
|
+
/** The files copied onto this participant's desktop before it started. Never their contents. */
|
|
245
|
+
desktopFiles?: RunDesktopFile[];
|
|
246
|
+
/**
|
|
247
|
+
* The participant's time zone (participants[].clock, else actor.clock): its turns gave the time
|
|
248
|
+
* in it, and its hosted browser ran in it. Absent when it had none.
|
|
249
|
+
*/
|
|
250
|
+
timeZone?: string;
|
|
223
251
|
terminal?: {
|
|
224
252
|
title: string;
|
|
225
253
|
format: "ansi" | "plain";
|
|
@@ -260,10 +288,11 @@ export interface RunStream {
|
|
|
260
288
|
* grow. It is deliberately not an ActorTrace: a running participant has no final status,
|
|
261
289
|
* completionReason, or completedAt, and this shape cannot claim them. Present only on
|
|
262
290
|
* `inProgress` bundles; the final write replaces it with the real `actor` and never
|
|
263
|
-
* carries it.
|
|
291
|
+
* carries it. A v3 run.json holds `humanish.live-actor.v2` with `itemsFile` in place of `items`;
|
|
292
|
+
* the trace loader (src/run/stream-trace.ts) reads the file and puts the items back.
|
|
264
293
|
*/
|
|
265
294
|
liveActor?: {
|
|
266
|
-
schema: "humanish.live-actor.v1";
|
|
295
|
+
schema: "humanish.live-actor.v1" | "humanish.live-actor.v2";
|
|
267
296
|
executionProfile?: ActorTrace["executionProfile"];
|
|
268
297
|
providerRequests?: ActorTrace["providerRequests"];
|
|
269
298
|
historyTurnsOmitted?: number;
|
|
@@ -273,6 +302,8 @@ export interface RunStream {
|
|
|
273
302
|
/** When this flush was written (ISO-8601). */
|
|
274
303
|
updatedAt: string;
|
|
275
304
|
items: ActorTraceItem[];
|
|
305
|
+
/** In a v3 run.json, in place of `items`: the live file that holds them. */
|
|
306
|
+
itemsFile?: LiveItemsIndex;
|
|
276
307
|
};
|
|
277
308
|
completion?: RunStreamCompletion;
|
|
278
309
|
recording?: RunDesktopRecording;
|
package/dist/study/arrivals.d.ts
CHANGED
|
@@ -8,41 +8,58 @@ export declare const LATEST_START_AFTER_MS: number;
|
|
|
8
8
|
export declare function isStartAfterMs(value: unknown): value is number;
|
|
9
9
|
/** How long before its end a participant is told the time left, unless it declares `windDownMs`. */
|
|
10
10
|
export declare const DEFAULT_WIND_DOWN_MS: number;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* A participant's declared window: when it starts, and when it ends on the run's clock
|
|
13
|
+
* (`endAfterMs`) or at the latest after its own session started (`maxSessionMs`).
|
|
14
|
+
*/
|
|
12
15
|
export interface ParticipantWindow {
|
|
13
16
|
readonly id?: string;
|
|
14
17
|
readonly startAfterMs?: number;
|
|
15
18
|
readonly endAfterMs?: number;
|
|
19
|
+
readonly maxSessionMs?: number;
|
|
16
20
|
readonly windDownMs?: number;
|
|
17
21
|
}
|
|
18
22
|
/**
|
|
19
23
|
* Why a participant's declared end cannot run, or undefined: an end that is not a whole number of
|
|
20
|
-
* milliseconds up to 24 hours, one at or before its start,
|
|
21
|
-
*
|
|
24
|
+
* milliseconds up to 24 hours, one at or before its start, a cap that is not a whole number of
|
|
25
|
+
* milliseconds from 1 to 24 hours, a cap beside an end, or a wind-down that is not a whole number
|
|
26
|
+
* of milliseconds or has neither. `path` names the entry, as in "participants[1]".
|
|
22
27
|
*/
|
|
23
28
|
export declare function windowReason(entry: {
|
|
24
29
|
readonly id?: string;
|
|
25
30
|
readonly startAfterMs?: number;
|
|
26
31
|
readonly endAfterMs?: unknown;
|
|
32
|
+
readonly maxSessionMs?: unknown;
|
|
27
33
|
readonly windDownMs?: unknown;
|
|
28
34
|
}, path: string): string | undefined;
|
|
29
35
|
/**
|
|
30
|
-
* A participant's session budget: with a declared end, its window plus SCHEDULED_END_ROOM_MS
|
|
31
|
-
* place of the study's `sessionMs` (execution.timeoutMs
|
|
36
|
+
* A participant's session budget: with a declared end, its window plus SCHEDULED_END_ROOM_MS, and
|
|
37
|
+
* with a cap, the cap plus the same room, in place of the study's `sessionMs` (execution.timeoutMs
|
|
38
|
+
* or its default); else `sessionMs`.
|
|
32
39
|
*/
|
|
33
40
|
export declare function participantSessionMs(participant: ParticipantWindow, sessionMs: number): number;
|
|
34
41
|
/**
|
|
35
|
-
* When a participant
|
|
36
|
-
*
|
|
37
|
-
* at its declared time.
|
|
42
|
+
* When a participant's session ends: at a time on the run's clock, or at the latest a time after
|
|
43
|
+
* its own session starts, which the route reads when the session starts.
|
|
38
44
|
*/
|
|
39
|
-
export
|
|
45
|
+
export type ParticipantEnd = {
|
|
46
|
+
readonly kind: "scheduled_end";
|
|
40
47
|
readonly atMs: number;
|
|
41
48
|
readonly windDownMs: number;
|
|
42
|
-
} |
|
|
49
|
+
} | {
|
|
50
|
+
readonly kind: "session_cap";
|
|
51
|
+
readonly afterMs: number;
|
|
52
|
+
readonly windDownMs: number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* How a participant that was due at `scheduledAt` (runOnSchedule's ScheduledStart) ends, or
|
|
56
|
+
* undefined when it declares neither an end nor a cap. A participant that waited for a slot still
|
|
57
|
+
* ends at its declared time, and still gets its whole cap.
|
|
58
|
+
*/
|
|
59
|
+
export declare function participantEnd(participant: ParticipantWindow, scheduledAt: number): ParticipantEnd | undefined;
|
|
43
60
|
/**
|
|
44
|
-
* Why the sandbox of the first participant whose window passes `ceilingMs` cannot live that
|
|
45
|
-
* or undefined. Its deadline is its session (participantSessionMs) plus `headroomMs`, what the
|
|
61
|
+
* Why the sandbox of the first participant whose window or cap passes `ceilingMs` cannot live that
|
|
62
|
+
* long, or undefined. Its deadline is its session (participantSessionMs) plus `headroomMs`, what the
|
|
46
63
|
* route adds to a session for setup and teardown (sandboxHeadroomMs).
|
|
47
64
|
*/
|
|
48
65
|
export declare function windowDeadlineRefusal(participants: readonly ParticipantWindow[], sandbox: {
|
|
@@ -50,8 +67,9 @@ export declare function windowDeadlineRefusal(participants: readonly Participant
|
|
|
50
67
|
readonly headroomMs: number;
|
|
51
68
|
}, ceilingMs: number): string | undefined;
|
|
52
69
|
/**
|
|
53
|
-
* Each participant's window for `study check`, when any participant declares an end: when
|
|
54
|
-
* and ends and when it is first told the time left,
|
|
70
|
+
* Each participant's window for `study check`, when any participant declares an end or a cap: when
|
|
71
|
+
* it starts and ends and when it is first told the time left, when it starts and its cap and how
|
|
72
|
+
* far into its session it is first told the time left, or when it starts and its session budget.
|
|
55
73
|
*/
|
|
56
74
|
export declare function describeWindows(participants: readonly ParticipantWindow[], sessionMs: number): string | undefined;
|
|
57
75
|
/** The refusal of a start offset that is not one, naming its `field`. */
|
package/dist/study/arrivals.js
CHANGED
|
@@ -32,13 +32,18 @@ export const DEFAULT_WIND_DOWN_MS = 10 * 60_000;
|
|
|
32
32
|
const SCHEDULED_END_ROOM_MS = 5 * 60_000;
|
|
33
33
|
/**
|
|
34
34
|
* Why a participant's declared end cannot run, or undefined: an end that is not a whole number of
|
|
35
|
-
* milliseconds up to 24 hours, one at or before its start,
|
|
36
|
-
*
|
|
35
|
+
* milliseconds up to 24 hours, one at or before its start, a cap that is not a whole number of
|
|
36
|
+
* milliseconds from 1 to 24 hours, a cap beside an end, or a wind-down that is not a whole number
|
|
37
|
+
* of milliseconds or has neither. `path` names the entry, as in "participants[1]".
|
|
37
38
|
*/
|
|
38
39
|
export function windowReason(entry, path) {
|
|
39
|
-
const { endAfterMs, windDownMs } = entry;
|
|
40
|
-
if (
|
|
41
|
-
return `${path}
|
|
40
|
+
const { endAfterMs, maxSessionMs, windDownMs } = entry;
|
|
41
|
+
if (maxSessionMs !== undefined && endAfterMs !== undefined)
|
|
42
|
+
return `${path} sets both endAfterMs and maxSessionMs. endAfterMs holds the participant until a set time, and maxSessionMs lets it end its session sooner, up to a cap, so a participant takes one of them. Keep endAfterMs for one that stays until a set time, or maxSessionMs for one that may leave when it is done.`;
|
|
43
|
+
if (maxSessionMs !== undefined && !(isStartAfterMs(maxSessionMs) && maxSessionMs > 0))
|
|
44
|
+
return `${path}.maxSessionMs must be a whole number of milliseconds from 1 to ${LATEST_START_AFTER_MS} (24 hours): the longest the participant's session lasts, counted from its start.`;
|
|
45
|
+
if (windDownMs !== undefined && endAfterMs === undefined && maxSessionMs === undefined)
|
|
46
|
+
return `${path}.windDownMs is how long before its endAfterMs or maxSessionMs a participant is told the time left, so it needs one of them. Add an end, or remove windDownMs.`;
|
|
42
47
|
if (windDownMs !== undefined && !isStartAfterMs(windDownMs))
|
|
43
48
|
return `${path}.windDownMs must be a whole number of milliseconds from 0 to ${LATEST_START_AFTER_MS} (24 hours): how long before its end the participant is told the time left.`;
|
|
44
49
|
if (endAfterMs === undefined)
|
|
@@ -53,54 +58,75 @@ export function windowReason(entry, path) {
|
|
|
53
58
|
return undefined;
|
|
54
59
|
}
|
|
55
60
|
/**
|
|
56
|
-
* A participant's session budget: with a declared end, its window plus SCHEDULED_END_ROOM_MS
|
|
57
|
-
* place of the study's `sessionMs` (execution.timeoutMs
|
|
61
|
+
* A participant's session budget: with a declared end, its window plus SCHEDULED_END_ROOM_MS, and
|
|
62
|
+
* with a cap, the cap plus the same room, in place of the study's `sessionMs` (execution.timeoutMs
|
|
63
|
+
* or its default); else `sessionMs`.
|
|
58
64
|
*/
|
|
59
65
|
export function participantSessionMs(participant, sessionMs) {
|
|
66
|
+
const windowMs = declaredWindowMs(participant);
|
|
67
|
+
return windowMs === undefined ? sessionMs : windowMs + SCHEDULED_END_ROOM_MS;
|
|
68
|
+
}
|
|
69
|
+
/** How long a participant with a declared end or cap may work, or undefined without either. */
|
|
70
|
+
function declaredWindowMs(participant) {
|
|
71
|
+
if (participant.maxSessionMs !== undefined)
|
|
72
|
+
return participant.maxSessionMs;
|
|
60
73
|
if (participant.endAfterMs === undefined)
|
|
61
|
-
return
|
|
62
|
-
return participant.endAfterMs - (participant.startAfterMs ?? 0)
|
|
74
|
+
return undefined;
|
|
75
|
+
return participant.endAfterMs - (participant.startAfterMs ?? 0);
|
|
63
76
|
}
|
|
64
77
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* at its declared time.
|
|
78
|
+
* How a participant that was due at `scheduledAt` (runOnSchedule's ScheduledStart) ends, or
|
|
79
|
+
* undefined when it declares neither an end nor a cap. A participant that waited for a slot still
|
|
80
|
+
* ends at its declared time, and still gets its whole cap.
|
|
68
81
|
*/
|
|
69
|
-
export function
|
|
82
|
+
export function participantEnd(participant, scheduledAt) {
|
|
83
|
+
const windDownMs = participant.windDownMs ?? DEFAULT_WIND_DOWN_MS;
|
|
84
|
+
if (participant.maxSessionMs !== undefined)
|
|
85
|
+
return { kind: "session_cap", afterMs: participant.maxSessionMs, windDownMs };
|
|
70
86
|
if (participant.endAfterMs === undefined)
|
|
71
87
|
return undefined;
|
|
72
88
|
return {
|
|
89
|
+
kind: "scheduled_end",
|
|
73
90
|
atMs: scheduledAt - (participant.startAfterMs ?? 0) + participant.endAfterMs,
|
|
74
|
-
windDownMs
|
|
91
|
+
windDownMs,
|
|
75
92
|
};
|
|
76
93
|
}
|
|
77
94
|
/**
|
|
78
|
-
* Why the sandbox of the first participant whose window passes `ceilingMs` cannot live that
|
|
79
|
-
* or undefined. Its deadline is its session (participantSessionMs) plus `headroomMs`, what the
|
|
95
|
+
* Why the sandbox of the first participant whose window or cap passes `ceilingMs` cannot live that
|
|
96
|
+
* long, or undefined. Its deadline is its session (participantSessionMs) plus `headroomMs`, what the
|
|
80
97
|
* route adds to a session for setup and teardown (sandboxHeadroomMs).
|
|
81
98
|
*/
|
|
82
99
|
export function windowDeadlineRefusal(participants, sandbox, ceilingMs) {
|
|
83
100
|
const inMinutes = (ms) => Math.round(ms / 60_000);
|
|
84
101
|
for (const participant of participants) {
|
|
85
|
-
if (participant
|
|
102
|
+
if (declaredWindowMs(participant) === undefined)
|
|
86
103
|
continue;
|
|
87
104
|
const deadlineMs = participantSessionMs(participant, sandbox.sessionMs) + sandbox.headroomMs;
|
|
88
105
|
if (deadlineMs <= ceilingMs)
|
|
89
106
|
continue;
|
|
90
107
|
const name = participant.id === undefined ? "A participant" : `Participant "${participant.id}"`;
|
|
91
|
-
|
|
108
|
+
const works = participant.maxSessionMs === undefined
|
|
109
|
+
? `works from +${formatDuration(participant.startAfterMs ?? 0)} to +${formatDuration(participant.endAfterMs ?? 0)}`
|
|
110
|
+
: `may work for up to ${formatDuration(participant.maxSessionMs)}`;
|
|
111
|
+
const window = participant.maxSessionMs === undefined ? "window" : "cap";
|
|
112
|
+
return `${name} ${works}, so its sandbox needs a ${inMinutes(deadlineMs)}m deadline: the ${window}, ${inMinutes(SCHEDULED_END_ROOM_MS)}m for its closing report and ${inMinutes(sandbox.headroomMs)}m to set up and tear down. A sandbox may not live longer than ${ceilingMs / 60_000}m. Shorten its ${window}.${ceilingAdvice(deadlineMs)}`;
|
|
92
113
|
}
|
|
93
114
|
return undefined;
|
|
94
115
|
}
|
|
95
116
|
/**
|
|
96
|
-
* Each participant's window for `study check`, when any participant declares an end: when
|
|
97
|
-
* and ends and when it is first told the time left,
|
|
117
|
+
* Each participant's window for `study check`, when any participant declares an end or a cap: when
|
|
118
|
+
* it starts and ends and when it is first told the time left, when it starts and its cap and how
|
|
119
|
+
* far into its session it is first told the time left, or when it starts and its session budget.
|
|
98
120
|
*/
|
|
99
121
|
export function describeWindows(participants, sessionMs) {
|
|
100
|
-
if (!participants.some((participant) => participant
|
|
122
|
+
if (!participants.some((participant) => declaredWindowMs(participant) !== undefined))
|
|
101
123
|
return undefined;
|
|
102
124
|
const windows = participants.map((participant) => {
|
|
103
125
|
const start = `${participant.id ?? "participant"} from +${formatDuration(participant.startAfterMs ?? 0)}`;
|
|
126
|
+
if (participant.maxSessionMs !== undefined) {
|
|
127
|
+
const told = Math.max(0, participant.maxSessionMs - (participant.windDownMs ?? DEFAULT_WIND_DOWN_MS));
|
|
128
|
+
return `${start} until it ends itself or for up to ${formatDuration(participant.maxSessionMs)}, told the time left from ${formatDuration(told)} into its session`;
|
|
129
|
+
}
|
|
104
130
|
if (participant.endAfterMs === undefined)
|
|
105
131
|
return `${start} for up to ${formatDuration(sessionMs)}`;
|
|
106
132
|
const told = Math.max(participant.startAfterMs ?? 0, participant.endAfterMs - (participant.windDownMs ?? DEFAULT_WIND_DOWN_MS));
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { DwellWindow } from "../actors/stop-conditions.js";
|
|
2
|
-
/** One participant as the plan has it: when it starts and ends, and its dwell window. */
|
|
2
|
+
/** One participant as the plan has it: when it starts and ends, its cap, and its dwell window. */
|
|
3
3
|
export interface EstimatedParticipant {
|
|
4
4
|
readonly startAfterMs?: number;
|
|
5
5
|
readonly endAfterMs?: number;
|
|
6
|
+
readonly maxSessionMs?: number;
|
|
6
7
|
readonly limits: {
|
|
7
8
|
readonly dwell?: DwellWindow;
|
|
8
9
|
};
|
|
@@ -33,10 +34,10 @@ export interface BundleEstimate {
|
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
36
|
* The bundle a live run of `participants` leaves, each in its session of `sessionMs` unless it
|
|
36
|
-
* declares an end. A participant with an end works its whole window; one without
|
|
37
|
-
* the measured open session above, or its budget
|
|
38
|
-
*
|
|
39
|
-
* session, and the frame after it.
|
|
37
|
+
* declares an end or a cap. A participant with an end works its whole window; one without ends
|
|
38
|
+
* itself, so it is counted for the measured open session above, or its budget (its cap, else
|
|
39
|
+
* `sessionMs`) when that is shorter. Each takes a model turn at the measured pace, and a dwell
|
|
40
|
+
* window adds a frame every `everyMs` for its length, cut to the session, and the frame after it.
|
|
40
41
|
*/
|
|
41
42
|
export declare function estimateBundle(participants: readonly EstimatedParticipant[], sessionMs: number): BundleEstimate;
|
|
42
43
|
/**
|
|
@@ -48,26 +48,31 @@ const RATES = Object.freeze({
|
|
|
48
48
|
const MIB = 1024 * 1024;
|
|
49
49
|
/**
|
|
50
50
|
* The bundle a live run of `participants` leaves, each in its session of `sessionMs` unless it
|
|
51
|
-
* declares an end. A participant with an end works its whole window; one without
|
|
52
|
-
* the measured open session above, or its budget
|
|
53
|
-
*
|
|
54
|
-
* session, and the frame after it.
|
|
51
|
+
* declares an end or a cap. A participant with an end works its whole window; one without ends
|
|
52
|
+
* itself, so it is counted for the measured open session above, or its budget (its cap, else
|
|
53
|
+
* `sessionMs`) when that is shorter. Each takes a model turn at the measured pace, and a dwell
|
|
54
|
+
* window adds a frame every `everyMs` for its length, cut to the session, and the frame after it.
|
|
55
55
|
*/
|
|
56
56
|
export function estimateBundle(participants, sessionMs) {
|
|
57
57
|
let turns = 0;
|
|
58
58
|
let frames = 0;
|
|
59
59
|
let longestWindowMs = 0;
|
|
60
|
-
|
|
60
|
+
// The longest any participant without an end is counted, for the messages.
|
|
61
|
+
let openMs = Math.min(sessionMs, RATES.openSessionMs);
|
|
61
62
|
for (const participant of participants) {
|
|
62
63
|
const windowMs = participant.endAfterMs === undefined
|
|
63
64
|
? undefined
|
|
64
65
|
: participant.endAfterMs - (participant.startAfterMs ?? 0);
|
|
66
|
+
const budgetMs = windowMs ?? participant.maxSessionMs ?? sessionMs;
|
|
67
|
+
const countedMs = windowMs ?? Math.min(budgetMs, RATES.openSessionMs);
|
|
65
68
|
if (windowMs !== undefined)
|
|
66
69
|
longestWindowMs = Math.max(longestWindowMs, windowMs);
|
|
67
|
-
|
|
70
|
+
else
|
|
71
|
+
openMs = Math.max(openMs, countedMs);
|
|
72
|
+
turns += Math.ceil(countedMs / (RATES.secondsPerTurn * 1000));
|
|
68
73
|
const dwell = participant.limits.dwell;
|
|
69
74
|
if (dwell !== undefined)
|
|
70
|
-
frames += Math.floor(Math.min(dwell.ms,
|
|
75
|
+
frames += Math.floor(Math.min(dwell.ms, budgetMs) / dwell.everyMs) + 1;
|
|
71
76
|
}
|
|
72
77
|
const count = participants.length;
|
|
73
78
|
const runFileBytes = RATES.runBytes +
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { StudyParseFailure, StudyParticipantFile } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Where `participants[].files` act: the hosted desktop of an independent computer-use participant.
|
|
4
|
+
* The parser's unread-field check and the route planners give this reason.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DESKTOP_FILES_ROUTE_REASON = "files are copied onto a hosted computer-use desktop; needs route: computer-use with execution.target: e2b-desktop";
|
|
7
|
+
/** A participant's `files` list: each entry's `from` and `as`. */
|
|
8
|
+
export declare function parseParticipantFiles(raw: unknown, field: string): {
|
|
9
|
+
ok: true;
|
|
10
|
+
value: StudyParticipantFile[] | undefined;
|
|
11
|
+
} | StudyParseFailure;
|
|
12
|
+
/** One file read on this machine for a participant's desktop. */
|
|
13
|
+
export interface DesktopFile {
|
|
14
|
+
/** Where it lands, relative to the desktop user's home folder. */
|
|
15
|
+
readonly as: string;
|
|
16
|
+
/** The file's bytes, shared by every participant that receives it and uploaded as they are. */
|
|
17
|
+
readonly data: ArrayBuffer;
|
|
18
|
+
readonly size: number;
|
|
19
|
+
/** The sha256 of `data`, 64 hex characters. */
|
|
20
|
+
readonly sha256: string;
|
|
21
|
+
}
|
|
22
|
+
type DesktopFilesRead = {
|
|
23
|
+
readonly ok: true;
|
|
24
|
+
readonly files: ReadonlyMap<string, readonly DesktopFile[]>;
|
|
25
|
+
} | {
|
|
26
|
+
readonly ok: false;
|
|
27
|
+
readonly message: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Read every participant's declared files from `folder`, the folder that holds the study file, and
|
|
31
|
+
* return them by participant id. Runs before any sandbox exists, so a refusal costs nothing.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readDesktopFiles(folder: string, participants: readonly {
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly files?: readonly StudyParticipantFile[];
|
|
36
|
+
}[]): Promise<DesktopFilesRead>;
|
|
37
|
+
export {};
|