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/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,37 @@ The Unreleased section holds the full notes for the next version until it is tag
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 0.126.0: Per-participant time zones, session caps and desktop files, page paths, a lighter live Observer (2026-10-13)
|
|
12
|
+
|
|
13
|
+
humanish 0.126.0 adds per-participant settings for long, mixed studies and a lighter live Observer.
|
|
14
|
+
Each participant can have its own time zone (`participants[].clock`), which now also sets its
|
|
15
|
+
hosted browser, its desktop's panel clock and a `desktop-cli` terminal; an existing
|
|
16
|
+
`actor.clock.timeZone` sets them too. `participants[].maxSessionMs` lets a participant end its
|
|
17
|
+
session sooner and stops one still working at the cap with a closing report.
|
|
18
|
+
`participants[].files` puts files on a participant's desktop for upload dialogs, and each
|
|
19
|
+
computer-use turn records the page it was on (origin and path only). A closing report that opens
|
|
20
|
+
"REACHED THE GOAL." no longer fails its run over the page copy it paraphrases. The served Observer's
|
|
21
|
+
live feed sends only new items each poll: about 90 KB per poll through a synthetic 8-hour,
|
|
22
|
+
24-participant run, where 0.125.0 sent up to 69.5 MB. Offsets and miscased zone names are now
|
|
23
|
+
refused, and so is a zone on the local browser runtime.
|
|
24
|
+
|
|
25
|
+
[Release notes](https://github.com/danielgwilson/humanish/releases/tag/v0.126.0)
|
|
26
|
+
|
|
27
|
+
## 0.125.0: Run bundle v3 with live items in append-only files, loadRunBundle, faster repeated verify (2026-10-12)
|
|
28
|
+
|
|
29
|
+
humanish 0.125.0 keeps each live flush of a long study small, exports the run bundle loader, and
|
|
30
|
+
verifies a finished study faster. Run bundles are now `humanish.run-bundle.v3`: a running
|
|
31
|
+
participant's items so far live in an append-only file under `live/`, and each flush appends only
|
|
32
|
+
the items recorded since the last one. On a synthetic run of 24 participants working 8 hours, a
|
|
33
|
+
flush wrote about 0.23 MB in about 0.24 s, where 0.124.0 wrote up to 120 MB in 3.0 s. A finished
|
|
34
|
+
v3 bundle differs from v2 only in its schema name; 0.124.0 and earlier refuse it by name.
|
|
35
|
+
`loadRunBundle(cwd, run)` returns run.json with every trace and live file joined and checked, for
|
|
36
|
+
v1, v2 and v3 bundles. The five verifies of a finished study read each screenshot once: 10 s in
|
|
37
|
+
all for 1,203 screenshots, down from 35 s. A key in a study's title is redacted from the bundle,
|
|
38
|
+
and one in a persona id or desktop template fails at parse instead of blocking the run.
|
|
39
|
+
|
|
40
|
+
[Release notes](https://github.com/danielgwilson/humanish/releases/tag/v0.125.0)
|
|
41
|
+
|
|
11
42
|
## 0.124.0: Small Observer files for long studies, faster verify, closing reports under zero data retention (2026-10-12)
|
|
12
43
|
|
|
13
44
|
humanish 0.124.0 keeps a long study's Observer files small, checks screenshots about eight times
|
package/CONTEXT.md
CHANGED
|
@@ -12,7 +12,7 @@ bundle and output fields keep older spellings; the second table maps them.
|
|
|
12
12
|
| Term | Meaning | Where it lives in the code |
|
|
13
13
|
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
14
14
|
| study | What a user designs and runs: one YAML file that declares the subject, the actor, the participants and the caps. The file lives at `humanish/studies/<id>.yaml` with schema `humanish.study.v3`, read by `humanish study` and `--study`. humanish refuses a 0.107 `humanish.lab.v2` file, and `humanish migrate` converts it (second table). | `src/study/`; `StudyConfig` in `src/study/types.ts`, parsed by `parseStudy` in `src/study/config.ts` |
|
|
15
|
-
| run | One execution of a study, on disk: `.humanish/runs/<runId>/`, with `run.json` (`humanish.run-bundle.
|
|
15
|
+
| run | One execution of a study, on disk: `.humanish/runs/<runId>/`, with `run.json` (`humanish.run-bundle.v3`) as the source of truth and each participant's trace in its own file. | `src/run/`; `RunBundle` in `src/run/bundle.ts` |
|
|
16
16
|
| dry run | A run with no live participant, from `--dry-run` or `mode: dry-run`. humanish writes a synthetic run bundle, review and Observer with no browser, model, keys or provider spend, and tests no product behavior. The `preview` route only dry-runs; "preview" names that route and nothing else. | `runDryRun` in `src/run/dry-run.ts`; `resolveStudyDryRun` in `src/study/plan.ts` |
|
|
17
17
|
| participant | One simulated person, or agent, taking part in a study. The planner builds each one's record (id, persona id, assignment, labels and the route's own fields). Shared-world participants resolve the plan's records; computer-use participants rebuild the same records from the study config with `computerUseParticipants` (`src/study/plan-participants.ts`) and resolve those. `ResolvedParticipant` adds the bundle ids, persona and prompt. `run.json` records each one as a `simulations[]` entry with one or more `streams[]`. | `ResolvedParticipant` in `src/run/participant.ts`; the planned records in `src/study/plan-participants.ts`. Computer-use and shared-world participants run a `DesktopParticipantRun` (`src/routes/computer-use/types.ts`). The terminal session (`src/routes/terminal/session.ts`), scripted surfaces (`src/routes/scripted/plan.ts`) and fixture simulations (`src/run/dry-run.ts`) still build their own. |
|
|
18
18
|
| persona | Who a participant is: traits, background, device. `humanish/personas/<id>.yaml`. | `src/study/persona.ts`, `src/study/persona-resolve.ts` |
|
package/README.md
CHANGED
|
@@ -335,7 +335,7 @@ and the library exports.
|
|
|
335
335
|
- Breaking changes ship together in one minor release. Its release notes carry a migration
|
|
336
336
|
table: each old form, its new form, and the change to make.
|
|
337
337
|
- The study file and the run bundle carry versioned schemas, `humanish.study.v3` and
|
|
338
|
-
`humanish.run-bundle.
|
|
338
|
+
`humanish.run-bundle.v3`. A breaking change to either one bumps its version.
|
|
339
339
|
|
|
340
340
|
Pin the exact version in `package.json` and read [CHANGELOG.md](CHANGELOG.md) before upgrading.
|
|
341
341
|
|
|
@@ -483,7 +483,7 @@ for the supported library seam.
|
|
|
483
483
|
|
|
484
484
|
- run a study: `runStudy`, `RunStudyOptions`, `StudyOutcome`, `StudyResult`, `StudyEvent`, `routeOf`,
|
|
485
485
|
`parseStudy`, `STUDY_SCHEMA`;
|
|
486
|
-
- read a run: `verifyRun`, `renderObserver`, `RunBundle`, `ActorTrace`;
|
|
486
|
+
- read a run: `verifyRun`, `loadRunBundle`, `renderObserver`, `RunBundle`, `ActorTrace`;
|
|
487
487
|
- bring a participant: `ComputerUseProvider`, `ComputerUseExecutor`, `ProviderContext`,
|
|
488
488
|
`createOpenAiResponsesProvider`, `runComputerUseLoop`, `defaultRedactionHooks`;
|
|
489
489
|
- score a run: `AdapterScorerModule` and its scoring contexts.
|
|
@@ -78,6 +78,8 @@ export interface CuaActorSessionOptions {
|
|
|
78
78
|
onMessage?: (text: string) => void;
|
|
79
79
|
/** Runtime-only per-turn raw-frame callback threaded to the loop; see LoopTaps.onScreenshot. */
|
|
80
80
|
onScreenshot?: (frame: Buffer) => void;
|
|
81
|
+
/** Whether the trace carries the observed page, threaded to the loop; see LoopRunOptions.recordPages. */
|
|
82
|
+
recordPages?: boolean;
|
|
81
83
|
/** Per-turn trace snapshot callback threaded to the loop; see CuaLoopOptions.onTrace. */
|
|
82
84
|
onTrace?: (items: readonly ActorTraceItem[], usage: ActorTokenUsage, metadata?: CuaLiveMetadata) => void;
|
|
83
85
|
}
|
|
@@ -57,6 +57,7 @@ export async function runCuaActorSession(options) {
|
|
|
57
57
|
...(options.onMessage === undefined ? {} : { onMessage: options.onMessage }),
|
|
58
58
|
...(options.onScreenshot === undefined ? {} : { onScreenshot: options.onScreenshot }),
|
|
59
59
|
...(options.onTrace === undefined ? {} : { onTrace: options.onTrace }),
|
|
60
|
+
...(options.recordPages === undefined ? {} : { recordPages: options.recordPages }),
|
|
60
61
|
};
|
|
61
62
|
const result = await runComputerUseLoopWithTaps(loopOptions);
|
|
62
63
|
if (result.trace.executionProfile?.billing === "account-unknown") {
|
|
@@ -4,17 +4,18 @@ import type { CuaObservation, ParticipantClock } from "./types.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* The time text for the request the loop is about to send: the local time when the participant
|
|
6
6
|
* has a time zone, and the end time and the minutes left from the start of its wind-down, or
|
|
7
|
-
* after it tried to end its session before its end (`askedToEnd`). Undefined when
|
|
8
|
-
* nothing to say.
|
|
7
|
+
* after it tried to end its session before its scheduled end (`askedToEnd`). Undefined when
|
|
8
|
+
* there is nothing to say.
|
|
9
9
|
*/
|
|
10
10
|
export declare function clockHint(clock: ParticipantClock | undefined, now: () => number, askedToEnd?: boolean): string | undefined;
|
|
11
11
|
/**
|
|
12
|
-
* Whether the participant
|
|
13
|
-
* the session: the end is what the study declared for it.
|
|
12
|
+
* Whether the participant is held to a scheduled end still to come. Its own end before then does
|
|
13
|
+
* not end the session: the end is what the study declared for it. A session cap holds no one.
|
|
14
14
|
*/
|
|
15
|
-
export declare function
|
|
15
|
+
export declare function heldToScheduledEnd(session: LoopSession): boolean;
|
|
16
16
|
/**
|
|
17
|
-
* The stop at the participant's
|
|
18
|
-
* planned end and the participant is asked for
|
|
17
|
+
* The stop at the participant's end, or undefined before it: its scheduled end or its session cap,
|
|
18
|
+
* which is the stop cause. The session passes as a planned end and the participant is asked for
|
|
19
|
+
* its closing report on the last observation.
|
|
19
20
|
*/
|
|
20
|
-
export declare function
|
|
21
|
+
export declare function clockEndStop(session: LoopSession, observation: CuaObservation): Stop | undefined;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { plural } from "../../../run/text.js";
|
|
2
|
-
// What the participant knows about the time. A hosted desktop shows
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// What the participant knows about the time. A hosted desktop's panel clock shows the time in the
|
|
3
|
+
// participant's zone, or in UTC without one, and a local browser shows no clock. Neither shows
|
|
4
|
+
// when the session ends, so a participant told "your session ends at 11:00" relates it to now
|
|
5
|
+
// through each turn's text. The text rides the request's context hint, which every provider sends
|
|
6
|
+
// with the turn (OpenAI as a user message, Codex and the local agents as the harness note).
|
|
6
7
|
/**
|
|
7
8
|
* The time text for the request the loop is about to send: the local time when the participant
|
|
8
9
|
* has a time zone, and the end time and the minutes left from the start of its wind-down, or
|
|
9
|
-
* after it tried to end its session before its end (`askedToEnd`). Undefined when
|
|
10
|
-
* nothing to say.
|
|
10
|
+
* after it tried to end its session before its scheduled end (`askedToEnd`). Undefined when
|
|
11
|
+
* there is nothing to say.
|
|
11
12
|
*/
|
|
12
13
|
export function clockHint(clock, now, askedToEnd = false) {
|
|
13
14
|
if (clock === undefined)
|
|
@@ -19,31 +20,40 @@ export function clockHint(clock, now, askedToEnd = false) {
|
|
|
19
20
|
const { end } = clock;
|
|
20
21
|
if (end !== undefined && (askedToEnd || nowMs >= end.atMs - end.windDownMs)) {
|
|
21
22
|
const minutesLeft = Math.max(1, Math.ceil((end.atMs - nowMs) / 60_000));
|
|
22
|
-
|
|
23
|
+
const at = `${endTime(clock, end.atMs)}${end.kind === "session_cap" ? " at the latest" : ""}`;
|
|
24
|
+
const close = end.kind === "session_cap"
|
|
25
|
+
? "You may end it sooner when you are done. If you are still working then, it ends by itself, and you will then be asked for your closing account."
|
|
26
|
+
: "Keep working until then. The session ends by itself at that time, and you will then be asked for your closing account.";
|
|
27
|
+
parts.push(`${askedToEnd ? "Your session is not over. " : ""}Your session ends at ${at}, in ${plural(minutesLeft, "minute")}. ${close}`);
|
|
23
28
|
}
|
|
24
29
|
return parts.length === 0 ? undefined : parts.join(" ");
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
|
-
* Whether the participant
|
|
28
|
-
* the session: the end is what the study declared for it.
|
|
32
|
+
* Whether the participant is held to a scheduled end still to come. Its own end before then does
|
|
33
|
+
* not end the session: the end is what the study declared for it. A session cap holds no one.
|
|
29
34
|
*/
|
|
30
|
-
export function
|
|
35
|
+
export function heldToScheduledEnd(session) {
|
|
31
36
|
const end = session.settings.clock?.end;
|
|
32
|
-
return end !== undefined && session.now() < end.atMs;
|
|
37
|
+
return end !== undefined && end.kind !== "session_cap" && session.now() < end.atMs;
|
|
33
38
|
}
|
|
34
39
|
/**
|
|
35
|
-
* The stop at the participant's
|
|
36
|
-
* planned end and the participant is asked for
|
|
40
|
+
* The stop at the participant's end, or undefined before it: its scheduled end or its session cap,
|
|
41
|
+
* which is the stop cause. The session passes as a planned end and the participant is asked for
|
|
42
|
+
* its closing report on the last observation.
|
|
37
43
|
*/
|
|
38
|
-
export function
|
|
44
|
+
export function clockEndStop(session, observation) {
|
|
39
45
|
const end = session.settings.clock?.end;
|
|
40
46
|
if (end === undefined || session.now() < end.atMs)
|
|
41
47
|
return undefined;
|
|
48
|
+
const at = endTime(session.settings.clock, end.atMs);
|
|
49
|
+
const kind = end.kind ?? "scheduled_end";
|
|
42
50
|
return {
|
|
43
51
|
completionReason: "goal_satisfied",
|
|
44
|
-
reason:
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
reason: kind === "session_cap"
|
|
53
|
+
? `the participant's session reached its cap at ${at}`
|
|
54
|
+
: `the participant's scheduled end at ${at} was reached`,
|
|
55
|
+
stopCause: kind,
|
|
56
|
+
debriefTrigger: { kind, observation },
|
|
47
57
|
};
|
|
48
58
|
}
|
|
49
59
|
// The end to the nearest minute: a run starts at no particular second, and "ends at 00:09" read
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ActorTrace } from "../../contract.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type DebriefTrigger } from "./ending.js";
|
|
3
3
|
import type { LoopSession } from "./session.js";
|
|
4
4
|
import type { CuaTurnRequest } from "./types.js";
|
|
5
5
|
type Debrief = NonNullable<ActorTrace["debrief"]>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { closingReportSchema, participantImpressionsSchema } from "../../closing-report.js";
|
|
2
2
|
import { isComputerUseAdmissionLimitError } from "../admission-limit.js";
|
|
3
3
|
import { IMPRESSIONS_ASK, IMPRESSIONS_HINT, recordImpressions } from "./impressions.js";
|
|
4
|
+
import { declaredOutcomeOf } from "./ending.js";
|
|
4
5
|
import { singleDispatch } from "./provider-call.js";
|
|
5
6
|
import { CuaDeadlineError, raceSessionDeadline, requestScope } from "./race.js";
|
|
6
7
|
import { notice } from "./trace.js";
|
|
@@ -109,7 +110,7 @@ async function debriefExchange(session, context, trigger, ask, { send, boundMs }
|
|
|
109
110
|
const turn = provider.requestPolicy === "fail_closed"
|
|
110
111
|
? await singleDispatch(session, "debrief", (dispatchSignal) => send(request, dispatchSignal), boundMs)
|
|
111
112
|
: await raceSessionDeadline(send(request, scope.signal), boundMs, signal);
|
|
112
|
-
return acceptDebriefTurn(session, turn, ask, record);
|
|
113
|
+
return acceptDebriefTurn(session, turn, trigger, ask, record);
|
|
113
114
|
}
|
|
114
115
|
catch (error) {
|
|
115
116
|
// A failed optional report cannot rewrite the already observed structured completion.
|
|
@@ -132,7 +133,7 @@ async function debriefExchange(session, context, trigger, ask, { send, boundMs }
|
|
|
132
133
|
scope.end();
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
|
-
function acceptDebriefTurn(session, turn, ask, record) {
|
|
136
|
+
function acceptDebriefTurn(session, turn, trigger, ask, record) {
|
|
136
137
|
const { maxUsd, overRunBudget, estimateTurnCostUsd } = session.settings;
|
|
137
138
|
session.usage.record(turn, "debrief");
|
|
138
139
|
session.lastResponseId = turn.responseId ?? session.lastResponseId;
|
|
@@ -148,11 +149,18 @@ function acceptDebriefTurn(session, turn, ask, record) {
|
|
|
148
149
|
return record("failed", "the closing response requested actions or safety checks; none were executed and its report was not accepted", usageReported);
|
|
149
150
|
}
|
|
150
151
|
// First-party providers parse their replies already; these parses cover third-party ports.
|
|
151
|
-
const { status, detail, ...added } = ask.read(session, turn);
|
|
152
|
+
const { status, detail, ...added } = ask.read(session, turn, trigger);
|
|
152
153
|
return { ...record(status, detail, usageReported), ...added };
|
|
153
154
|
}
|
|
154
|
-
/**
|
|
155
|
-
|
|
155
|
+
/**
|
|
156
|
+
* A closing report: its summary and friction as one participant message, then its impressions.
|
|
157
|
+
* At a scheduled end or a session cap an accepted report is the participant's account of the
|
|
158
|
+
* session: its summary becomes the session's reason (loopResult), so the outcome it declares
|
|
159
|
+
* becomes the session's declared outcome. The blocker rule reads a declared `reached` or
|
|
160
|
+
* `blocked` before the summary's wording. After a matched stop condition or dwell window, the
|
|
161
|
+
* condition stays the completion evidence.
|
|
162
|
+
*/
|
|
163
|
+
function readClosingReport(session, turn, trigger) {
|
|
156
164
|
const closing = closingReportSchema.safeParse(turn.closingReport);
|
|
157
165
|
if (!closing.success) {
|
|
158
166
|
return {
|
|
@@ -173,6 +181,8 @@ function readClosingReport(session, turn) {
|
|
|
173
181
|
}));
|
|
174
182
|
session.trace.bump("messages");
|
|
175
183
|
session.impressions = recordImpressions(session, closing.data.impressions);
|
|
184
|
+
if (trigger.kind === "scheduled_end" || trigger.kind === "session_cap")
|
|
185
|
+
session.declaredOutcome = declaredOutcomeOf(turn, closing.data.summary);
|
|
176
186
|
return {
|
|
177
187
|
status: "completed",
|
|
178
188
|
detail: "one read-only report; no additional desktop actions; original stop and task outcomes preserved",
|
|
@@ -8,7 +8,7 @@ import type { CuaObservation, CuaProvider, CuaTurn } from "./types.js";
|
|
|
8
8
|
* the participant's own ending when only its impressions are missing.
|
|
9
9
|
*/
|
|
10
10
|
export interface DebriefTrigger {
|
|
11
|
-
readonly kind: "stop_when" | "dwell" | "scheduled_end" | "participant_end";
|
|
11
|
+
readonly kind: "stop_when" | "dwell" | "scheduled_end" | "session_cap" | "participant_end";
|
|
12
12
|
/** The final observation, already captured; the debrief request sends it without observing. */
|
|
13
13
|
readonly observation: CuaObservation;
|
|
14
14
|
}
|
|
@@ -22,12 +22,15 @@ export interface Stop {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Read the fixed closing line the prompt asks for: the first non-empty line of the
|
|
25
|
-
* participant's last message, exactly one of three phrases,
|
|
26
|
-
* else is absence, never a guess. Exported for tests.
|
|
25
|
+
* participant's last message or closing report summary, exactly one of three phrases,
|
|
26
|
+
* punctuation and case forgiven. Anything else is absence, never a guess. Exported for tests.
|
|
27
27
|
*/
|
|
28
28
|
export declare function declaredOutcomeFromClosingLine(message: string | undefined): ParticipantDeclaredOutcome | undefined;
|
|
29
|
-
/**
|
|
30
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The participant's own word for how it ended: the turn's schema field first, then the closing
|
|
31
|
+
* line of `text`, its last message or its closing report's summary.
|
|
32
|
+
*/
|
|
33
|
+
export declare function declaredOutcomeOf(turn: CuaTurn, text: string | undefined): ParticipantDeclaredOutcome | undefined;
|
|
31
34
|
/**
|
|
32
35
|
* The participant reported a natural endpoint. "not_reached" is a participant who stopped without
|
|
33
36
|
* finishing: gave_up, which tallies as abandoned. "blocked" keeps goal_satisfied here (the actor
|
|
@@ -6,8 +6,8 @@ import { notice } from "./trace.js";
|
|
|
6
6
|
import { plural } from "../../../run/text.js";
|
|
7
7
|
/**
|
|
8
8
|
* Read the fixed closing line the prompt asks for: the first non-empty line of the
|
|
9
|
-
* participant's last message, exactly one of three phrases,
|
|
10
|
-
* else is absence, never a guess. Exported for tests.
|
|
9
|
+
* participant's last message or closing report summary, exactly one of three phrases,
|
|
10
|
+
* punctuation and case forgiven. Anything else is absence, never a guess. Exported for tests.
|
|
11
11
|
*/
|
|
12
12
|
export function declaredOutcomeFromClosingLine(message) {
|
|
13
13
|
if (message === undefined)
|
|
@@ -27,9 +27,12 @@ export function declaredOutcomeFromClosingLine(message) {
|
|
|
27
27
|
return "blocked";
|
|
28
28
|
return undefined;
|
|
29
29
|
}
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The participant's own word for how it ended: the turn's schema field first, then the closing
|
|
32
|
+
* line of `text`, its last message or its closing report's summary.
|
|
33
|
+
*/
|
|
34
|
+
export function declaredOutcomeOf(turn, text) {
|
|
35
|
+
return turn.outcome ?? declaredOutcomeFromClosingLine(text);
|
|
33
36
|
}
|
|
34
37
|
/**
|
|
35
38
|
* The participant reported a natural endpoint. "not_reached" is a participant who stopped without
|
|
@@ -43,7 +43,13 @@ export declare class DesktopObserver {
|
|
|
43
43
|
* session is already over.
|
|
44
44
|
*/
|
|
45
45
|
observeFinalTasks(turnNumber: number): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Observe the desktop and note the observation's page as the session's final page. Every
|
|
48
|
+
* observation passes through here, so the page recordScreenshot puts on an item is the page of
|
|
49
|
+
* the observation it records.
|
|
50
|
+
*/
|
|
46
51
|
private observeBounded;
|
|
52
|
+
private observeOrRetry;
|
|
47
53
|
/**
|
|
48
54
|
* The observation with every utterance heard since the provider last saw one. New utterances are
|
|
49
55
|
* recorded as notices; too many pending utterances fail the executor as invalid_response.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { evaluateStopWhen } from "../../stop-conditions.js";
|
|
2
2
|
import { ComputerUseExecutorError, isComputerUseExecutorError } from "../executor-error.js";
|
|
3
3
|
import { CUA_SPEECH_LIMITS } from "../speech.js";
|
|
4
|
+
import { tracePage } from "../../../evidence/trace-page.js";
|
|
4
5
|
import { dwellCompleted, missingFrame, stopWhenMatched } from "./ending.js";
|
|
5
6
|
import { CuaAbortError, CuaStallError, raceCallBound } from "./race.js";
|
|
6
7
|
import { notice } from "./trace.js";
|
|
@@ -119,10 +120,24 @@ export class DesktopObserver {
|
|
|
119
120
|
throw error;
|
|
120
121
|
}
|
|
121
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Observe the desktop and note the observation's page as the session's final page. Every
|
|
125
|
+
* observation passes through here, so the page recordScreenshot puts on an item is the page of
|
|
126
|
+
* the observation it records.
|
|
127
|
+
*/
|
|
128
|
+
async observeBounded(label) {
|
|
129
|
+
const observation = await this.observeOrRetry(label);
|
|
130
|
+
const { session } = this;
|
|
131
|
+
session.finalPage =
|
|
132
|
+
session.settings.recordPages === false
|
|
133
|
+
? undefined
|
|
134
|
+
: tracePage(observation.url, (text) => session.redactNarration(text));
|
|
135
|
+
return observation;
|
|
136
|
+
}
|
|
122
137
|
// An executor without stallRecovery "fail_closed" gets one retry of a stalled observe.
|
|
123
138
|
// One that cannot safely replay a pending request opts out, so even a shorter outer bound stops
|
|
124
139
|
// without retrying.
|
|
125
|
-
async
|
|
140
|
+
async observeOrRetry(label) {
|
|
126
141
|
const { session } = this;
|
|
127
142
|
const { executor } = session;
|
|
128
143
|
try {
|
|
@@ -179,10 +194,13 @@ export class DesktopObserver {
|
|
|
179
194
|
const path = await session.writeScreenshot(`${label}.png`, bytes);
|
|
180
195
|
const screenshotRef = { path, redaction: method };
|
|
181
196
|
session.lastScreenshotRef = screenshotRef;
|
|
197
|
+
const page = session.finalPage;
|
|
182
198
|
session.trace.record("screenshot", () => ({
|
|
183
199
|
lifecycle: "completed",
|
|
184
200
|
title: label,
|
|
185
201
|
screenshotRef,
|
|
202
|
+
// A copy: the live flush freezes a recorded item, and finalPage stays the session's own.
|
|
203
|
+
...(page === undefined ? {} : { page: { ...page } }),
|
|
186
204
|
}));
|
|
187
205
|
session.trace.bump("screenshots");
|
|
188
206
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ActorTraceItem, ParticipantDeclaredOutcome, ParticipantImpressions } from "../../contract.js";
|
|
1
|
+
import type { ActorTraceItem, ParticipantDeclaredOutcome, ParticipantImpressions, TracePage } from "../../contract.js";
|
|
2
2
|
import type { AffordanceObservation } from "../../affordance.js";
|
|
3
3
|
import { TaskTracker } from "../../../study/tasks.js";
|
|
4
4
|
import type { DebriefTrigger, Stop } from "./ending.js";
|
|
@@ -12,7 +12,7 @@ export type ScreenshotRef = NonNullable<ActorTraceItem["screenshotRef"]>;
|
|
|
12
12
|
* wrapped callbacks are fields on LoopSession instead. Callers destructure hooks to call them.
|
|
13
13
|
*/
|
|
14
14
|
export type LoopSettings = {
|
|
15
|
-
readonly [K in "instructions" | "persona" | "redaction" | "stopWhen" | "dwell" | "clock" | "tasks" | "maxUsd" | "overRunBudget" | "estimateTurnCostUsd" | "onObservedUrl" | "onMessage" | "onScreenshot" | "onTrace"]: LoopRunOptions[K];
|
|
15
|
+
readonly [K in "instructions" | "persona" | "redaction" | "stopWhen" | "dwell" | "clock" | "tasks" | "maxUsd" | "overRunBudget" | "estimateTurnCostUsd" | "onObservedUrl" | "onMessage" | "onScreenshot" | "onTrace" | "recordPages"]: LoopRunOptions[K];
|
|
16
16
|
};
|
|
17
17
|
/** The participant's dispatched actions, as the backstop and failure notices describe them. */
|
|
18
18
|
export interface ActionHistory {
|
|
@@ -61,6 +61,8 @@ export declare class LoopSession {
|
|
|
61
61
|
lastScreenshotRef: ScreenshotRef | undefined;
|
|
62
62
|
lastResponseId: string | undefined;
|
|
63
63
|
observedAppState: boolean;
|
|
64
|
+
/** The page of the session's last observation; undefined when that observation read no URL. */
|
|
65
|
+
finalPage: TracePage | undefined;
|
|
64
66
|
declaredOutcome: ParticipantDeclaredOutcome | undefined;
|
|
65
67
|
/** Set by a structured stop; the debrief follows it even if the session then failed. */
|
|
66
68
|
debriefTrigger: DebriefTrigger | undefined;
|
|
@@ -61,6 +61,8 @@ export class LoopSession {
|
|
|
61
61
|
// Whether any observation carried appState. trace.ts states it in redaction.notes; the appState
|
|
62
62
|
// itself is never persisted.
|
|
63
63
|
observedAppState = false;
|
|
64
|
+
/** The page of the session's last observation; undefined when that observation read no URL. */
|
|
65
|
+
finalPage;
|
|
64
66
|
declaredOutcome;
|
|
65
67
|
/** Set by a structured stop; the debrief follows it even if the session then failed. */
|
|
66
68
|
debriefTrigger;
|
|
@@ -70,7 +72,7 @@ export class LoopSession {
|
|
|
70
72
|
scrubText;
|
|
71
73
|
constructor(options) {
|
|
72
74
|
// Read once, in this order: a caller mutating its options object mid-run changes nothing.
|
|
73
|
-
const { instructions, provider, executor, persona, redaction, timeoutMs, now, signal, idleSteps = DEFAULT_IDLE_STEPS, noProgressSteps = DEFAULT_NO_PROGRESS_STEPS, maxWaitMs, idleWaitMs, acknowledgeSafetyChecks = () => null, redactScreenshots = false, scrubText = (text) => text, writeScreenshot = async (name) => `screenshots/${name}`, stopWhen, dwell, clock, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), tasks, maxUsd, overRunBudget, estimateTurnCostUsd, requireReportedUsageForSpendCap = false, onObservedUrl, onMessage, onScreenshot, onTrace, } = options;
|
|
75
|
+
const { instructions, provider, executor, persona, redaction, timeoutMs, now, signal, idleSteps = DEFAULT_IDLE_STEPS, noProgressSteps = DEFAULT_NO_PROGRESS_STEPS, maxWaitMs, idleWaitMs, acknowledgeSafetyChecks = () => null, redactScreenshots = false, scrubText = (text) => text, writeScreenshot = async (name) => `screenshots/${name}`, stopWhen, dwell, clock, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), tasks, maxUsd, overRunBudget, estimateTurnCostUsd, requireReportedUsageForSpendCap = false, onObservedUrl, onMessage, onScreenshot, onTrace, recordPages, } = options;
|
|
74
76
|
this.settings = {
|
|
75
77
|
instructions,
|
|
76
78
|
persona,
|
|
@@ -86,6 +88,7 @@ export class LoopSession {
|
|
|
86
88
|
onMessage,
|
|
87
89
|
onScreenshot,
|
|
88
90
|
onTrace,
|
|
91
|
+
recordPages,
|
|
89
92
|
};
|
|
90
93
|
this.provider = provider;
|
|
91
94
|
this.executor = executor;
|
|
@@ -67,8 +67,9 @@ export function loopResult(session, stop, debrief) {
|
|
|
67
67
|
const { provider, settings, trace: recorder, usage } = session;
|
|
68
68
|
const { completionReason, stopCause } = stop;
|
|
69
69
|
// A participant whose session ended on the clock has its closing account as its reason, as one
|
|
70
|
-
// that ended the session itself has its last message; the stop cause keeps the scheduled end
|
|
71
|
-
|
|
70
|
+
// that ended the session itself has its last message; the stop cause keeps the scheduled end or
|
|
71
|
+
// the session cap.
|
|
72
|
+
const reason = (stopCause === "scheduled_end" || stopCause === "session_cap") && debrief?.report !== undefined
|
|
72
73
|
? debrief.report.summary
|
|
73
74
|
: stop.reason;
|
|
74
75
|
const completedAtMs = session.now();
|
|
@@ -131,6 +132,7 @@ export function loopResult(session, stop, debrief) {
|
|
|
131
132
|
waitSettings: { ...session.waits },
|
|
132
133
|
counts,
|
|
133
134
|
items: recorder.items,
|
|
135
|
+
...(session.finalPage === undefined ? {} : { finalPage: session.finalPage }),
|
|
134
136
|
...(session.actionHistory.affordances.length > 0
|
|
135
137
|
? { affordanceUse: summarizeAffordanceUse(session.actionHistory.affordances) }
|
|
136
138
|
: {}),
|
|
@@ -4,12 +4,18 @@ import type { RedactionHooks } from "../../../evidence/redaction.js";
|
|
|
4
4
|
import type { DwellWindow, StopWhen } from "../../stop-conditions.js";
|
|
5
5
|
import type { StudyTask } from "../../../study/tasks.js";
|
|
6
6
|
import type { ReasoningEffort } from "../../reasoning-effort.js";
|
|
7
|
-
/** The participant's clock: its local time zone and its
|
|
7
|
+
/** The participant's clock: its local time zone and its end, on the loop's clock. */
|
|
8
8
|
export interface ParticipantClock {
|
|
9
9
|
readonly timeZone?: string;
|
|
10
|
+
/**
|
|
11
|
+
* When the session ends. `kind` is `scheduled_end` when absent: the participant is held to the
|
|
12
|
+
* end and may not end its session before it. A `session_cap` is the latest the session lasts:
|
|
13
|
+
* the participant may end it sooner.
|
|
14
|
+
*/
|
|
10
15
|
readonly end?: {
|
|
11
16
|
readonly atMs: number;
|
|
12
17
|
readonly windDownMs: number;
|
|
18
|
+
readonly kind?: "scheduled_end" | "session_cap";
|
|
13
19
|
};
|
|
14
20
|
}
|
|
15
21
|
/**
|
|
@@ -94,9 +100,10 @@ export interface CuaObservation {
|
|
|
94
100
|
appState?: Record<string, unknown>;
|
|
95
101
|
/**
|
|
96
102
|
* Optional browser state captured by an executor that can inspect the driven browser
|
|
97
|
-
* deterministically (for example via Chrome DevTools Protocol). These fields
|
|
98
|
-
*
|
|
99
|
-
*
|
|
103
|
+
* deterministically (for example via Chrome DevTools Protocol). These fields drive stopWhen and
|
|
104
|
+
* progress decisions. The loop never persists the raw URL, title or text: persisting arbitrary
|
|
105
|
+
* DOM text would make private-data leakage too easy. From the URL it records only the page's
|
|
106
|
+
* origin and redacted path (`page` on screenshot items, `finalPage`; src/evidence/trace-page.ts).
|
|
100
107
|
*/
|
|
101
108
|
url?: string;
|
|
102
109
|
/**
|
|
@@ -359,7 +366,7 @@ export interface CuaLoopOptions {
|
|
|
359
366
|
* name, every request carries the local time. With `end`, from `end.atMs - end.windDownMs`
|
|
360
367
|
* every request names the end time (in `timeZone`, else UTC) and the minutes left, and at
|
|
361
368
|
* `end.atMs`, read on `now`, the loop asks for the closing report and ends the session with
|
|
362
|
-
* stop cause `scheduled_end
|
|
369
|
+
* stop cause `end.kind` (`scheduled_end` when absent).
|
|
363
370
|
*/
|
|
364
371
|
clock?: ParticipantClock;
|
|
365
372
|
/**
|
|
@@ -421,7 +428,7 @@ export interface LoopTaps {
|
|
|
421
428
|
/**
|
|
422
429
|
* Runtime-only URL tap: called with `observation.url` for each observation a turn may receive
|
|
423
430
|
* (the initial one, each post-action one and the one after a dwell window). The loop never
|
|
424
|
-
* persists the URL. Default: no-op.
|
|
431
|
+
* persists the raw URL. Default: no-op.
|
|
425
432
|
*
|
|
426
433
|
* Built-in caller: the external-public shared-world handoff (src/routes/shared-world/handoff.ts)
|
|
427
434
|
* latches the host participant's lobby URL and checks that each follower reached the same lobby.
|
|
@@ -447,8 +454,15 @@ export interface LoopTaps {
|
|
|
447
454
|
*/
|
|
448
455
|
onScreenshot?: (frame: Buffer) => void;
|
|
449
456
|
}
|
|
450
|
-
/** The loop's options inside humanish: the public ones
|
|
451
|
-
export type LoopRunOptions = CuaLoopOptions & LoopTaps
|
|
457
|
+
/** The loop's options inside humanish: the public ones, the handoff's taps and the page switch. */
|
|
458
|
+
export type LoopRunOptions = CuaLoopOptions & LoopTaps & {
|
|
459
|
+
/**
|
|
460
|
+
* Whether screenshot items and the trace carry the observed page (`page`, `finalPage`).
|
|
461
|
+
* Default true. The shared-world route passes false: its observed URLs land in the bundle only
|
|
462
|
+
* as digests (docs/architecture/external-public-shared-world.md).
|
|
463
|
+
*/
|
|
464
|
+
recordPages?: boolean;
|
|
465
|
+
};
|
|
452
466
|
export type CuaLiveMetadata = Pick<ActorTrace, "executionProfile" | "providerRequests" | "historyTurnsOmitted">;
|
|
453
467
|
/** What runComputerUseLoop returns; the public name for an actor session result. */
|
|
454
468
|
export type CuaLoopResult = ActorSessionResult;
|
|
@@ -2,7 +2,7 @@ import { closingReportSchema } from "../closing-report.js";
|
|
|
2
2
|
import { ComputerUseProviderError } from "./provider-error.js";
|
|
3
3
|
import { runActionBatch } from "./loop/actions.js";
|
|
4
4
|
import { advanceBackstop, startBackstop, } from "./loop/backstop.js";
|
|
5
|
-
import {
|
|
5
|
+
import { clockEndStop, clockHint, heldToScheduledEnd } from "./loop/clock.js";
|
|
6
6
|
import { requestDebrief } from "./loop/debrief.js";
|
|
7
7
|
import * as stops from "./loop/ending.js";
|
|
8
8
|
import { declaredOutcomeOf } from "./loop/ending.js";
|
|
@@ -118,7 +118,7 @@ async function runTurns(session, conversation) {
|
|
|
118
118
|
const blocked = reviewSafetyChecks(session, conversation, turn);
|
|
119
119
|
if (blocked !== undefined)
|
|
120
120
|
return blocked;
|
|
121
|
-
if ((turn.done || turn.actions.length === 0) &&
|
|
121
|
+
if ((turn.done || turn.actions.length === 0) && heldToScheduledEnd(session)) {
|
|
122
122
|
const held = keepWorking(session, conversation, backstop, observation);
|
|
123
123
|
if ("stop" in held)
|
|
124
124
|
return held.stop;
|
|
@@ -127,7 +127,7 @@ async function runTurns(session, conversation) {
|
|
|
127
127
|
}
|
|
128
128
|
if (turn.done || turn.actions.length === 0) {
|
|
129
129
|
// The declared outcome is kept even if redacting the participant's summary fails.
|
|
130
|
-
session.declaredOutcome = declaredOutcomeOf(turn);
|
|
130
|
+
session.declaredOutcome = declaredOutcomeOf(turn, turn.message);
|
|
131
131
|
const ended = stops.participantEnded(turn, session.declaredOutcome, (text) => session.redactNarration(text));
|
|
132
132
|
const report = closingReportSchema.safeParse(turn.closingReport).data;
|
|
133
133
|
// A provider that can ask for impressions alone does so after the session, like a debrief.
|
|
@@ -152,7 +152,7 @@ async function runTurns(session, conversation) {
|
|
|
152
152
|
actions: turn.actions,
|
|
153
153
|
observation,
|
|
154
154
|
heardNewSpeech: observer.heardNewSpeech,
|
|
155
|
-
waitingIsTheTask:
|
|
155
|
+
waitingIsTheTask: heldToScheduledEnd(session),
|
|
156
156
|
}, { idleSteps: session.idleSteps, noProgressSteps: session.noProgressSteps });
|
|
157
157
|
backstop = step.backstop;
|
|
158
158
|
const stalled = applyBackstop(session, conversation, step, [checkpoint.hint, batch.hint]);
|
|
@@ -204,7 +204,7 @@ function refuseAccountBilledCaps(options) {
|
|
|
204
204
|
function haltBeforeTurn(session, observation) {
|
|
205
205
|
if (session.signal?.aborted)
|
|
206
206
|
return stops.harnessAborted;
|
|
207
|
-
return (
|
|
207
|
+
return (clockEndStop(session, observation) ??
|
|
208
208
|
(session.remaining() < 0 ? stops.timeLimit(session) : undefined));
|
|
209
209
|
}
|
|
210
210
|
/**
|