humanish 0.131.1 → 0.133.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 +25 -0
- package/dist/actors/codex/restricted-participant.d.ts +2 -0
- package/dist/actors/codex/restricted-participant.js +6 -2
- package/dist/actors/computer-use/loop/actions.js +1 -0
- package/dist/actors/computer-use/loop/debrief.js +63 -23
- package/dist/actors/computer-use/loop/types.d.ts +16 -7
- package/dist/actors/computer-use/loop/watch.js +17 -15
- package/dist/actors/computer-use/openai-context.js +26 -14
- package/dist/actors/computer-use/openai-conversation.d.ts +38 -8
- package/dist/actors/computer-use/openai-conversation.js +133 -22
- package/dist/actors/computer-use/openai-provider.js +11 -35
- package/dist/actors/computer-use/openai-wire.d.ts +21 -0
- package/dist/actors/computer-use/openai-wire.js +82 -9
- package/dist/actors/computer-use/wait.d.ts +8 -6
- package/dist/actors/computer-use/wait.js +10 -8
- package/dist/actors/contract.d.ts +4 -0
- package/dist/actors/local-agent/cli.js +5 -5
- package/dist/evidence/redaction.d.ts +2 -0
- package/dist/evidence/redaction.js +8 -0
- package/dist/evidence/sensitive-json.d.ts +9 -0
- package/dist/evidence/sensitive-json.js +96 -0
- package/dist/routes/computer-use/local-vm.js +9 -3
- package/dist/routes/computer-use/participant-model.js +3 -0
- package/dist/study/bundle-estimate.d.ts +5 -0
- package/dist/study/bundle-estimate.js +15 -1
- package/dist/study/types.d.ts +5 -4
- package/dist/verify/verify.js +4 -2
- package/docs/architecture/actor-contract.md +9 -3
- package/docs/architecture/browser-control.md +2 -2
- package/docs/contracts/schemas.md +30 -11
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// containsSensitive over a JSON document that may not fit in one string. V8's longest string is
|
|
2
|
+
// 536,870,888 characters on Node 24, and a run with every participant's trace loaded can pass it.
|
|
3
|
+
//
|
|
4
|
+
// The document is scanned as pieces: the text before the array and its first element, each
|
|
5
|
+
// element after that, and the last element with the text after the array. Every cut then falls on
|
|
6
|
+
// the `,` between two objects, `},{`. Most patterns stop at a quote, so a match stays inside one
|
|
7
|
+
// JSON string. Those that read a name and its value cross only the quotes around its `:`. A
|
|
8
|
+
// credential variable's unquoted value runs over brackets and braces up to a comma, so at a cut it
|
|
9
|
+
// stops, but after the last element it can run over the array's `]`. That is why the text after
|
|
10
|
+
// the array joins the last element.
|
|
11
|
+
//
|
|
12
|
+
// The sandbox URL can cross a cut. Its user info runs over quotes, braces and commas up to an `@`:
|
|
13
|
+
// `https://host"},{"contact":"ops@e2b-host`. From the end of its scheme to one character past
|
|
14
|
+
// `e2b`, that match holds no slash and no whitespace. So a match across a cut starts at a scheme
|
|
15
|
+
// with no slash or whitespace after it up to the cut, and reaches the next piece only up to its
|
|
16
|
+
// first slash or whitespace. Between that scheme and the cut, the match can only be in its user
|
|
17
|
+
// info. The scheme and every `@` there are inside JSON strings, so a host that starts after one
|
|
18
|
+
// stops at its string's closing quote. Had that host reached `e2b` first, the scan of its piece or
|
|
19
|
+
// an earlier junction would have matched. So the scan keeps only the scheme between pieces. Each
|
|
20
|
+
// cut's junction scans the scheme, `"}` in place of the text after it, the cut's `,` and the next
|
|
21
|
+
// piece up to its first slash or whitespace. A public E2B page after the scheme would need a space
|
|
22
|
+
// or the text's end before the cut, so neither text exempts the URL. The scan holds one piece at a
|
|
23
|
+
// time.
|
|
24
|
+
import { isRecord } from "../run/type-guards.js";
|
|
25
|
+
import { containsSandboxUrl, containsSensitive } from "./redaction.js";
|
|
26
|
+
// The sandbox URL's scheme, `https://` or `https:\/\/` as JSON may escape it, at a text's end.
|
|
27
|
+
const SCHEME_AT_END = /https?:\\?\/\\?\/$/i;
|
|
28
|
+
const SLASH_OR_SPACE = /[/\s]/;
|
|
29
|
+
/**
|
|
30
|
+
* containsSensitive(JSON.stringify(value)), with each element of the array `value[key]` serialized
|
|
31
|
+
* and scanned on its own, so no one string holds two elements. Only an array of plain objects is
|
|
32
|
+
* scanned that way. Any other array, and a value, array or element with a toJSON or a prototype of
|
|
33
|
+
* its own, is scanned as the whole text, which can be longer than one string holds. Getters and
|
|
34
|
+
* proxies are not detected, so the answer is the whole text's for values as JSON.parse returns
|
|
35
|
+
* them.
|
|
36
|
+
*/
|
|
37
|
+
export function containsSensitiveJson(value, key) {
|
|
38
|
+
// JSON.stringify writes only own enumerable keys, and `around` cuts the text at this one.
|
|
39
|
+
const elements = isPlainRecord(value) && Object.keys(value).includes(key) ? value[key] : undefined;
|
|
40
|
+
// Array.from reads a hole as undefined, where every would skip it.
|
|
41
|
+
if (!isPlainRecord(value) ||
|
|
42
|
+
!isPlainArray(elements) ||
|
|
43
|
+
!Array.from(elements).every(isPlainRecord))
|
|
44
|
+
return wholeTextSensitive(value);
|
|
45
|
+
const [prefix, suffix] = around(value, key);
|
|
46
|
+
if (elements.length === 0)
|
|
47
|
+
return containsSensitive(prefix + suffix);
|
|
48
|
+
// The scheme of a URL no slash or whitespace has followed, from an earlier piece.
|
|
49
|
+
let scheme;
|
|
50
|
+
for (const [index, element] of elements.entries()) {
|
|
51
|
+
const own = JSON.stringify(element);
|
|
52
|
+
const piece = (index === 0 ? prefix : "") + own + (index === elements.length - 1 ? suffix : "");
|
|
53
|
+
if (containsSensitive(piece))
|
|
54
|
+
return true;
|
|
55
|
+
if (scheme !== undefined && containsSandboxUrl(`${scheme}"},${head(piece)}`))
|
|
56
|
+
return true;
|
|
57
|
+
scheme = schemeAfter(scheme, piece);
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const wholeTextSensitive = (value) => containsSensitive(JSON.stringify(value) ?? "");
|
|
62
|
+
// JSON.stringify calls a toJSON with the value's key in its parent, and an element serialized
|
|
63
|
+
// alone gets "" in place of its index. It writes a boxed primitive as the primitive. A plain object
|
|
64
|
+
// or array with no toJSON is written as its own keys or items wherever it sits.
|
|
65
|
+
const hasToJson = (value) => typeof value.toJSON === "function";
|
|
66
|
+
function isPlainRecord(value) {
|
|
67
|
+
if (!isRecord(value) || hasToJson(value))
|
|
68
|
+
return false;
|
|
69
|
+
const prototype = Object.getPrototypeOf(value);
|
|
70
|
+
return prototype === Object.prototype || prototype === null;
|
|
71
|
+
}
|
|
72
|
+
const isPlainArray = (value) => Array.isArray(value) && Object.getPrototypeOf(value) === Array.prototype && !hasToJson(value);
|
|
73
|
+
/** The document's text before the first element of `value[key]` and after its last. */
|
|
74
|
+
function around(value, key) {
|
|
75
|
+
const keys = Object.keys(value);
|
|
76
|
+
const at = keys.indexOf(key);
|
|
77
|
+
const entries = (from, to) => Object.fromEntries(keys.slice(from, to).map((name) => [name, value[name]]));
|
|
78
|
+
const before = JSON.stringify({ ...entries(0, at), [key]: [] });
|
|
79
|
+
const after = JSON.stringify(entries(at + 1));
|
|
80
|
+
return [before.slice(0, -"]}".length), after === "{}" ? "]}" : `],${after.slice(1)}`];
|
|
81
|
+
}
|
|
82
|
+
/** `piece` up to and including its first slash or whitespace, or all of it when it has none. */
|
|
83
|
+
function head(piece) {
|
|
84
|
+
const at = piece.search(SLASH_OR_SPACE);
|
|
85
|
+
return at === -1 ? piece : piece.slice(0, at + 1);
|
|
86
|
+
}
|
|
87
|
+
/** The scheme a sandbox URL could continue from after `piece`, given the one before it. */
|
|
88
|
+
function schemeAfter(before, piece) {
|
|
89
|
+
let last = piece.length - 1;
|
|
90
|
+
while (last >= 0 && !SLASH_OR_SPACE.test(piece[last]))
|
|
91
|
+
last -= 1;
|
|
92
|
+
if (last === -1)
|
|
93
|
+
return before;
|
|
94
|
+
// `https:\/\/` is the longest scheme: ten characters ending at the slash.
|
|
95
|
+
return SCHEME_AT_END.exec(piece.slice(Math.max(0, last - 9), last + 1))?.[0];
|
|
96
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { collectDesktopRecording } from "../../evidence/desktop-recording-artifact.js";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { participantList } from "../../study/study-fields.js";
|
|
3
4
|
import { inboxRecipientFor, } from "./participant-desktop.js";
|
|
4
5
|
import { createLocalFirecrackerDesktop, } from "../../substrates/local/firecracker-desktop.js";
|
|
5
6
|
import { localBrowserDefaults, localBrowserUnsupportedReason, } from "../../substrates/local/runtime-config.js";
|
|
@@ -139,11 +140,16 @@ function createLocalParticipantDesktop(context, run, warnings, artifactRoot) {
|
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
/** The Codex account participant, whose unconfirmed cleanup blocks the study's analysis. */
|
|
142
|
-
function accountProvider(state,
|
|
143
|
-
return async ({ executor }) => {
|
|
143
|
+
function accountProvider(state, config) {
|
|
144
|
+
return async ({ executor, participant: { index } }) => {
|
|
145
|
+
// The wait settings the participant's loop runs with (plan-participants.ts), so its tool
|
|
146
|
+
// description states them.
|
|
147
|
+
const maxWaitMs = config.actor?.maxWaitMs;
|
|
148
|
+
const idleWaitMs = participantList(config)?.[index]?.idleWaitMs ?? config.actor?.idleWaitMs;
|
|
144
149
|
const participant = createRestrictedCodexParticipant({
|
|
145
150
|
speechEnabled: executor.speechEnabled === true,
|
|
146
151
|
...(maxWaitMs === undefined ? {} : { maxWaitMs }),
|
|
152
|
+
...(idleWaitMs === undefined ? {} : { idleWaitMs }),
|
|
147
153
|
});
|
|
148
154
|
state.participants.push(participant);
|
|
149
155
|
const provider = Object.assign(participant.provider, {
|
|
@@ -185,7 +191,7 @@ export function prepareLocalVmRun(options) {
|
|
|
185
191
|
const { config: _config, assets: _assets, signal: _signal, ...runOptions } = options;
|
|
186
192
|
return {
|
|
187
193
|
options: account
|
|
188
|
-
? { ...runOptions, createProvider: accountProvider(state, config
|
|
194
|
+
? { ...runOptions, createProvider: accountProvider(state, config) }
|
|
189
195
|
: runOptions,
|
|
190
196
|
localVm: {
|
|
191
197
|
desktop: (run, warnings, artifactRoot) => createLocalParticipantDesktop(context, run, warnings, artifactRoot),
|
|
@@ -47,6 +47,9 @@ export async function startParticipantModel(spec, deps, executor) {
|
|
|
47
47
|
...(spec.planned.limits.maxWaitMs === undefined
|
|
48
48
|
? {}
|
|
49
49
|
: { maxWaitMs: spec.planned.limits.maxWaitMs }),
|
|
50
|
+
...(spec.planned.limits.idleWaitMs === undefined
|
|
51
|
+
? {}
|
|
52
|
+
: { idleWaitMs: spec.planned.limits.idleWaitMs }),
|
|
50
53
|
session: { env },
|
|
51
54
|
});
|
|
52
55
|
return { provider: codexParticipant.provider, codexParticipant };
|
|
@@ -16,6 +16,11 @@ export interface BundleEstimate {
|
|
|
16
16
|
readonly runFileBytes: number;
|
|
17
17
|
/** The largest participant trace file, which analysis also reads whole. */
|
|
18
18
|
readonly largestTraceBytes: number;
|
|
19
|
+
/**
|
|
20
|
+
* Every participant's trace file together, whose items the run writer and the Observer page join
|
|
21
|
+
* in one string.
|
|
22
|
+
*/
|
|
23
|
+
readonly tracesBytes: number;
|
|
19
24
|
/** observer/index.html, which verify reads whole. */
|
|
20
25
|
readonly observerPageBytes: number;
|
|
21
26
|
/** Every file, screenshots included. */
|
|
@@ -16,6 +16,13 @@
|
|
|
16
16
|
// observer-data.json leaves the lists out too and was 0.94 to 1.12 of run.json's size on the live
|
|
17
17
|
// runs measured, so it passes 32 MiB only after run.json passes 16 MiB, and the estimate does not
|
|
18
18
|
// check it.
|
|
19
|
+
// The Observer page and the Observer server join every participant's items in one string before
|
|
20
|
+
// gzip. On a hosted desktop the sandbox id scrub (publicSandboxView) does too, on every write of
|
|
21
|
+
// run.json and on a bundle export of the loaded run, which its 25 MiB default --max-bytes refuses
|
|
22
|
+
// first. V8's longest string is 536,870,888 characters
|
|
23
|
+
// on Node 24. A trace file's bytes are at least the characters of its items as compact JSON, so the
|
|
24
|
+
// estimate compares the traces' bytes and may warn a little early.
|
|
25
|
+
import { constants } from "node:buffer";
|
|
19
26
|
import { EVIDENCE_LIMITS } from "../analysis/analysis-limits.js";
|
|
20
27
|
import { RUN_ARTIFACT_MAX_BYTES } from "../run/contained-output.js";
|
|
21
28
|
import { formatDuration } from "../run/projection.js";
|
|
@@ -156,6 +163,7 @@ export function estimateBundle(participants, sessionMs) {
|
|
|
156
163
|
return {
|
|
157
164
|
runFileBytes,
|
|
158
165
|
largestTraceBytes: largestTrace,
|
|
166
|
+
tracesBytes: traces,
|
|
159
167
|
observerPageBytes,
|
|
160
168
|
// observer-data.json is about run.json's size.
|
|
161
169
|
totalBytes: observerPageBytes + screenshots * RATES.screenshotBytes + traces + 2 * runFileBytes,
|
|
@@ -178,6 +186,7 @@ export function estimateBundle(participants, sessionMs) {
|
|
|
178
186
|
export function bundleLimitReason(estimate) {
|
|
179
187
|
const runFile = estimate.runFileBytes > EVIDENCE_LIMITS.sourceBytes;
|
|
180
188
|
const page = estimate.observerPageBytes > RUN_ARTIFACT_MAX_BYTES;
|
|
189
|
+
const joined = estimate.tracesBytes > constants.MAX_STRING_LENGTH;
|
|
181
190
|
const entries = estimate.entries > estimate.entryLimit;
|
|
182
191
|
const passed = [
|
|
183
192
|
...(runFile
|
|
@@ -195,6 +204,11 @@ export function bundleLimitReason(estimate) {
|
|
|
195
204
|
`The Observer page, observer/index.html, may grow to about ${mib(estimate.observerPageBytes)}, past the ${mib(RUN_ARTIFACT_MAX_BYTES)} verify reads in one file.`,
|
|
196
205
|
]
|
|
197
206
|
: []),
|
|
207
|
+
...(joined
|
|
208
|
+
? [
|
|
209
|
+
`All participants' traces together may grow to about ${mib(estimate.tracesBytes)}, past the ${mib(constants.MAX_STRING_LENGTH)} that fits in one string. On a hosted desktop, writing run.json puts them in one string, and so does a bundle export allowed past its 25 MiB default; the Observer page always does. run.json may then stop updating, that export would fail and the page would not build.`,
|
|
210
|
+
]
|
|
211
|
+
: []),
|
|
198
212
|
...(entries
|
|
199
213
|
? [
|
|
200
214
|
`The run may hold about ${count(estimate.entries)} files and folders, past the ${count(estimate.entryLimit)} the public-safety scan reads in a run of ${plural(estimate.basis.participants, "participant")}.`,
|
|
@@ -222,7 +236,7 @@ export function bundleLimitReason(estimate) {
|
|
|
222
236
|
...(runFile ? ["shorten the mission (actor.mission)"] : []),
|
|
223
237
|
];
|
|
224
238
|
// A trace is one participant's, so splitting the study helps only the run's own files.
|
|
225
|
-
if (runFile || page || entries || changes.length === 0)
|
|
239
|
+
if (runFile || page || joined || entries || changes.length === 0)
|
|
226
240
|
changes.push("split the participants across studies");
|
|
227
241
|
const advice = changes.length < 2
|
|
228
242
|
? changes.join("")
|
package/dist/study/types.d.ts
CHANGED
|
@@ -414,10 +414,11 @@ export interface StudyActor {
|
|
|
414
414
|
maxWaitMs?: number;
|
|
415
415
|
/**
|
|
416
416
|
* Computer-use and shared-world: how long a `wait` that names no duration lasts when the
|
|
417
|
-
* participant's turn only waits or takes screenshots,
|
|
418
|
-
* while it watches such a turn, in whole milliseconds
|
|
419
|
-
* maxWaitMs. Default 5000. After an action in the same
|
|
420
|
-
* computer
|
|
417
|
+
* participant's turn only waits or takes screenshots, the shortest such a turn is watched for,
|
|
418
|
+
* and how often the loop looks at the screen while it watches such a turn, in whole milliseconds
|
|
419
|
+
* from 1000 to 600000 and no longer than maxWaitMs. Default 5000. After an action in the same
|
|
420
|
+
* turn such a wait lasts 500 ms. OpenAI's computer tool `wait` never names a duration; its
|
|
421
|
+
* wait_minutes function does.
|
|
421
422
|
*/
|
|
422
423
|
idleWaitMs?: number;
|
|
423
424
|
/**
|
package/dist/verify/verify.js
CHANGED
|
@@ -2,7 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import { listAnalysisExecutions } from "../analysis/store-executions.js";
|
|
3
3
|
import { loadAnalysis } from "../analysis/load.js";
|
|
4
4
|
import { analysisSharingProblems } from "../analysis/sharing.js";
|
|
5
|
-
import {
|
|
5
|
+
import { REDACTED_SANDBOX_ID } from "../evidence/redaction.js";
|
|
6
|
+
import { containsSensitiveJson } from "../evidence/sensitive-json.js";
|
|
6
7
|
import { keyedSandboxIds, readRunSandboxIds } from "../run/sandbox-ids.js";
|
|
7
8
|
import { validatePreparedRunArtifactPaths } from "../run/paths.js";
|
|
8
9
|
import { isReadRunBundleSchema, RUN_BUNDLE_FILE, RUN_BUNDLE_SCHEMA, RUN_BUNDLE_V1_SCHEMA, RUN_BUNDLE_V2_SCHEMA, RUN_BUNDLE_V3_SCHEMA, } from "../run/bundle.js";
|
|
@@ -112,9 +113,10 @@ export async function verifyResolvedRun(cwd, runInput, runPaths) {
|
|
|
112
113
|
addTraceScan(traces, publicSafetyFindings, unscannedArtifacts);
|
|
113
114
|
// JSON escapes can hide a sensitive value from the byte scan while the
|
|
114
115
|
// decoded recording exposes it to Observer, feedback, or analysis input.
|
|
116
|
+
// Each participant is scanned apart: together their traces can pass V8's longest string.
|
|
115
117
|
if (publicSafetyFindings.length < MAX_REPORTED_FINDINGS &&
|
|
116
118
|
recorded !== null &&
|
|
117
|
-
|
|
119
|
+
containsSensitiveJson(recorded, "streams")) {
|
|
118
120
|
publicSafetyFindings.push("sensitive decoded run.json");
|
|
119
121
|
}
|
|
120
122
|
checks.push({
|
|
@@ -176,6 +176,7 @@ export interface ActorTraceItem {
|
|
|
176
176
|
looks: number;
|
|
177
177
|
area?: { x: number; y: number; width: number; height: number }; // pixels, coordinates only
|
|
178
178
|
};
|
|
179
|
+
namedMs?: number; // computer-use wait ui_action: the length the participant named, before any cap
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
export interface ActorCapabilities {
|
|
@@ -387,9 +388,14 @@ request), without provider support, or without remaining time and known budget.
|
|
|
387
388
|
unreported usage remains an unknown cost line. `counts.debriefCalls` is separate
|
|
388
389
|
from interaction turns, and reported usage contributes to aggregate cost.
|
|
389
390
|
|
|
390
|
-
The OpenAI implementation
|
|
391
|
-
|
|
392
|
-
|
|
391
|
+
The OpenAI implementation sends each closing request with tools disabled and no HTTP or
|
|
392
|
+
policy retries. The request sends no `text.format`, so it reads the session from the prompt
|
|
393
|
+
cache, and states the JSON Schema in its input. The reply's JSON object is read from its text,
|
|
394
|
+
also from inside a code fence or after a message of its own. A completed reply with no JSON that
|
|
395
|
+
schema accepts, apart from a refusal, comes back with `closingRetry` set. The loop may then call
|
|
396
|
+
the same method once more, after the checks the first request passed and with a time bound of
|
|
397
|
+
its own, and that request sets the strict schema. `counts.debriefCalls` counts each request. Its
|
|
398
|
+
output limit is 3072 tokens, or the declared `maxOutputTokens` when lower. Stateless/ZDR mode does not offer retrospective
|
|
393
399
|
reporting: its client-carried conversation summarizes its oldest turns past a token
|
|
394
400
|
budget, so it may not hold the whole session a retrospective report needs. It does offer
|
|
395
401
|
the impressions-only request, which reads the carried conversation: its newest
|
|
@@ -110,8 +110,8 @@ with speech, whose heard speech reaches the participant only with a screenshot),
|
|
|
110
110
|
shortened` notice with both durations when it does, and sends the rest as consecutive waits of at
|
|
111
111
|
most 30 seconds. The Codex participant's `humanish_ui` schema leaves a wait's length open for that
|
|
112
112
|
reason; the wire still refuses a single wait over 30 seconds. A wait that names no duration, as
|
|
113
|
-
|
|
114
|
-
in the same turn. A turn that only waits or takes screenshots sends no wait to the desktop: the
|
|
113
|
+
the OpenAI computer tool's `wait` does, reaches the desktop with a length too: 500 ms after an
|
|
114
|
+
action in the same turn. A turn that only waits or takes screenshots sends no wait to the desktop: the
|
|
115
115
|
loop observes the screen every `actor.idleWaitMs` until it changes or the wait's time passes
|
|
116
116
|
(`src/actors/computer-use/loop/watch.ts`), so a waiting participant costs one observe request per
|
|
117
117
|
look.
|
|
@@ -1082,14 +1082,17 @@ Core-owned fields:
|
|
|
1082
1082
|
16 pixels counts once it holds still at a look; one of 24 tiles or fewer once
|
|
1083
1083
|
it has held still for more than 65 s with no tile within 4 tiles of it ticking
|
|
1084
1084
|
or moving with a region of more than 24 tiles
|
|
1085
|
-
(`src/evidence/frame-change.ts`). The first watch
|
|
1086
|
-
length: the named waits' lengths, together up to `actor.maxWaitMs` (else
|
|
1087
|
-
600000), and `idleWaitMs` for each wait that names none
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1085
|
+
(`src/evidence/frame-change.ts`). The first watch's limit is the turn's
|
|
1086
|
+
planned length: the named waits' lengths, together up to `actor.maxWaitMs` (else
|
|
1087
|
+
600000), and `idleWaitMs` for each wait that names none, and at least
|
|
1088
|
+
`idleWaitMs`. Each further one in a row that ended at its limit, or that a
|
|
1089
|
+
change of 24 tiles or fewer woke, lasts twice as long, up to the larger of its
|
|
1090
|
+
planned length and that cap. An OpenAI participant names a length with the
|
|
1091
|
+
`wait_minutes` function tool; its computer tool's `wait` names none.
|
|
1092
|
+
`limitMs` is how long it could last; `askedMs` is what the turn's
|
|
1093
|
+
waits asked for, the same sum with no cap, so it is above `limitMs` when the
|
|
1094
|
+
named waits passed the cap and below it when the watch grew or the named waits
|
|
1095
|
+
were shorter than `idleWaitMs`; `wake` is `changed`, `speech`,
|
|
1093
1096
|
`condition`, `limit`, `clock` (wind-down or end), `stalled` (a look got no
|
|
1094
1097
|
frame; the next observation is a fresh one), or `stopped` (the study budget or
|
|
1095
1098
|
the session's time ran out); `looks` counts its looks; `area` (`x`, `y`,
|
|
@@ -1100,6 +1103,14 @@ Core-owned fields:
|
|
|
1100
1103
|
waits after the one carrying `watch` are titled `wait 0ms`. The look that
|
|
1101
1104
|
ended a watch is the turn's `turn-NN` screenshot; other looks are not kept.
|
|
1102
1105
|
Additive: absent on plain waits, other producers and older bundles
|
|
1106
|
+
- optional `items[].namedMs`, on the computer-use `ui_action` of each wait that
|
|
1107
|
+
named its length: that length in milliseconds, as the participant named it,
|
|
1108
|
+
before `actor.maxWaitMs` or the watch's cap applied. An OpenAI participant
|
|
1109
|
+
names a length only through `wait_minutes`, so its waits with `namedMs` are
|
|
1110
|
+
that tool's calls. Its waits without it are the computer tool's `wait`, a
|
|
1111
|
+
`wait_minutes` call whose minutes were not a finite number above zero, and
|
|
1112
|
+
a call to a function it does not have. Additive: absent on waits that named
|
|
1113
|
+
none, other items and producers, and older bundles
|
|
1103
1114
|
- optional `items[].page` and `finalPage` (`{ origin, path }`, `TracePage` in
|
|
1104
1115
|
`src/actors/contract.ts`): where a computer-use participant's browser was. The loop puts `page`
|
|
1105
1116
|
on the `screenshot` item of each observation that read a URL: `turn-00-start` is the page before
|
|
@@ -1183,8 +1194,9 @@ Core-owned fields:
|
|
|
1183
1194
|
with the outputs that answered it, and the latest reply. Past an estimated
|
|
1184
1195
|
64,000 input tokens it is cut down to 32,000 in one step: the opening
|
|
1185
1196
|
screenshot is dropped first, then the oldest exchanges become lines of a note,
|
|
1186
|
-
sent as an assistant message, that keeps their reasoning summaries, messages
|
|
1187
|
-
and
|
|
1197
|
+
sent as an assistant message, that keeps their reasoning summaries, messages,
|
|
1198
|
+
actions and `wait_minutes` calls as text, with any text that came back with
|
|
1199
|
+
their screenshots or in a function call's output, such as how a wait ended.
|
|
1188
1200
|
Past 32,000 characters the note keeps its first four lines and its newest
|
|
1189
1201
|
ones, and names the turns between them as not listed.
|
|
1190
1202
|
The two newest exchanges are always carried whole, even when they alone pass
|
|
@@ -1199,7 +1211,14 @@ Core-owned fields:
|
|
|
1199
1211
|
`estimatedInputTokens`. The impressions-only request has `kind: impressions`;
|
|
1200
1212
|
the optional `kind` field is absent on participant turns and in older bundles.
|
|
1201
1213
|
Impressions carry the context the next turn would carry, with the same budget
|
|
1202
|
-
and summarization of older turns.
|
|
1214
|
+
and summarization of older turns. The closing report and impressions requests
|
|
1215
|
+
send no `text.format`, so they can read the session from the prompt cache; the
|
|
1216
|
+
request states the JSON Schema in its input instead. A completed reply with no
|
|
1217
|
+
JSON the schema accepts, apart from a refusal, can be sent once more with the
|
|
1218
|
+
strict schema, only after the budget and time checks the first passed and with
|
|
1219
|
+
a time bound of its own. An impressions request sent twice has two entries. The
|
|
1220
|
+
closing report has no entry here. Each closing request that reported usage has
|
|
1221
|
+
its own row in `tokenUsage.turns` after the participant's requests. Billed input per request is in `tokenUsage.turns`. A
|
|
1203
1222
|
participant that ran in `explicit_context` gets a run warning. Absent on other
|
|
1204
1223
|
providers and on earlier bundles
|
|
1205
1224
|
- optional `affordanceUse` (`humanish.affordance-use.v1`): which kind of route this
|