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.
Files changed (51) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/actors/computer-use/loop/ending.d.ts +3 -1
  3. package/dist/actors/computer-use/loop/ending.js +5 -3
  4. package/dist/actors/computer-use/openai-provider.js +1 -1
  5. package/dist/actors/goal-source.d.ts +32 -2
  6. package/dist/actors/goal-source.js +41 -2
  7. package/dist/analysis/evidence-sources.js +2 -2
  8. package/dist/analysis/types.d.ts +2 -2
  9. package/dist/analysis/validation.js +2 -1
  10. package/dist/observer/data.js +8 -5
  11. package/dist/observer-app.html +1 -1
  12. package/dist/routes/computer-use/fanout-bundle.js +3 -0
  13. package/dist/routes/computer-use/fanout-records.js +1 -1
  14. package/dist/routes/computer-use/participant-execution.js +6 -2
  15. package/dist/routes/computer-use/participant-facts.js +4 -1
  16. package/dist/routes/computer-use/participant-runs.d.ts +9 -9
  17. package/dist/routes/computer-use/participant-runs.js +52 -86
  18. package/dist/routes/computer-use/plan.d.ts +2 -0
  19. package/dist/routes/computer-use/plan.js +26 -16
  20. package/dist/routes/computer-use/rerun-selection.js +8 -5
  21. package/dist/routes/computer-use/setup.js +1 -1
  22. package/dist/routes/computer-use/single-bundle.js +9 -1
  23. package/dist/routes/computer-use/types.d.ts +2 -3
  24. package/dist/routes/computer-use/types.js +0 -3
  25. package/dist/routes/shared-world/bundle-records.js +1 -1
  26. package/dist/routes/shared-world/external-public.js +1 -1
  27. package/dist/routes/shared-world/plan.d.ts +2 -0
  28. package/dist/routes/shared-world/plan.js +3 -3
  29. package/dist/routes/shared-world/provisioned.js +1 -1
  30. package/dist/run/bundle.d.ts +11 -1
  31. package/dist/run/judge.d.ts +7 -0
  32. package/dist/run/judge.js +4 -4
  33. package/dist/run/notes.js +1 -1
  34. package/dist/run/outcomes.d.ts +8 -2
  35. package/dist/run/outcomes.js +56 -14
  36. package/dist/study/arrivals.d.ts +84 -31
  37. package/dist/study/arrivals.js +165 -86
  38. package/dist/study/bundle-estimate.d.ts +8 -7
  39. package/dist/study/bundle-estimate.js +131 -70
  40. package/dist/study/desktop-files.js +4 -4
  41. package/dist/study/plan-participants.d.ts +4 -9
  42. package/dist/study/plan-participants.js +2 -14
  43. package/dist/study/plan-types.d.ts +6 -1
  44. package/dist/study/plan.d.ts +2 -1
  45. package/dist/study/plan.js +6 -4
  46. package/dist/study/types.d.ts +2 -1
  47. package/dist/substrates/e2b/lifetime.d.ts +22 -8
  48. package/dist/substrates/e2b/lifetime.js +54 -10
  49. package/docs/contracts/run-bundle.md +1 -1
  50. package/docs/contracts/schemas.md +24 -11
  51. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import type { ActorCompletionReason, ActorStatus, ParticipantDeclaredOutcome } from "../actors/contract.js";
2
+ import type { ClockEnd } from "../actors/goal-source.js";
2
3
  import type { ReviewSummary } from "./bundle.js";
3
4
  export type Verdict = ReviewSummary["verdict"];
4
5
  /** How a finished session ended, as the blocker rule reads it. */
@@ -43,6 +44,12 @@ export interface ParticipantFacts {
43
44
  noEngagement: boolean;
44
45
  /** selfReportedBlocker held for its session. */
45
46
  selfReportedBlocker: boolean;
47
+ /**
48
+ * Its session ran until its scheduled end or session cap without the participant declaring it
49
+ * reached the goal (ranUntilClockEnd). A pass counts under `ranUntilEnd` in place of
50
+ * `reachedGoal`.
51
+ */
52
+ ranUntil?: ClockEnd;
46
53
  }
47
54
  /** The participant facts its bundle and result can name. */
48
55
  export interface ParticipantRecordFacts extends ParticipantFacts {
package/dist/run/judge.js CHANGED
@@ -68,12 +68,12 @@ export function judgeParticipantRecords(participants, wording = {}) {
68
68
  : `${participant.id ?? "Participant"}: ${participant.skippedReason ?? participant.sessionError ?? participant.reason ?? "did not pass"}`,
69
69
  };
70
70
  });
71
- const terminal = records.filter((_, index) => participants[index].status !== undefined && !participants[index].inProgress);
71
+ const ended = (participant) => participant.status !== undefined && !participant.inProgress;
72
+ const terminal = records.filter((_, index) => ended(participants[index]));
73
+ const terminalFacts = participants.filter(ended);
72
74
  return {
73
75
  participants: records,
74
- tally: tallyParticipantOutcomes(terminal.map((participant) => participant.judgedStatus), participants
75
- .filter((participant) => participant.status !== undefined && !participant.inProgress)
76
- .map((participant) => participant.reportedFriction === true)),
76
+ tally: tallyParticipantOutcomes(terminal.map((participant) => participant.judgedStatus), terminalFacts.map((participant) => participant.reportedFriction === true), terminalFacts.map((participant) => participant.ranUntil !== undefined)),
77
77
  };
78
78
  }
79
79
  function participantNotPassedMessage(participant, wording) {
package/dist/run/notes.js CHANGED
@@ -21,7 +21,7 @@ const refuse = (code, message) => ({
21
21
  });
22
22
  /**
23
23
  * The stream id a note's participant names: a stream id, or the participant's own id from the study
24
- * (`charge-nurse`). Both kinds of id are matched, so a name that is one stream's id and another
24
+ * (`support-lead`). Both kinds of id are matched, so a name that is one stream's id and another
25
25
  * stream's study id is refused, naming both, as is a study id that two streams record. Otherwise
26
26
  * the refusal names the run's participants by caption, with the id to type for each.
27
27
  */
@@ -6,7 +6,13 @@ import type { ParticipantOutcomes, ReviewSummary, RunTaskFunnel } from "./bundle
6
6
  * `total` but nowhere else, so the parts can never exceed the whole. */
7
7
  export declare function tallyParticipantOutcomes(statuses: readonly ActorStatus[],
8
8
  /** Per-participant: did this one report friction or a defect? Same order as `statuses`. */
9
- reportedFriction?: readonly boolean[]): ParticipantOutcomes;
9
+ reportedFriction?: readonly boolean[],
10
+ /**
11
+ * Per-participant: did its session run until its scheduled end or session cap without it
12
+ * declaring that it reached the goal (ranUntilClockEnd)? Same order as `statuses`. Such a pass
13
+ * counts under `ranUntilEnd` in place of `reachedGoal`.
14
+ */
15
+ ranUntilEnd?: readonly boolean[]): ParticipantOutcomes;
10
16
  /** Roll per-participant funnels up into the study funnel. Undefined when nothing measured one. */
11
17
  export declare function aggregateTaskFunnels(funnels: readonly TaskFunnel[]): RunTaskFunnel | undefined;
12
18
  /** The funnel as one line, denominator on every number: `signup 2/2 · verify-email 1/2`. */
@@ -53,7 +59,7 @@ export declare function formatParticipantOutcomes(outcomes: ParticipantOutcomes,
53
59
  export declare function participantOutcomeDetails(streams: readonly {
54
60
  actor?: unknown;
55
61
  status?: unknown;
56
- }[]): ParticipantOutcomeDetail[];
62
+ }[], goalSourceOf?: (actor: unknown, recordedStatus: unknown) => CuaGoalSource | undefined): ParticipantOutcomeDetail[];
57
63
  /** Refresh a CUA completion claim from recorded traces, leaving original evidence and enums intact. */
58
64
  export declare function withCuaReviewProvenance(review: ReviewSummary, streams: readonly {
59
65
  actor?: unknown;
@@ -1,13 +1,19 @@
1
1
  import { ACTOR_STATUSES } from "../actors/contract.js";
2
- import { cuaGoalSource, isCuaTrace, CUA_COMPLETION_NOTE, } from "../actors/goal-source.js";
3
- import { actorEnding } from "../actors/stop-cause.js";
2
+ import { cuaEndpointSource, cuaGoalSource, isClockEnd, isCuaTrace, CUA_COMPLETION_NOTE, } from "../actors/goal-source.js";
3
+ import { actorEnding, actorStopCauseLabel } from "../actors/stop-cause.js";
4
4
  import { plural } from "./text.js";
5
5
  import { isNonNegativeSafeInteger, isRecord } from "./type-guards.js";
6
6
  /** Tally participant outcomes from actor statuses. Statuses this does not recognise are counted in
7
7
  * `total` but nowhere else, so the parts can never exceed the whole. */
8
8
  export function tallyParticipantOutcomes(statuses,
9
9
  /** Per-participant: did this one report friction or a defect? Same order as `statuses`. */
10
- reportedFriction = []) {
10
+ reportedFriction = [],
11
+ /**
12
+ * Per-participant: did its session run until its scheduled end or session cap without it
13
+ * declaring that it reached the goal (ranUntilClockEnd)? Same order as `statuses`. Such a pass
14
+ * counts under `ranUntilEnd` in place of `reachedGoal`.
15
+ */
16
+ ranUntilEnd = []) {
11
17
  const tally = {
12
18
  total: statuses.length,
13
19
  reachedGoal: 0,
@@ -17,8 +23,11 @@ reportedFriction = []) {
17
23
  harnessFailed: 0,
18
24
  reportedFriction: reportedFriction.filter(Boolean).length,
19
25
  };
20
- for (const status of statuses) {
21
- if (status === "passed")
26
+ let clockEnds = 0;
27
+ statuses.forEach((status, index) => {
28
+ if (status === "passed" && ranUntilEnd[index] === true)
29
+ clockEnds += 1;
30
+ else if (status === "passed")
22
31
  tally.reachedGoal += 1;
23
32
  else if (status === "abandoned")
24
33
  tally.abandoned += 1;
@@ -28,8 +37,8 @@ reportedFriction = []) {
28
37
  tally.blocked += 1;
29
38
  else if (status === "failed")
30
39
  tally.harnessFailed += 1;
31
- }
32
- return tally;
40
+ });
41
+ return clockEnds === 0 ? tally : { ...tally, ranUntilEnd: clockEnds };
33
42
  }
34
43
  /** Roll per-participant funnels up into the study funnel. Undefined when nothing measured one. */
35
44
  export function aggregateTaskFunnels(funnels) {
@@ -86,15 +95,38 @@ export function formatRunTaskFunnel(funnel) {
86
95
  })
87
96
  .join(" · ");
88
97
  }
98
+ /**
99
+ * "1 ran until its session cap", "2 ran until their scheduled end", grouped by clock end, most
100
+ * first. Clock ends the details cannot account for get one line that names both.
101
+ */
102
+ function ranUntilPhrases(count, details) {
103
+ const words = (n, end) => n === 1 ? `1 ran until its ${end}` : `${n} ran until their ${end}`;
104
+ const ends = details.flatMap((entry) => (isClockEnd(entry.goalSource) ? [entry.goalSource] : []));
105
+ if (ends.length !== count)
106
+ return [words(count, "scheduled end or session cap")];
107
+ return ["session_cap", "scheduled_end"]
108
+ .map((end) => [end, ends.filter((entry) => entry === end).length])
109
+ .filter(([, n]) => n > 0)
110
+ .sort(([, m], [, n]) => n - m)
111
+ .map(([end, n]) => words(n, actorStopCauseLabel(end)));
112
+ }
89
113
  function participantCompletionLine(outcomes, terminalCauses) {
90
- const completions = terminalCauses.filter((entry) => entry.status === "passed");
114
+ const passed = terminalCauses.filter((entry) => entry.status === "passed");
115
+ // A bundle without `ranUntilEnd` either has no such clock end or predates the count and tallied
116
+ // its clock ends under reachedGoal. Those stay among its completions, and the breakdown names
117
+ // them, so a re-read never changes a recorded count.
118
+ const completions = outcomes.ranUntilEnd === undefined
119
+ ? passed
120
+ : passed.filter((entry) => !isClockEnd(entry.goalSource));
91
121
  const reported = completions.filter((entry) => entry.goalSource === "participant_report").length;
92
122
  const matched = completions.filter((entry) => entry.goalSource === "condition_matched").length;
123
+ const clockEnds = completions.filter((entry) => isClockEnd(entry.goalSource));
93
124
  let goalLine = outcomes.reachedGoal === 0
94
125
  ? `0/${outcomes.total} recorded completions`
95
126
  : `${outcomes.reachedGoal}/${outcomes.total} reached the goal`;
96
127
  if (outcomes.reachedGoal > 0 && terminalCauses.some((entry) => entry.goalSource !== undefined)) {
97
128
  const count = `${outcomes.reachedGoal}/${outcomes.total}`;
129
+ const other = outcomes.reachedGoal - reported - matched - clockEnds.length;
98
130
  goalLine =
99
131
  completions.length !== outcomes.reachedGoal
100
132
  ? `${count} recorded completions (completion source unavailable)`
@@ -105,9 +137,8 @@ function participantCompletionLine(outcomes, terminalCauses) {
105
137
  : `${count} recorded completions (${[
106
138
  reported > 0 ? `${reported} participant-reported` : undefined,
107
139
  matched > 0 ? `${matched} condition-matched` : undefined,
108
- outcomes.reachedGoal - reported - matched > 0
109
- ? `${outcomes.reachedGoal - reported - matched} other or unavailable source`
110
- : undefined,
140
+ ...(clockEnds.length > 0 ? ranUntilPhrases(clockEnds.length, clockEnds) : []),
141
+ other > 0 ? `${other} other or unavailable source` : undefined,
111
142
  ]
112
143
  .filter(Boolean)
113
144
  .join(", ")})`;
@@ -124,6 +155,8 @@ export function sessionEndingInWords(trace) {
124
155
  const source = cuaGoalSource(trace);
125
156
  if (source === "participant_report")
126
157
  return "reported reaching the goal";
158
+ if (isClockEnd(source))
159
+ return `ran until its ${actorStopCauseLabel(source)}`;
127
160
  return source === "condition_matched" ? "met its completion condition" : "reached the goal";
128
161
  }
129
162
  case "turn_completed":
@@ -178,6 +211,10 @@ export function formatParticipantOutcomes(outcomes, terminalCauses = []) {
178
211
  if (outcomes.total === 0)
179
212
  return "no participants reached a terminal state";
180
213
  const parts = [participantCompletionLine(outcomes, terminalCauses)];
214
+ // Passed at the clock without declaring that they reached the goal: next to the completions,
215
+ // and never counted among them.
216
+ if (outcomes.ranUntilEnd !== undefined && outcomes.ranUntilEnd > 0)
217
+ parts.push(...ranUntilPhrases(outcomes.ranUntilEnd, terminalCauses.filter((entry) => entry.status === "passed")));
181
218
  // Detail may explain a recorded outcome, but must never change its count or invent a match
182
219
  // between a tally and an incomplete set of traces.
183
220
  const append = (count, statuses, fallback) => {
@@ -212,12 +249,12 @@ export function formatParticipantOutcomes(outcomes, terminalCauses = []) {
212
249
  return parts.join(", ");
213
250
  }
214
251
  /** Presentation details tolerate optional legacy actor payloads without changing their tallies. */
215
- export function participantOutcomeDetails(streams) {
252
+ export function participantOutcomeDetails(streams, goalSourceOf = cuaGoalSource) {
216
253
  return streams.flatMap((stream) => {
217
254
  if (!isRecord(stream.actor))
218
255
  return [];
219
256
  const actor = stream.actor;
220
- const goalSource = cuaGoalSource(actor, stream.status);
257
+ const goalSource = goalSourceOf(actor, stream.status);
221
258
  if (!ACTOR_STATUSES.includes(String(actor.status))) {
222
259
  return goalSource === "unavailable" ? [{ status: "passed", goalSource }] : [];
223
260
  }
@@ -257,6 +294,9 @@ export function withCuaReviewProvenance(review, streams) {
257
294
  const { reachedGoal, total } = review.participants;
258
295
  const bareLine = `${reachedGoal}/${total} reached the goal`;
259
296
  const qualifiedLine = participantCompletionLine(review.participants, details);
297
+ // Earlier releases qualified the count with the endpoint source (cuaEndpointSource), which
298
+ // reads a clock end as a participant report.
299
+ const endpointLine = participantCompletionLine(review.participants, participantOutcomeDetails(streams, cuaEndpointSource));
260
300
  // A summary with no count gets it in a header. Earlier releases wrote a header in other words.
261
301
  const header = `${plural(total, "participant")} took part: ${outcomes}.${tasks} `;
262
302
  const olderHeader = `Run gate: ${review.verdict}. Participants: ${outcomes}.${tasks} Recorded summary: `;
@@ -266,7 +306,9 @@ export function withCuaReviewProvenance(review, streams) {
266
306
  ? review.summary.split(bareLine).join(qualifiedLine)
267
307
  : review.summary.includes(qualifiedLine)
268
308
  ? review.summary
269
- : `${header}${review.summary}`;
309
+ : review.summary.includes(endpointLine)
310
+ ? review.summary.split(endpointLine).join(qualifiedLine)
311
+ : `${header}${review.summary}`;
270
312
  return {
271
313
  ...review,
272
314
  summary,
@@ -6,18 +6,41 @@ import type { RunParticipantArrival } from "../run/bundle.js";
6
6
  export declare const LATEST_START_AFTER_MS: number;
7
7
  /** Whether `value` is a start offset humanish runs: whole milliseconds from 0 to 24 hours. */
8
8
  export declare function isStartAfterMs(value: unknown): value is number;
9
- /** How long before its end a participant is told the time left, unless it declares `windDownMs`. */
10
- export declare const DEFAULT_WIND_DOWN_MS: number;
11
9
  /**
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`).
10
+ * When a participant starts and how its session ends, resolved once from its participants entry
11
+ * when the plan builds the participant (participantSchedule). The plan, `study check`, the bundle
12
+ * estimate and the route read a participant's time from here.
14
13
  */
15
- export interface ParticipantWindow {
16
- readonly id?: string;
14
+ export interface ParticipantSchedule {
15
+ /** Its declared start, in ms after the run starts its participants; absent, with the run. */
17
16
  readonly startAfterMs?: number;
18
- readonly endAfterMs?: number;
19
- readonly maxSessionMs?: number;
20
- readonly windDownMs?: number;
17
+ /**
18
+ * How its session ends, when it declares `endAfterMs` or `maxSessionMs`. Without either it works
19
+ * up to the study's session budget.
20
+ */
21
+ readonly end?: ScheduleEnd;
22
+ }
23
+ /**
24
+ * A declared end. A `scheduled_end` is `endAfterMs` on the run's clock and holds the participant
25
+ * until then; a `session_cap` is `maxSessionMs` from the start of the participant's own session,
26
+ * which it may end sooner. `windowMs` is how long it may work: from its start to its end, or its
27
+ * cap. `windDownMs` is how long before the end each turn tells it the time left. `sessionMs` is its
28
+ * session budget and how long it holds a slot: the window plus SCHEDULED_END_ROOM_MS.
29
+ */
30
+ type ScheduleEnd = {
31
+ readonly windowMs: number;
32
+ readonly windDownMs: number;
33
+ readonly sessionMs: number;
34
+ } & ({
35
+ readonly kind: "scheduled_end";
36
+ readonly endAfterMs: number;
37
+ } | {
38
+ readonly kind: "session_cap";
39
+ });
40
+ /** A participant as the schedule's readers name it. */
41
+ export interface ScheduledParticipant {
42
+ readonly id: string;
43
+ readonly schedule: ParticipantSchedule;
21
44
  }
22
45
  /**
23
46
  * Why a participant's declared end cannot run, or undefined: an end that is not a whole number of
@@ -33,11 +56,32 @@ export declare function windowReason(entry: {
33
56
  readonly windDownMs?: unknown;
34
57
  }, path: string): string | undefined;
35
58
  /**
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`.
59
+ * The schedule of a participants entry that windowReason accepts. A cap wins over an end, which
60
+ * windowReason refuses beside it anyway.
61
+ */
62
+ export declare function participantSchedule(entry: {
63
+ readonly startAfterMs?: number;
64
+ readonly endAfterMs?: number;
65
+ readonly maxSessionMs?: number;
66
+ readonly windDownMs?: number;
67
+ }): ParticipantSchedule;
68
+ /**
69
+ * The schedule a rerun gives a participant: it starts with the rerun, and a scheduled end keeps its
70
+ * time on the rerun's clock and its wind-down, so its window runs from the rerun's start.
39
71
  */
40
- export declare function participantSessionMs(participant: ParticipantWindow, sessionMs: number): number;
72
+ export declare function startingWithTheRun(schedule: ParticipantSchedule): ParticipantSchedule;
73
+ /**
74
+ * A participant's own session budget and sandbox deadline. With an end or a cap it is the end's
75
+ * `sessionMs`, and the sandbox keeps the study sandbox's headroom over the study session on top;
76
+ * without one, the study's own.
77
+ */
78
+ export declare function participantSession(schedule: ParticipantSchedule, study: {
79
+ readonly sessionMs: number;
80
+ readonly sandboxMs: number;
81
+ }): {
82
+ sessionMs: number;
83
+ sandboxMs: number;
84
+ };
41
85
  /**
42
86
  * When a participant's session ends: at a time on the run's clock, or at the latest a time after
43
87
  * its own session starts, which the route reads when the session starts.
@@ -53,25 +97,27 @@ export type ParticipantEnd = {
53
97
  };
54
98
  /**
55
99
  * 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.
100
+ * undefined without an end or a cap. A scheduled end comes its window after the participant's
101
+ * time, which is its `endAfterMs` on the run's clock, even when it waited for a slot; a cap is
102
+ * whole from wherever its session starts.
58
103
  */
59
- export declare function participantEnd(participant: ParticipantWindow, scheduledAt: number): ParticipantEnd | undefined;
104
+ export declare function participantEnd(schedule: ParticipantSchedule, scheduledAt: number): ParticipantEnd | undefined;
60
105
  /**
61
106
  * 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
63
- * route adds to a session for setup and teardown (sandboxHeadroomMs).
107
+ * long, or undefined. Its deadline is its participantSession's, from the study's session budget and
108
+ * sandbox deadline.
64
109
  */
65
- export declare function windowDeadlineRefusal(participants: readonly ParticipantWindow[], sandbox: {
110
+ export declare function windowDeadlineRefusal(participants: readonly ScheduledParticipant[], study: {
66
111
  readonly sessionMs: number;
67
- readonly headroomMs: number;
112
+ readonly sandboxMs: number;
68
113
  }, ceilingMs: number): string | undefined;
69
114
  /**
70
115
  * Each participant's window for `study check`, when any participant declares an end or a cap: when
71
116
  * 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.
117
+ * far into its session it is first told the time left, or when it starts and the study's
118
+ * `sessionMs`.
73
119
  */
74
- export declare function describeWindows(participants: readonly ParticipantWindow[], sessionMs: number): string | undefined;
120
+ export declare function describeWindows(participants: readonly ScheduledParticipant[], sessionMs: number): string | undefined;
75
121
  /** The refusal of a start offset that is not one, naming its `field`. */
76
122
  export declare function startAfterMessage(field: string): string;
77
123
  /**
@@ -112,13 +158,13 @@ export interface StartQueue {
112
158
  readonly ownSlot?: number;
113
159
  }
114
160
  /**
115
- * The schedule a roster's declared offsets give under the queue (`undefined` is 0: the participant
116
- * starts with the run). Participants start in schedule order, by offset and then roster position,
117
- * each at its time or when a slot frees, whichever is later; this is the rule runOnSchedule
118
- * follows. `sessionsMs` gives a participant with a declared end its own session budget
119
- * (participantSessionMs); the others hold their slot for `queue.sessionMs`.
161
+ * The schedule a roster's participants give under the queue. Participants start in schedule order,
162
+ * by offset and then roster position, each at its time or when a slot frees, whichever is later;
163
+ * this is the rule runOnSchedule follows. A participant with an end or a cap holds its slot for its
164
+ * own session budget, and the others for `queue.sessionMs`. A participant whose slot would free
165
+ * only at or after its scheduled end takes none: the run skips it (pastEndReason).
120
166
  */
121
- export declare function planArrivals(offsets: readonly (number | undefined)[], queue: StartQueue, sessionsMs?: readonly number[]): ArrivalPlan;
167
+ export declare function planArrivals(schedules: readonly ParticipantSchedule[], queue: StartQueue): ArrivalPlan;
122
168
  /**
123
169
  * The plan's schedule, after a "schedule:" label: the first and last start, and how many run at
124
170
  * once at the most.
@@ -128,13 +174,19 @@ export declare function describeArrivals(arrivals: ArrivalPlan): string;
128
174
  * A planner's arrival plan for its participants under the start queue, and the warnings the plan
129
175
  * records about it. A participant's desktop is created when it starts, so on computer use a late
130
176
  * start adds nothing to any sandbox's deadline; the provisioned shared world checks its app's
131
- * sandbox with servedScheduleRefusal. `lowered` is the warning that the E2B plan's limit runs the
132
- * roster in waves.
177
+ * sandbox with servedScheduleRefusal. `lowered` is participantsAtOnce's warning that the E2B plan's
178
+ * limit or HUMANISH_CUA_MAX_CONCURRENCY runs the roster in waves.
133
179
  */
134
- export declare function planStarts(participants: readonly ParticipantWindow[], queue: StartQueue, lowered: string | undefined): {
180
+ export declare function planStarts(participants: readonly ScheduledParticipant[], queue: StartQueue, lowered: string | undefined): {
135
181
  arrivals: ArrivalPlan;
136
182
  warnings: string[];
137
183
  };
184
+ /**
185
+ * Why a participant whose start comes at `nowMs` is skipped, or undefined: its scheduled end came
186
+ * while it waited, so its session would end before it began. The plan's simulation applies the
187
+ * same rule (planArrivals).
188
+ */
189
+ export declare function pastEndReason(end: ParticipantEnd | undefined, nowMs: number): string | undefined;
138
190
  /**
139
191
  * Why a sandbox that serves the app until the last participant ends, the provisioned shared
140
192
  * world's subject sandbox, cannot live that long under `ceilingMs`; undefined when it can. Its
@@ -177,3 +229,4 @@ export declare function runOnSchedule<T, R>(items: readonly T[], options: {
177
229
  readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
178
230
  readonly signal?: AbortSignal;
179
231
  }, start: (item: T, index: number, scheduled: ScheduledStart) => Promise<R>): Promise<R[]>;
232
+ export {};