humanish 0.126.0 → 0.127.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 +13 -0
- package/dist/actors/computer-use/loop/ending.d.ts +3 -1
- package/dist/actors/computer-use/loop/ending.js +5 -3
- package/dist/actors/computer-use/openai-provider.js +1 -1
- package/dist/actors/goal-source.d.ts +32 -2
- package/dist/actors/goal-source.js +41 -2
- package/dist/analysis/evidence-sources.js +2 -2
- package/dist/analysis/types.d.ts +2 -2
- package/dist/analysis/validation.js +2 -1
- package/dist/observer/data.js +8 -5
- package/dist/observer-app.html +1 -1
- package/dist/routes/computer-use/fanout-bundle.js +3 -0
- package/dist/routes/computer-use/fanout-records.js +1 -1
- package/dist/routes/computer-use/participant-execution.js +6 -2
- package/dist/routes/computer-use/participant-facts.js +4 -1
- package/dist/routes/computer-use/participant-runs.d.ts +9 -9
- package/dist/routes/computer-use/participant-runs.js +52 -86
- package/dist/routes/computer-use/plan.d.ts +2 -0
- package/dist/routes/computer-use/plan.js +26 -16
- package/dist/routes/computer-use/rerun-selection.js +8 -5
- package/dist/routes/computer-use/setup.js +1 -1
- package/dist/routes/computer-use/single-bundle.js +9 -1
- package/dist/routes/computer-use/types.d.ts +2 -3
- package/dist/routes/computer-use/types.js +0 -3
- package/dist/routes/shared-world/bundle-records.js +1 -1
- package/dist/routes/shared-world/external-public.js +1 -1
- package/dist/routes/shared-world/plan.d.ts +2 -0
- package/dist/routes/shared-world/plan.js +3 -3
- package/dist/routes/shared-world/provisioned.js +1 -1
- package/dist/run/bundle.d.ts +11 -1
- package/dist/run/judge.d.ts +7 -0
- package/dist/run/judge.js +4 -4
- package/dist/run/notes.js +1 -1
- package/dist/run/outcomes.d.ts +8 -2
- package/dist/run/outcomes.js +56 -14
- package/dist/study/arrivals.d.ts +84 -31
- package/dist/study/arrivals.js +165 -86
- package/dist/study/bundle-estimate.d.ts +8 -7
- package/dist/study/bundle-estimate.js +131 -70
- package/dist/study/desktop-files.js +4 -4
- package/dist/study/plan-participants.d.ts +4 -9
- package/dist/study/plan-participants.js +2 -14
- package/dist/study/plan-types.d.ts +6 -1
- package/dist/study/plan.d.ts +2 -1
- package/dist/study/plan.js +6 -4
- package/dist/study/types.d.ts +2 -1
- package/dist/substrates/e2b/lifetime.d.ts +22 -8
- package/dist/substrates/e2b/lifetime.js +54 -10
- package/docs/contracts/run-bundle.md +1 -1
- package/docs/contracts/schemas.md +24 -11
- package/package.json +1 -1
package/dist/study/arrivals.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// When each participant of a computer-use or shared-world run starts, and on computer use when it
|
|
2
2
|
// ends. A study may give each participant an offset from the moment the run starts its participants
|
|
3
3
|
// (`participants[].startAfterMs`) and, on computer use, an end on the same clock
|
|
4
|
-
// (`participants[].endAfterMs`)
|
|
5
|
-
// participant the time left (`windDownMs`).
|
|
6
|
-
//
|
|
4
|
+
// (`participants[].endAfterMs`) or a cap on its own session (`maxSessionMs`), with a wind-down
|
|
5
|
+
// before either in which each turn tells the participant the time left (`windDownMs`). Each
|
|
6
|
+
// participant's ParticipantSchedule resolves those fields once; everything that reads a
|
|
7
|
+
// participant's time reads the schedule. The plan simulates the start queue to report the schedule
|
|
8
|
+
// and when the last session ends at the latest; the routes start their participants through
|
|
7
9
|
// runOnSchedule, which waits for each one's time and then for a free slot. A participant's desktop
|
|
8
10
|
// is created when it starts, so a late start holds no sandbox before then.
|
|
9
11
|
import { setTimeout as delay } from "node:timers/promises";
|
|
@@ -22,8 +24,8 @@ export function isStartAfterMs(value) {
|
|
|
22
24
|
value >= 0 &&
|
|
23
25
|
value <= LATEST_START_AFTER_MS);
|
|
24
26
|
}
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
+
/** The longest default wind-down: 10 minutes is enough to wrap up what a participant is doing. */
|
|
28
|
+
const MAX_DEFAULT_WIND_DOWN_MS = 10 * 60_000;
|
|
27
29
|
/**
|
|
28
30
|
* Room a scheduled participant's session keeps past its end for the turn in flight and the closing
|
|
29
31
|
* report. The loop ends the session at the end; this keeps the session's own time limit, and the
|
|
@@ -58,79 +60,120 @@ export function windowReason(entry, path) {
|
|
|
58
60
|
return undefined;
|
|
59
61
|
}
|
|
60
62
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* or its default); else `sessionMs`.
|
|
63
|
+
* The schedule of a participants entry that windowReason accepts. A cap wins over an end, which
|
|
64
|
+
* windowReason refuses beside it anyway.
|
|
64
65
|
*/
|
|
65
|
-
export function
|
|
66
|
-
const
|
|
67
|
-
|
|
66
|
+
export function participantSchedule(entry) {
|
|
67
|
+
const start = entry.startAfterMs === undefined ? {} : { startAfterMs: entry.startAfterMs };
|
|
68
|
+
if (entry.maxSessionMs !== undefined)
|
|
69
|
+
return {
|
|
70
|
+
...start,
|
|
71
|
+
end: { kind: "session_cap", ...endTiming(entry.maxSessionMs, entry.windDownMs) },
|
|
72
|
+
};
|
|
73
|
+
if (entry.endAfterMs === undefined)
|
|
74
|
+
return start;
|
|
75
|
+
const windowMs = entry.endAfterMs - (entry.startAfterMs ?? 0);
|
|
76
|
+
return {
|
|
77
|
+
...start,
|
|
78
|
+
end: {
|
|
79
|
+
kind: "scheduled_end",
|
|
80
|
+
endAfterMs: entry.endAfterMs,
|
|
81
|
+
...endTiming(windowMs, entry.windDownMs),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
68
84
|
}
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
/**
|
|
86
|
+
* A window's wind-down and session budget. Without a declared `windDownMs` the wind-down is a
|
|
87
|
+
* quarter of the window, at most 10 minutes. A fixed 10 minutes covered the whole of a window of
|
|
88
|
+
* 10 minutes or less, so a short session was told its end from the first turn. A quarter leaves
|
|
89
|
+
* most of a short session without a countdown, and a window of 40 minutes or more keeps the 10
|
|
90
|
+
* minutes.
|
|
91
|
+
*/
|
|
92
|
+
function endTiming(windowMs, windDownMs) {
|
|
93
|
+
return {
|
|
94
|
+
windowMs,
|
|
95
|
+
windDownMs: windDownMs ?? Math.min(MAX_DEFAULT_WIND_DOWN_MS, Math.floor(windowMs / 4)),
|
|
96
|
+
sessionMs: windowMs + SCHEDULED_END_ROOM_MS,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The schedule a rerun gives a participant: it starts with the rerun, and a scheduled end keeps its
|
|
101
|
+
* time on the rerun's clock and its wind-down, so its window runs from the rerun's start.
|
|
102
|
+
*/
|
|
103
|
+
export function startingWithTheRun(schedule) {
|
|
104
|
+
const { end } = schedule;
|
|
105
|
+
if (end === undefined)
|
|
106
|
+
return {};
|
|
107
|
+
if (end.kind === "session_cap")
|
|
108
|
+
return { end };
|
|
109
|
+
return participantSchedule({ endAfterMs: end.endAfterMs, windDownMs: end.windDownMs });
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A participant's own session budget and sandbox deadline. With an end or a cap it is the end's
|
|
113
|
+
* `sessionMs`, and the sandbox keeps the study sandbox's headroom over the study session on top;
|
|
114
|
+
* without one, the study's own.
|
|
115
|
+
*/
|
|
116
|
+
export function participantSession(schedule, study) {
|
|
117
|
+
const sessionMs = schedule.end?.sessionMs ?? study.sessionMs;
|
|
118
|
+
return { sessionMs, sandboxMs: study.sandboxMs - study.sessionMs + sessionMs };
|
|
76
119
|
}
|
|
77
120
|
/**
|
|
78
121
|
* How a participant that was due at `scheduledAt` (runOnSchedule's ScheduledStart) ends, or
|
|
79
|
-
* undefined
|
|
80
|
-
*
|
|
122
|
+
* undefined without an end or a cap. A scheduled end comes its window after the participant's
|
|
123
|
+
* time, which is its `endAfterMs` on the run's clock, even when it waited for a slot; a cap is
|
|
124
|
+
* whole from wherever its session starts.
|
|
81
125
|
*/
|
|
82
|
-
export function participantEnd(
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
-
return { kind: "session_cap", afterMs: participant.maxSessionMs, windDownMs };
|
|
86
|
-
if (participant.endAfterMs === undefined)
|
|
126
|
+
export function participantEnd(schedule, scheduledAt) {
|
|
127
|
+
const { end } = schedule;
|
|
128
|
+
if (end === undefined)
|
|
87
129
|
return undefined;
|
|
88
|
-
|
|
89
|
-
kind: "
|
|
90
|
-
|
|
91
|
-
windDownMs,
|
|
92
|
-
};
|
|
130
|
+
if (end.kind === "session_cap")
|
|
131
|
+
return { kind: "session_cap", afterMs: end.windowMs, windDownMs: end.windDownMs };
|
|
132
|
+
return { kind: "scheduled_end", atMs: scheduledAt + end.windowMs, windDownMs: end.windDownMs };
|
|
93
133
|
}
|
|
94
134
|
/**
|
|
95
135
|
* Why the sandbox of the first participant whose window or cap passes `ceilingMs` cannot live that
|
|
96
|
-
* long, or undefined. Its deadline is its
|
|
97
|
-
*
|
|
136
|
+
* long, or undefined. Its deadline is its participantSession's, from the study's session budget and
|
|
137
|
+
* sandbox deadline.
|
|
98
138
|
*/
|
|
99
|
-
export function windowDeadlineRefusal(participants,
|
|
139
|
+
export function windowDeadlineRefusal(participants, study, ceilingMs) {
|
|
100
140
|
const inMinutes = (ms) => Math.round(ms / 60_000);
|
|
101
|
-
for (const
|
|
102
|
-
|
|
141
|
+
for (const { id, schedule } of participants) {
|
|
142
|
+
const { end } = schedule;
|
|
143
|
+
if (end === undefined)
|
|
103
144
|
continue;
|
|
104
|
-
const deadlineMs =
|
|
145
|
+
const deadlineMs = participantSession(schedule, study).sandboxMs;
|
|
105
146
|
if (deadlineMs <= ceilingMs)
|
|
106
147
|
continue;
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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)}`;
|
|
148
|
+
const works = end.kind === "scheduled_end"
|
|
149
|
+
? `works from +${formatDuration(schedule.startAfterMs ?? 0)} to +${formatDuration(end.endAfterMs)}`
|
|
150
|
+
: `may work for up to ${formatDuration(end.windowMs)}`;
|
|
151
|
+
const window = end.kind === "scheduled_end" ? "window" : "cap";
|
|
152
|
+
return `Participant "${id}" ${works}, so its sandbox needs a ${inMinutes(deadlineMs)}m deadline: the ${window}, ${inMinutes(SCHEDULED_END_ROOM_MS)}m for its closing report and ${inMinutes(study.sandboxMs - study.sessionMs)}m to set up and tear down. A sandbox may not live longer than ${ceilingMs / 60_000}m. Shorten its ${window}.${ceilingAdvice(deadlineMs)}`;
|
|
113
153
|
}
|
|
114
154
|
return undefined;
|
|
115
155
|
}
|
|
116
156
|
/**
|
|
117
157
|
* Each participant's window for `study check`, when any participant declares an end or a cap: when
|
|
118
158
|
* 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
|
|
159
|
+
* far into its session it is first told the time left, or when it starts and the study's
|
|
160
|
+
* `sessionMs`.
|
|
120
161
|
*/
|
|
121
162
|
export function describeWindows(participants, sessionMs) {
|
|
122
|
-
if (
|
|
163
|
+
if (participants.every((participant) => participant.schedule.end === undefined))
|
|
123
164
|
return undefined;
|
|
124
|
-
const windows = participants.map((
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
if (participant.endAfterMs === undefined)
|
|
165
|
+
const windows = participants.map(({ id, schedule }) => {
|
|
166
|
+
const startAfterMs = schedule.startAfterMs ?? 0;
|
|
167
|
+
const start = `${id} from +${formatDuration(startAfterMs)}`;
|
|
168
|
+
const { end } = schedule;
|
|
169
|
+
if (end === undefined)
|
|
131
170
|
return `${start} for up to ${formatDuration(sessionMs)}`;
|
|
132
|
-
|
|
133
|
-
|
|
171
|
+
if (end.kind === "session_cap") {
|
|
172
|
+
const told = Math.max(0, end.windowMs - end.windDownMs);
|
|
173
|
+
return `${start} until it ends itself or for up to ${formatDuration(end.windowMs)}, told the time left from ${formatDuration(told)} into its session`;
|
|
174
|
+
}
|
|
175
|
+
const told = Math.max(startAfterMs, end.endAfterMs - end.windDownMs);
|
|
176
|
+
return `${start} to +${formatDuration(end.endAfterMs)}, told the time left from +${formatDuration(told)}`;
|
|
134
177
|
});
|
|
135
178
|
return `${windows.join("; ")}.`;
|
|
136
179
|
}
|
|
@@ -152,34 +195,42 @@ export function rosterStartsReason(roster) {
|
|
|
152
195
|
return undefined;
|
|
153
196
|
}
|
|
154
197
|
/**
|
|
155
|
-
* The schedule a roster's
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
198
|
+
* The schedule a roster's participants give under the queue. Participants start in schedule order,
|
|
199
|
+
* by offset and then roster position, each at its time or when a slot frees, whichever is later;
|
|
200
|
+
* this is the rule runOnSchedule follows. A participant with an end or a cap holds its slot for its
|
|
201
|
+
* own session budget, and the others for `queue.sessionMs`. A participant whose slot would free
|
|
202
|
+
* only at or after its scheduled end takes none: the run skips it (pastEndReason).
|
|
160
203
|
*/
|
|
161
|
-
export function planArrivals(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
204
|
+
export function planArrivals(schedules, queue) {
|
|
205
|
+
return simulateQueue(schedules, queue).arrivals;
|
|
206
|
+
}
|
|
207
|
+
/** planArrivals, and the roster indexes of the participants the run would skip. */
|
|
208
|
+
function simulateQueue(schedules, queue) {
|
|
209
|
+
const due = schedules.map((schedule) => schedule.startAfterMs ?? 0);
|
|
210
|
+
const sessionsMs = schedules.map((schedule) => schedule.end?.sessionMs ?? queue.sessionMs);
|
|
211
|
+
const session = (index) => sessionsMs[index];
|
|
212
|
+
const { starts, skipped } = queueStarts(schedules, due, queue, session);
|
|
213
|
+
const skips = new Set(skipped);
|
|
214
|
+
const started = starts.flatMap((_, index) => (skips.has(index) ? [] : [index]));
|
|
165
215
|
let waiting = 0;
|
|
166
216
|
let longestWaitMs = 0;
|
|
167
|
-
for (const
|
|
168
|
-
const waitMs =
|
|
217
|
+
for (const index of started) {
|
|
218
|
+
const waitMs = starts[index] - due[index];
|
|
169
219
|
if (waitMs > 0)
|
|
170
220
|
waiting += 1;
|
|
171
221
|
longestWaitMs = Math.max(longestWaitMs, waitMs);
|
|
172
222
|
}
|
|
173
|
-
|
|
174
|
-
declared:
|
|
223
|
+
const arrivals = {
|
|
224
|
+
declared: schedules.some((schedule) => schedule.startAfterMs !== undefined),
|
|
175
225
|
firstStartMs: Math.min(...due),
|
|
176
226
|
lastStartMs: Math.max(...due),
|
|
177
|
-
peak: mostAtOnce(starts, session),
|
|
227
|
+
peak: mostAtOnce(started.map((index) => [starts[index], session(index)])),
|
|
178
228
|
waiting,
|
|
179
229
|
longestWaitMs,
|
|
180
|
-
lastEndMs: Math.max(...
|
|
230
|
+
lastEndMs: Math.max(...started.map((index) => starts[index] + session(index))),
|
|
181
231
|
sessionMs: queue.sessionMs,
|
|
182
232
|
};
|
|
233
|
+
return { arrivals, skipped };
|
|
183
234
|
}
|
|
184
235
|
/**
|
|
185
236
|
* The plan's schedule, after a "schedule:" label: the first and last start, and how many run at
|
|
@@ -195,21 +246,43 @@ export function describeArrivals(arrivals) {
|
|
|
195
246
|
* A planner's arrival plan for its participants under the start queue, and the warnings the plan
|
|
196
247
|
* records about it. A participant's desktop is created when it starts, so on computer use a late
|
|
197
248
|
* start adds nothing to any sandbox's deadline; the provisioned shared world checks its app's
|
|
198
|
-
* sandbox with servedScheduleRefusal. `lowered` is
|
|
199
|
-
* roster in waves.
|
|
249
|
+
* sandbox with servedScheduleRefusal. `lowered` is participantsAtOnce's warning that the E2B plan's
|
|
250
|
+
* limit or HUMANISH_CUA_MAX_CONCURRENCY runs the roster in waves.
|
|
200
251
|
*/
|
|
201
252
|
export function planStarts(participants, queue, lowered) {
|
|
202
|
-
const arrivals =
|
|
203
|
-
|
|
253
|
+
const { arrivals, skipped } = simulateQueue(participants.map((participant) => participant.schedule), queue);
|
|
254
|
+
const ids = skipped.map((index) => participants[index].id);
|
|
255
|
+
return { arrivals, warnings: startQueueWarnings(arrivals, ids, queue, lowered) };
|
|
204
256
|
}
|
|
205
257
|
// A roster that starts together keeps `lowered`. A declared schedule spreads its participants
|
|
206
|
-
// itself, so it is warned only when it holds someone past their time
|
|
207
|
-
// when the limit is the reason.
|
|
208
|
-
function startQueueWarnings(arrivals, queue, lowered) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const late = `${plural(arrivals.waiting, "participant")} would start after their time
|
|
212
|
-
|
|
258
|
+
// itself, so it is warned only when it holds someone past their time or skips someone, with
|
|
259
|
+
// `lowered` beside that when the limit is the reason.
|
|
260
|
+
function startQueueWarnings(arrivals, skipped, queue, lowered) {
|
|
261
|
+
const budget = `when every session uses its ${formatDuration(arrivals.sessionMs)} budget`;
|
|
262
|
+
const atMost = `the run starts at most ${queue.slots} at once`;
|
|
263
|
+
const late = `${plural(arrivals.waiting, "participant")} would start after their time ${budget}, the latest by ${formatDuration(arrivals.longestWaitMs)}: ${atMost} and holds the next one until a slot frees.`;
|
|
264
|
+
const one = skipped.length === 1;
|
|
265
|
+
const names = skipped.map((id) => `"${id}"`);
|
|
266
|
+
const who = one
|
|
267
|
+
? `Participant ${names[0]}`
|
|
268
|
+
: `${skipped.length} participants (${names.join(", ")})`;
|
|
269
|
+
const skip = `${who} would still be waiting for a free slot at ${one ? "its" : "their"} end (participants[].endAfterMs) ${budget}, so the run would skip ${one ? "it without creating its desktop" : "them without creating their desktops"}: ${atMost}. Let more participants run at once, or give ${one ? "it" : "them"} a later end.`;
|
|
270
|
+
const held = arrivals.waiting > 0 || skipped.length > 0;
|
|
271
|
+
return [
|
|
272
|
+
...(arrivals.declared && arrivals.waiting > 0 ? [late] : []),
|
|
273
|
+
...(skipped.length > 0 ? [skip] : []),
|
|
274
|
+
...(lowered !== undefined && (!arrivals.declared || held) ? [lowered] : []),
|
|
275
|
+
];
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Why a participant whose start comes at `nowMs` is skipped, or undefined: its scheduled end came
|
|
279
|
+
* while it waited, so its session would end before it began. The plan's simulation applies the
|
|
280
|
+
* same rule (planArrivals).
|
|
281
|
+
*/
|
|
282
|
+
export function pastEndReason(end, nowMs) {
|
|
283
|
+
if (end?.kind !== "scheduled_end" || nowMs < end.atMs)
|
|
284
|
+
return undefined;
|
|
285
|
+
return "skipped: its scheduled end (participants[].endAfterMs) came while it waited to start, so its session would have ended before it began; no desktop was created";
|
|
213
286
|
}
|
|
214
287
|
/**
|
|
215
288
|
* Why a sandbox that serves the app until the last participant ends, the provisioned shared
|
|
@@ -243,8 +316,9 @@ function scheduleOrder(due) {
|
|
|
243
316
|
return due.map((_, index) => index).sort((a, b) => due[a] - due[b] || a - b);
|
|
244
317
|
}
|
|
245
318
|
/** Each participant's start in roster order, when each session holds its slot for `session(index)`. */
|
|
246
|
-
function queueStarts(due, queue, session) {
|
|
319
|
+
function queueStarts(schedules, due, queue, session) {
|
|
247
320
|
const starts = [...due];
|
|
321
|
+
const skipped = [];
|
|
248
322
|
const queued = scheduleOrder(due).filter((index) => index !== queue.ownSlot);
|
|
249
323
|
const pooled = queue.ownSlot === undefined ? queue.slots : queue.slots - 1;
|
|
250
324
|
// When each slot is next free; a participant takes the one that frees first.
|
|
@@ -253,16 +327,21 @@ function queueStarts(due, queue, session) {
|
|
|
253
327
|
const slot = free.indexOf(Math.min(...free));
|
|
254
328
|
const start = Math.max(due[index], free[slot]);
|
|
255
329
|
starts[index] = start;
|
|
256
|
-
|
|
330
|
+
const end = schedules[index]?.end;
|
|
331
|
+
if (end?.kind === "scheduled_end" && start >= end.endAfterMs)
|
|
332
|
+
skipped.push(index);
|
|
333
|
+
else
|
|
334
|
+
free[slot] = start + session(index);
|
|
257
335
|
}
|
|
258
|
-
return starts;
|
|
336
|
+
return { starts, skipped };
|
|
259
337
|
}
|
|
260
|
-
|
|
338
|
+
/** The most sessions running at once, each given as its start and how long it holds its slot. */
|
|
339
|
+
function mostAtOnce(sessions) {
|
|
261
340
|
// An end sorts before a start at the same instant: that slot is free again.
|
|
262
|
-
const changes =
|
|
263
|
-
.flatMap((start,
|
|
341
|
+
const changes = sessions
|
|
342
|
+
.flatMap(([start, ms]) => [
|
|
264
343
|
{ at: start, by: 1 },
|
|
265
|
-
{ at: start +
|
|
344
|
+
{ at: start + ms, by: -1 },
|
|
266
345
|
])
|
|
267
346
|
.sort((a, b) => a.at - b.at || a.by - b.by);
|
|
268
347
|
let live = 0;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { DwellWindow } from "../actors/stop-conditions.js";
|
|
2
|
-
|
|
2
|
+
import type { ParticipantSchedule } from "./arrivals.js";
|
|
3
|
+
/** One participant as the plan has it: its schedule and its dwell window. */
|
|
3
4
|
export interface EstimatedParticipant {
|
|
4
|
-
readonly
|
|
5
|
-
readonly endAfterMs?: number;
|
|
6
|
-
readonly maxSessionMs?: number;
|
|
5
|
+
readonly schedule: ParticipantSchedule;
|
|
7
6
|
readonly limits: {
|
|
8
7
|
readonly dwell?: DwellWindow;
|
|
9
8
|
};
|
|
@@ -12,8 +11,10 @@ export interface EstimatedParticipant {
|
|
|
12
11
|
export interface BundleEstimate {
|
|
13
12
|
/** run.json, which analysis reads whole. */
|
|
14
13
|
readonly runFileBytes: number;
|
|
15
|
-
/** The largest file
|
|
16
|
-
readonly
|
|
14
|
+
/** The largest participant trace file, which analysis also reads whole. */
|
|
15
|
+
readonly largestTraceBytes: number;
|
|
16
|
+
/** observer/index.html, which verify reads whole. */
|
|
17
|
+
readonly observerPageBytes: number;
|
|
17
18
|
/** Every file, screenshots included. */
|
|
18
19
|
readonly totalBytes: number;
|
|
19
20
|
/** Files and folders in the run folder, as the public-safety scan counts them. */
|
|
@@ -42,7 +43,7 @@ export interface BundleEstimate {
|
|
|
42
43
|
export declare function estimateBundle(participants: readonly EstimatedParticipant[], sessionMs: number): BundleEstimate;
|
|
43
44
|
/**
|
|
44
45
|
* The warning for a run whose estimate passes a limit, or undefined: each limit it passes, what the
|
|
45
|
-
* estimate counted,
|
|
46
|
+
* estimate counted, and what to change.
|
|
46
47
|
*/
|
|
47
48
|
export declare function bundleLimitReason(estimate: BundleEstimate): string | undefined;
|
|
48
49
|
/** The estimate beside the limits, for `study check`. */
|