humanish 0.125.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 +16 -0
- 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/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/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 +2 -1
- package/dist/observer/pinned-files.js +2 -2
- package/dist/observer/run-routes.d.ts +1 -1
- package/dist/observer/run-routes.js +38 -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/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/contained-output.d.ts +7 -3
- package/dist/run/contained-output.js +11 -7
- package/dist/run/judge.d.ts +7 -4
- package/dist/run/judge.js +13 -8
- package/dist/run/live-items.d.ts +19 -0
- package/dist/run/live-items.js +28 -8
- package/dist/run/stream-trace.d.ts +25 -4
- package/dist/run/stream-trace.js +55 -17
- package/dist/run/streams.d.ts +16 -0
- 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 +28 -20
- package/dist/study/parse/clock.d.ts +18 -0
- package/dist/study/parse/clock.js +36 -0
- 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/pages.d.ts +7 -0
- package/dist/verify/pages.js +50 -0
- package/dist/verify/verify.js +10 -3
- package/docs/architecture/actor-contract.md +2 -0
- package/docs/architecture/external-public-shared-world.md +4 -1
- package/docs/architecture/observer.md +10 -1
- package/docs/contracts/policy.md +7 -0
- package/docs/contracts/run-bundle.md +31 -1
- package/docs/contracts/schemas.md +72 -3
- package/package.json +2 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { REDACTION_MARKERS } from "../evidence/redaction.js";
|
|
2
|
+
import { isRecord } from "../run/type-guards.js";
|
|
3
|
+
// A recorded page (`items[].page`, `finalPage`) keeps a URL's origin and path only. The text scan
|
|
4
|
+
// finds secret shapes in it as in any trace text, but a query string, a fragment or user info can
|
|
5
|
+
// carry a short token no pattern matches, so a page holding one is a public-safety finding.
|
|
6
|
+
// The markers that can stand for a whole origin, such as a withheld sandbox host.
|
|
7
|
+
const MARKERS = new Set(Object.values(REDACTION_MARKERS));
|
|
8
|
+
// `scheme:` alone (a non-web URL) or `scheme://host[:port]`, with nothing after the host. The URL
|
|
9
|
+
// parser reads a backslash there as a slash, so a backslash counts as a path too.
|
|
10
|
+
const ORIGIN = /^[a-z][a-z0-9+.-]*:(?:\/\/[^/\\?#@]*)?$/i;
|
|
11
|
+
/** What is wrong with a recorded page, or undefined when it holds an origin and a path only. */
|
|
12
|
+
function pageProblem(page) {
|
|
13
|
+
if (!isRecord(page) || typeof page.origin !== "string" || typeof page.path !== "string")
|
|
14
|
+
return "is not an origin and a path";
|
|
15
|
+
if (/[?#]/.test(page.path))
|
|
16
|
+
return "has a query string or fragment in its path";
|
|
17
|
+
if (!MARKERS.has(page.origin) && !ORIGIN.test(page.origin))
|
|
18
|
+
return "has an origin with user info, a path, a query string or a fragment";
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One finding per recorded page that holds more than an origin and a path, in each finished
|
|
23
|
+
* trace's items and summary and each running participant's items. A finding names where the page
|
|
24
|
+
* is, never what it says.
|
|
25
|
+
*/
|
|
26
|
+
export function pageFindings(bundle) {
|
|
27
|
+
const findings = [];
|
|
28
|
+
const check = (where, page) => {
|
|
29
|
+
if (page === undefined)
|
|
30
|
+
return;
|
|
31
|
+
const problem = pageProblem(page);
|
|
32
|
+
if (problem !== undefined)
|
|
33
|
+
findings.push(`${where} ${problem}`);
|
|
34
|
+
};
|
|
35
|
+
// The shape guard admits items that are not records, so each is read as unknown.
|
|
36
|
+
const checkItems = (where, items) => {
|
|
37
|
+
if (!Array.isArray(items))
|
|
38
|
+
return;
|
|
39
|
+
items.forEach((item, index) => {
|
|
40
|
+
if (isRecord(item))
|
|
41
|
+
check(`${where}[${index}].page`, item.page);
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
for (const stream of bundle.streams) {
|
|
45
|
+
checkItems(`${stream.id} items`, stream.actor?.items);
|
|
46
|
+
check(`${stream.id} finalPage`, stream.actor?.finalPage);
|
|
47
|
+
checkItems(`${stream.id} liveActor.items`, stream.liveActor?.items);
|
|
48
|
+
}
|
|
49
|
+
return findings;
|
|
50
|
+
}
|
package/dist/verify/verify.js
CHANGED
|
@@ -16,6 +16,7 @@ import { runNotFoundMessage } from "../run/run-not-found.js";
|
|
|
16
16
|
import { actorVerdictConsistencyFindings, noEngagementActorFindings, validateCodexAppServerEvidence, validateTerminalProductEvidence, } from "./actor.js";
|
|
17
17
|
import { invalidRunEvidenceReferences, MAX_REPORTED_FINDINGS, missingLocalEvidenceArtifacts, rawScreenshotPostureWarnings, rawScreenshotStreamIds, redactedShapeFramePaths, scanRunPublicSafetyArtifacts, scanSourceEvidence, streamScreenshotPaths, unscannedMessage, } from "./artifacts.js";
|
|
18
18
|
import { flatContextWarnings } from "./context-growth.js";
|
|
19
|
+
import { pageFindings } from "./pages.js";
|
|
19
20
|
import { runNotFinished } from "./liveness.js";
|
|
20
21
|
import { costAndReceiptFindings } from "./costs.js";
|
|
21
22
|
import { rerunLineageFindings } from "./rerun.js";
|
|
@@ -229,7 +230,7 @@ function evidenceArtifactsCheck(shaped, problems) {
|
|
|
229
230
|
async function loadTraces(runPaths, stored) {
|
|
230
231
|
const scanned = { sensitive: [], opaque: [] };
|
|
231
232
|
if (!isRunBundle(stored))
|
|
232
|
-
return { bundle: stored, refusedTraces: [], ...scanned };
|
|
233
|
+
return { bundle: stored, refusedTraces: [], ...scanned, pages: [] };
|
|
233
234
|
// Each trace is source evidence: scanned by the bytes it was joined from, apart from the
|
|
234
235
|
// directory walk and any path exemption the walk makes.
|
|
235
236
|
const { bundle, refused } = await loadStreamTraces(runPaths, stored, {
|
|
@@ -239,9 +240,14 @@ async function loadTraces(runPaths, stored) {
|
|
|
239
240
|
scanned[scan === "sensitive" ? "sensitive" : "opaque"].push(file);
|
|
240
241
|
},
|
|
241
242
|
});
|
|
242
|
-
return {
|
|
243
|
+
return {
|
|
244
|
+
bundle,
|
|
245
|
+
refusedTraces: refused.map((error) => error.message),
|
|
246
|
+
...scanned,
|
|
247
|
+
pages: pageFindings(bundle),
|
|
248
|
+
};
|
|
243
249
|
}
|
|
244
|
-
/** The trace scan's results, beside the walk's: each file named once. */
|
|
250
|
+
/** The trace scan's results, beside the walk's: each file named once, then each page finding. */
|
|
245
251
|
function addTraceScan(traces, findings, unscanned) {
|
|
246
252
|
for (const file of traces.sensitive)
|
|
247
253
|
if (!findings.includes(`sensitive text ${file}`))
|
|
@@ -249,6 +255,7 @@ function addTraceScan(traces, findings, unscanned) {
|
|
|
249
255
|
for (const file of traces.opaque)
|
|
250
256
|
if (!unscanned.files.includes(file))
|
|
251
257
|
unscanned.files.push(file);
|
|
258
|
+
findings.push(...traces.pages);
|
|
252
259
|
}
|
|
253
260
|
/** The top-level run.json sections the shape guard requires. */
|
|
254
261
|
const REQUIRED_SECTIONS = [
|
|
@@ -167,6 +167,7 @@ export interface ActorTraceItem {
|
|
|
167
167
|
command?: { text?: string; cwd?: string; exitCode?: number; outputTail?: string };
|
|
168
168
|
screenshotRef?: { path: string; redaction: "blurred" | "ocr_scrubbed" | "none" };
|
|
169
169
|
text?: string; // redacted
|
|
170
|
+
page?: { origin: string; path: string }; // computer-use screenshot items; redacted, no query
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
export interface ActorCapabilities {
|
|
@@ -204,6 +205,7 @@ export interface ActorTrace {
|
|
|
204
205
|
ids: { sessionId?: string; threadId?: string; turnId?: string; model?: string };
|
|
205
206
|
counts: Record<string, number>;
|
|
206
207
|
items: ActorTraceItem[];
|
|
208
|
+
finalPage?: { origin: string; path: string }; // computer-use: the last observation's page
|
|
207
209
|
tokenUsage?: { input?: number; output?: number; total?: number; costUsd?: number };
|
|
208
210
|
estimatedCost?: ActorEstimatedCost; // humanish.actor-estimated-cost.v1 (additive)
|
|
209
211
|
capabilities: ActorCapabilities;
|
|
@@ -120,7 +120,10 @@ The runtime `location.href` (and the 6-char code inside it) is never persisted r
|
|
|
120
120
|
runtime-only; the threaded code flows only into the follower's composed prompt (never a raw bundle
|
|
121
121
|
field) and is scrubbed from all narration once latched; the shared origin and lobby path persist only
|
|
122
122
|
as sha256-16 digests (`publicOriginDigest`, `lobbyConvergenceDigest`). This matches the existing
|
|
123
|
-
e2b-URL / host-digest redaction discipline.
|
|
123
|
+
e2b-URL / host-digest redaction discipline. The computer-use loop records each observation's page
|
|
124
|
+
origin and path by default; the shared-world runner (`participantRunDeps` in
|
|
125
|
+
`src/routes/shared-world/participant-specs.ts`) runs every session with `recordPages: false`, on
|
|
126
|
+
both planes, so no page and no `finalPage` reaches a shared-world trace.
|
|
124
127
|
|
|
125
128
|
## Mobile fidelity caveat
|
|
126
129
|
|
|
@@ -74,7 +74,16 @@ public-safe metadata.
|
|
|
74
74
|
|
|
75
75
|
The browser polls `observer-feed.json` with `no-store` caching: the server's
|
|
76
76
|
projection of the run as `{ data, items }`, observer-data.json's content and the
|
|
77
|
-
items, from one read of the run per poll.
|
|
77
|
+
items, from one read of the run per poll. While a participant runs, its entry in
|
|
78
|
+
`items` carries `live`, a cursor `<position>.<generation>.<items>.<bytes>` naming its
|
|
79
|
+
live file (`live/<position>.<generation>.ndjson`) and how far the list goes. The page
|
|
80
|
+
sends the cursors of its last body back as `?after=<cursor>,<cursor>`, and the server
|
|
81
|
+
then reads and sends only the items past each one, marking that entry with
|
|
82
|
+
`liveAfter`, the cursor they follow (`src/observer/feed-cursors.ts`). A cursor for an
|
|
83
|
+
earlier generation, past the index or off a line end gets the participant's full list,
|
|
84
|
+
and so does every participant of a finished run. A tail is checked for line ends and
|
|
85
|
+
its line count; the sha256 run.json records is checked whenever a list is read whole.
|
|
86
|
+
A page that cannot join a body sends no cursors with its next poll. Static
|
|
78
87
|
`file://` opening still works for immutable review, but follow mode is the
|
|
79
88
|
operator path. Agents and CI should use `humanish watch --json --no-open` for
|
|
80
89
|
the same fresh evidence without browser open or a long-running process.
|
package/docs/contracts/policy.md
CHANGED
|
@@ -146,6 +146,13 @@ certification that the artifact is free of every class in the policy below. A
|
|
|
146
146
|
first-class PII/PHI detector is [planned](https://github.com/danielgwilson/humanish/issues/108) and would move the listed
|
|
147
147
|
PII classes from author-responsibility to enforced.
|
|
148
148
|
|
|
149
|
+
The same scan reads the page each computer-use observation recorded (`items[].page`,
|
|
150
|
+
`finalPage`). verify also reports a page whose path holds a query string or fragment, or whose
|
|
151
|
+
origin holds user info or anything after the host, since such a part can carry a short token no
|
|
152
|
+
pattern matches. The recorder withholds email- and token-shaped path segments
|
|
153
|
+
([schemas.md](schemas.md#actor-trace)); a name or other personal data in a readable path is not
|
|
154
|
+
detected.
|
|
155
|
+
|
|
149
156
|
Required redaction gates:
|
|
150
157
|
|
|
151
158
|
- run bundle verification;
|
|
@@ -332,7 +332,7 @@ An actor trace may include `stopCause` alongside its unchanged `status`,
|
|
|
332
332
|
`completionReason` and verbatim `reason`. Computer-use sessions distinguish
|
|
333
333
|
`provider_output_limit`, `provider_token_limit`, `time_limit`, `spend_limit`,
|
|
334
334
|
`study_spend_limit`, `adapter_limit`, `provider_incomplete`, `provider_status`,
|
|
335
|
-
`provider_refused_prompt`, `harness_aborted`, `usage_unreported`, and `
|
|
335
|
+
`provider_refused_prompt`, `harness_aborted`, `usage_unreported`, `scheduled_end`, and `session_cap`. Absence means the route or
|
|
336
336
|
recording did not retain this precise field; it does not mean the participant
|
|
337
337
|
finished.
|
|
338
338
|
|
|
@@ -454,6 +454,36 @@ contain the older requested-screen `stream.viewport` shape without `desktopGeome
|
|
|
454
454
|
Deterministic Playwright-style adapters remain free to declare a viewport that they also render
|
|
455
455
|
exactly; this hosted-desktop rule does not change that contract.
|
|
456
456
|
|
|
457
|
+
## Desktop Files
|
|
458
|
+
|
|
459
|
+
A hosted computer-use participant's stream carries `desktopFiles` when its study gave it
|
|
460
|
+
`participants[].files`: one entry per file confirmed on the desktop before the participant
|
|
461
|
+
started, in the order they landed.
|
|
462
|
+
|
|
463
|
+
```yaml
|
|
464
|
+
desktopFiles:
|
|
465
|
+
- as: Downloads/insurance-card.png
|
|
466
|
+
size: 251
|
|
467
|
+
sha256: <64 hex characters>
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
`as` is relative to the desktop user's home folder. `size` is the byte count humanish copied,
|
|
471
|
+
which `files.getInfo` on the desktop matched. `sha256` is the digest of those bytes, read on the
|
|
472
|
+
host before any sandbox existed. The contents and the `from` path are not recorded. A participant
|
|
473
|
+
whose copy failed records the files confirmed before the failure. No `desktopFiles` means no file
|
|
474
|
+
was confirmed on that desktop: the study gave the participant none, the run was a dry run, or the
|
|
475
|
+
copy failed before the first file was confirmed. A copy that failed records a `warn` event of type
|
|
476
|
+
`cua-lab.desktop.files.completed`.
|
|
477
|
+
|
|
478
|
+
## Participant Time Zone
|
|
479
|
+
|
|
480
|
+
A computer-use or shared-world participant's stream carries `timeZone`, a tz database name such as
|
|
481
|
+
`Europe/London`, when its study gave it one: `participants[].clock.timeZone`, else
|
|
482
|
+
`actor.clock.timeZone`. Its turns gave the local time in that zone, and on a hosted desktop its
|
|
483
|
+
browser launched with `TZ` set to it. The field records the declared zone, also on a dry run; it is
|
|
484
|
+
not read back from the browser. No `timeZone` means the participant had none, and its hosted
|
|
485
|
+
browser ran in the desktop's UTC.
|
|
486
|
+
|
|
457
487
|
## Subject Provenance
|
|
458
488
|
|
|
459
489
|
`subject` is an optional, additive top-level field: structured provenance for
|
|
@@ -210,6 +210,24 @@ The rest of this section names keys by their v3 spelling:
|
|
|
210
210
|
Public/non-loopback targets still require `policies.allowPublicTargets: true`.
|
|
211
211
|
`target` is mutually exclusive with `entry`: `target` is an absolute app-url
|
|
212
212
|
browser target; `entry` is a shared-world participant's same-origin path;
|
|
213
|
+
- `participants[].files` (computer-use on `execution.target: e2b-desktop` only):
|
|
214
|
+
1 to 20 `{ from, as }` entries, each a file copied onto that participant's desktop
|
|
215
|
+
before its browser opens. `from` is relative to the folder that holds the study file
|
|
216
|
+
(`cwd` for a library config); `as` is relative to the desktop user's home folder.
|
|
217
|
+
The parser refuses an absolute path, a `.`, `..` or empty part, a backslash, an `as`
|
|
218
|
+
that starts with `~`, lands in `subject/` or on a name starting with `.humanish`, looks
|
|
219
|
+
like a secret, a local path or encoded text, repeats another `as` or is its folder,
|
|
220
|
+
and a `from` the local-tree denylist names in any case (`isLocalTreeDenylisted` in
|
|
221
|
+
`src/subject/local-tree-archive.ts`). The route also refuses an `as` that holds a
|
|
222
|
+
value from the run's environment, such as a key. The route reads each distinct `from` once, before
|
|
223
|
+
any sandbox and on dry runs too, with `readContainedRegularFile`: a missing file, a
|
|
224
|
+
folder, a symbolic or hard link, a path out of the folder, a file above 10 MiB, more
|
|
225
|
+
than 25 MiB for one participant or 100 MiB of distinct files for the study is
|
|
226
|
+
refused with `HUMANISH_COMPUTER_USE_DESKTOP_FILES_INVALID` (`readDesktopFiles` in
|
|
227
|
+
`src/study/desktop-files.ts`). Each file
|
|
228
|
+
is written with the E2B SDK's `files.write` and its `files.getInfo` size must match, or
|
|
229
|
+
the participant fails before its first turn. The stream records `desktopFiles`
|
|
230
|
+
([run-bundle.md](run-bundle.md#desktop-files)). Other routes refuse the field;
|
|
213
231
|
- a `participants` entry with `count: n` (computer-use E2B route): a group of
|
|
214
232
|
repeated participants. It needs an `id` and the parser expands it to
|
|
215
233
|
`<id>-01` … `<id>-NN`, even when n is 1, before the engine runs (`viewer-01`,
|
|
@@ -235,17 +253,42 @@ The rest of this section names keys by their v3 spelling:
|
|
|
235
253
|
participant's session budget is its window plus 5 minutes (`participantSessionMs` in
|
|
236
254
|
`src/study/arrivals.ts`) in place of `execution.timeoutMs`, and its sandbox deadline adds the
|
|
237
255
|
route's headroom to that. At the end the loop requests the closing report (`debrief.trigger:
|
|
238
|
-
scheduled_end`) and the session ends `goal_satisfied` with `stopCause: scheduled_end`.
|
|
256
|
+
scheduled_end`) and the session ends `goal_satisfied` with `stopCause: scheduled_end`. The
|
|
257
|
+
outcome an accepted report declares, its `outcome` field or the first line of its summary, is
|
|
258
|
+
the trace's `declaredOutcome`. The blocker rule reads a declared `reached` or `blocked` before
|
|
259
|
+
the summary, and after a declared `not_reached` it reads the summary. Before
|
|
239
260
|
the end, a turn that requests no action does not end the session: the trace records a
|
|
240
261
|
`participant ended before its scheduled end` notice, the next request says the time left, and
|
|
241
262
|
the turn counts toward the idle backstop. Before the end, a turn that only waits or takes
|
|
242
263
|
screenshots counts toward neither backstop streak (`advanceBackstop`'s `waitingIsTheTask`). The
|
|
243
264
|
plan entry carries `endAfterMs`, and `study check` prints a `windows` row;
|
|
265
|
+
- `participants[].maxSessionMs` (computer-use only, also on `count:` groups): the longest the
|
|
266
|
+
participant's session lasts, in whole milliseconds from 1 to 86,400,000, counted from the start
|
|
267
|
+
of its own session. `windDownMs` applies to it as to `endAfterMs`. Refused: a value outside that
|
|
268
|
+
range, `maxSessionMs` beside `endAfterMs` on one participant, `maxSessionMs` on `shared-world`,
|
|
269
|
+
and a cap whose sandbox deadline passes the sandbox ceiling. The session budget is the cap plus
|
|
270
|
+
5 minutes (`participantSessionMs`) in place of `execution.timeoutMs`, and the schedule holds the
|
|
271
|
+
participant's slot that long. The route reads the cap's end when the session starts
|
|
272
|
+
(`participantEnd` in `src/study/arrivals.ts`, `participantClock` in
|
|
273
|
+
`src/routes/computer-use/participant-model.ts`) and passes it to the loop as
|
|
274
|
+
`clock.end` with `kind: session_cap`. The participant may end its session before the cap, and
|
|
275
|
+
its waiting turns count toward the backstops. At the cap the loop requests the closing report
|
|
276
|
+
(`debrief.trigger: session_cap`) and the session ends `goal_satisfied` with
|
|
277
|
+
`stopCause: session_cap`, its report's declared outcome read as at a scheduled end. The plan
|
|
278
|
+
entry carries `maxSessionMs`, and the plan line shows
|
|
279
|
+
`cap=<duration>`;
|
|
244
280
|
- `participants[].idleWaitMs` (computer-use and shared-world, also on `count:` groups): this
|
|
245
281
|
participant's `actor.idleWaitMs`, in the same range and refused above an explicit
|
|
246
282
|
`actor.maxWaitMs` (`waitLimitValidationReason`). Its trace records it in `waitSettings`;
|
|
247
|
-
- `actor.clock.timeZone` (computer-use): a tz database time zone name, such as `Europe/London`. Each turn's context hint carries
|
|
248
|
-
the local time in it, and a scheduled end is given in it (UTC without it)
|
|
283
|
+
- `actor.clock.timeZone` (computer-use and shared-world): a tz database time zone name, such as `Europe/London`. Each turn's context hint carries
|
|
284
|
+
the local time in it, and a scheduled end or session cap is given in it (UTC without it). On a hosted desktop the
|
|
285
|
+
participant's browser launches with `TZ` set to it (`openParticipantSurface` in
|
|
286
|
+
`src/routes/computer-use/e2b-desktop/start.ts`). `parseClock` in `src/study/parse/clock.ts` refuses a name Intl does not know, a
|
|
287
|
+
UTC offset and a case variant of Intl's spelling, which Chrome's ICU reads as UTC or `Etc/Unknown`. The computer-use planner
|
|
288
|
+
refuses a zone on the local runtime and the in-process route with `HUMANISH_COMPUTER_USE_TIME_ZONE_UNSUPPORTED`;
|
|
289
|
+
- `participants[].clock.timeZone` (computer-use and shared-world, also on `count:` groups): this participant's `actor.clock`,
|
|
290
|
+
with the same rule. The plan's `limits.timeZone` is the participant's zone, else the actor's. `study check` prints a
|
|
291
|
+
`time zones` row, and each participant's stream in `run.json` records `timeZone`;
|
|
249
292
|
- `execution.concurrency` (computer-use E2B routes, including shared-world): a
|
|
250
293
|
cap on participants in flight at once. When omitted, every participant runs
|
|
251
294
|
simultaneously: both routes resolve it when they plan, from the final participant count
|
|
@@ -1011,6 +1054,32 @@ Core-owned fields:
|
|
|
1011
1054
|
click-like `ui_action` items, structured `coord` (`x`/`y`). Both are additive:
|
|
1012
1055
|
absent on older bundles and non-stamping producers, and absence means
|
|
1013
1056
|
"timing/position unrecorded", never zero
|
|
1057
|
+
- optional `items[].page` and `finalPage` (`{ origin, path }`, `TracePage` in
|
|
1058
|
+
`src/actors/contract.ts`): where a computer-use participant's browser was. The loop puts `page`
|
|
1059
|
+
on the `screenshot` item of each observation that read a URL: `turn-00-start` is the page before
|
|
1060
|
+
any action, `turn-01` the page after turn 1's actions, and dwell frames have their own.
|
|
1061
|
+
`finalPage` is the page of the session's last observation, which is a closing observation with
|
|
1062
|
+
no item when tasks are declared; it is absent when that observation read no URL. The screenshot
|
|
1063
|
+
items with a `page`, in item order, then `finalPage`, list the participant's path through the
|
|
1064
|
+
app. `origin` is the URL's scheme, host and port; `path` is its percent-encoded path. A query
|
|
1065
|
+
string, fragment and user info are never kept, and a non-web URL keeps its scheme only
|
|
1066
|
+
(`{ origin: "about:", path: "" }`). Redaction (`tracePage` in `src/evidence/trace-page.ts`):
|
|
1067
|
+
a path segment becomes `[REDACTED_URL_PART]` when it percent-encodes a letter, digit, `-`, `.`,
|
|
1068
|
+
`_`, `~` or `%`, or when any decoded reading of it is email-shaped, holds a run of 16 or more letters,
|
|
1069
|
+
digits, `-` and `_` with a piece of 8 or more characters mixing letters and digits, or changes
|
|
1070
|
+
under the trace text redaction (the study's secrets, then `redactText`). The whole path then
|
|
1071
|
+
goes through that redaction, is cut to 1024 characters ending `[TRUNCATED]`, and is withheld
|
|
1072
|
+
whole when verify's trace scan would still read it as sensitive or opaque. A host label
|
|
1073
|
+
shaped like that token run becomes `[REDACTED_URL_PART]`, then the origin goes through the same
|
|
1074
|
+
redaction (an E2B sandbox host becomes `[REDACTED_SECRET]`). When origin and path together
|
|
1075
|
+
still change under that redaction (a study secret that is a whole link), both become
|
|
1076
|
+
`[REDACTED_URL_PART]`. A token shorter than 16 characters, an
|
|
1077
|
+
all-digit id and personal data in readable words stay, so the sharing rules for synthetic data
|
|
1078
|
+
still apply. Pages come from the browser-state probe, which hosted runs have on Chrome and
|
|
1079
|
+
Chromium only; a Firefox participant, or one whose probe is unavailable, has screenshots without
|
|
1080
|
+
pages. The local browser route and the shared-world route record none. verify reports a page
|
|
1081
|
+
whose path holds `?` or `#`, or whose origin is neither a redaction marker nor a scheme with an
|
|
1082
|
+
optional host and port, as a public-safety finding. Both fields are additive; absence means "page not recorded"
|
|
1014
1083
|
- optional `modelSettings` (`humanish.model-settings.v1`): how the model was
|
|
1015
1084
|
asked to run, alongside `ids.model` which says which model it was.
|
|
1016
1085
|
`reasoningEffort` records the value the request actually carried,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "humanish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.126.0",
|
|
4
4
|
"description": "Synthetic user research for apps, CLIs, and agent-facing product flows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"comms:agentmail:contract": "node --import tsx scripts/agentmail-contract-check.mjs",
|
|
97
97
|
"format": "oxfmt",
|
|
98
98
|
"format:check": "oxfmt --check",
|
|
99
|
-
"lint": "node scripts/check-lint-cap.mjs --max-warnings=
|
|
99
|
+
"lint": "node scripts/check-lint-cap.mjs --max-warnings=411",
|
|
100
100
|
"knip": "knip",
|
|
101
101
|
"prose:check": "node scripts/check-code-prose.mjs",
|
|
102
102
|
"vocabulary:check": "tsx scripts/check-retired-words.ts",
|