humanish 0.125.0 → 0.127.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -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 +10 -5
- package/dist/actors/computer-use/loop/ending.js +12 -7
- 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/computer-use/openai-provider.js +1 -1
- package/dist/actors/contract.d.ts +31 -8
- package/dist/actors/contract.js +3 -0
- package/dist/actors/goal-source.d.ts +32 -2
- package/dist/actors/goal-source.js +41 -2
- package/dist/actors/stop-cause.js +1 -0
- package/dist/analysis/evidence-sources.js +2 -2
- package/dist/analysis/types.d.ts +2 -2
- package/dist/analysis/validation.js +2 -1
- package/dist/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/data.js +8 -5
- 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-bundle.js +3 -0
- package/dist/routes/computer-use/fanout-records.js +5 -1
- 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-execution.js +6 -2
- package/dist/routes/computer-use/participant-facts.js +4 -1
- package/dist/routes/computer-use/participant-model.js +16 -6
- package/dist/routes/computer-use/participant-runs.d.ts +10 -10
- package/dist/routes/computer-use/participant-runs.js +54 -84
- package/dist/routes/computer-use/plan.d.ts +2 -0
- package/dist/routes/computer-use/plan.js +65 -17
- package/dist/routes/computer-use/rerun-selection.js +8 -5
- package/dist/routes/computer-use/setup.d.ts +1 -0
- package/dist/routes/computer-use/setup.js +29 -1
- package/dist/routes/computer-use/single-bundle.d.ts +5 -1
- package/dist/routes/computer-use/single-bundle.js +11 -1
- package/dist/routes/computer-use/types.d.ts +18 -13
- package/dist/routes/computer-use/types.js +0 -3
- package/dist/routes/shared-world/bundle-records.js +4 -1
- package/dist/routes/shared-world/external-public.js +1 -1
- package/dist/routes/shared-world/participant-specs.js +3 -2
- package/dist/routes/shared-world/plan.d.ts +2 -0
- package/dist/routes/shared-world/plan.js +8 -3
- package/dist/routes/shared-world/provisioned.js +1 -1
- package/dist/run/bundle.d.ts +11 -1
- package/dist/run/contained-output.d.ts +7 -3
- package/dist/run/contained-output.js +11 -7
- package/dist/run/judge.d.ts +14 -4
- package/dist/run/judge.js +17 -12
- package/dist/run/live-items.d.ts +19 -0
- package/dist/run/live-items.js +28 -8
- package/dist/run/notes.js +1 -1
- package/dist/run/outcomes.d.ts +8 -2
- package/dist/run/outcomes.js +56 -14
- 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 +105 -34
- package/dist/study/arrivals.js +179 -74
- package/dist/study/bundle-estimate.d.ts +12 -10
- package/dist/study/bundle-estimate.js +138 -72
- 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 -10
- package/dist/study/plan-participants.js +13 -9
- package/dist/study/plan-types.d.ts +6 -1
- package/dist/study/plan.d.ts +2 -1
- package/dist/study/plan.js +28 -15
- package/dist/study/preflight.js +4 -0
- package/dist/study/types.d.ts +35 -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/lifetime.d.ts +22 -8
- package/dist/substrates/e2b/lifetime.js +54 -10
- 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 +93 -11
- package/package.json +2 -2
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The clock in the hosted desktop's panel. The stock template's XFCE panel sits above every window,
|
|
2
|
+
// so a participant sees its clock in each screenshot, and it shows the desktop's zone, UTC, unless
|
|
3
|
+
// its clock plugin names another.
|
|
4
|
+
import { shellQuote } from "../shell.js";
|
|
5
|
+
import { e2bShell } from "./shell.js";
|
|
6
|
+
/**
|
|
7
|
+
* Set every clock in the desktop's XFCE panel to `timeZone`, a tz database name, by writing the
|
|
8
|
+
* clock plugin's `timezone` property through xfconf-query. The property lives in the session's
|
|
9
|
+
* settings daemon, reached over the session bus whose address xfce4-session holds, and the panel
|
|
10
|
+
* redraws its clock when the property changes (live: 02:12 UTC became 11:12 for Asia/Tokyo with no
|
|
11
|
+
* restart). A desktop without xfconf-query or without a clock plugin is left as it is. Throws with
|
|
12
|
+
* xfconf-query's error output when a clock is found and cannot be set.
|
|
13
|
+
*/
|
|
14
|
+
export async function setPanelClockZone(desktop, timeZone, requestTimeoutMs) {
|
|
15
|
+
const result = await e2bShell(desktop).run([
|
|
16
|
+
`zone=${shellQuote(timeZone)}`,
|
|
17
|
+
"command -v xfconf-query >/dev/null 2>&1 || exit 0",
|
|
18
|
+
'session_pid="$(pgrep -x xfce4-session | head -n 1)"',
|
|
19
|
+
'if [ -n "$session_pid" ]; then',
|
|
20
|
+
` bus="$(tr '\\0' '\\n' < "/proc/$session_pid/environ" | sed -n 's/^DBUS_SESSION_BUS_ADDRESS=//p')"`,
|
|
21
|
+
' if [ -n "$bus" ]; then export DBUS_SESSION_BUS_ADDRESS="$bus"; fi',
|
|
22
|
+
"fi",
|
|
23
|
+
`for plugin in $(xfconf-query -c xfce4-panel -p /plugins -l -v | awk '$2 == "clock" { print $1 }'); do`,
|
|
24
|
+
' xfconf-query -c xfce4-panel -p "$plugin/timezone" -n -t string -s "$zone" || exit 1',
|
|
25
|
+
"done",
|
|
26
|
+
].join("\n"), { requestTimeoutMs, timeoutMs: 15_000 });
|
|
27
|
+
if (result.exitCode !== 0)
|
|
28
|
+
throw new Error(result.stderr.trim() || `xfconf-query exited ${result.exitCode}`);
|
|
29
|
+
}
|
|
@@ -45,14 +45,24 @@ export type ConcurrentSandboxes = {
|
|
|
45
45
|
export declare function concurrentSandboxes(env: Readonly<Record<string, string | undefined>>): ConcurrentSandboxes;
|
|
46
46
|
/** The limit in use and where it came from, or why the setting cannot be read (doctor's row). */
|
|
47
47
|
export declare function describeConcurrentSandboxes(limit: ConcurrentSandboxes): string;
|
|
48
|
+
/** The setting that lowers how many participants a study runs at once, on any desktop. */
|
|
49
|
+
export declare const CUA_MAX_CONCURRENCY_ENV = "HUMANISH_CUA_MAX_CONCURRENCY";
|
|
48
50
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
*
|
|
51
|
+
* HUMANISH_CUA_MAX_CONCURRENCY when it is a whole number of 1 or more, else undefined: the
|
|
52
|
+
* operator's own bound on participants at once on this machine, which may only lower a study's.
|
|
53
|
+
* Any other value is ignored.
|
|
54
|
+
*/
|
|
55
|
+
export declare function concurrencyCap(env: Readonly<Record<string, string | undefined>>): number | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* How many participants a study runs at once: its `concurrency` (the declared
|
|
58
|
+
* execution.concurrency clamped to the roster, else every participant), lowered to `cap`
|
|
59
|
+
* (concurrencyCap) and then to what the E2B plan runs (`sandboxes`, on a hosted desktop).
|
|
60
|
+
* `declared` says whether the study set it. `alongside` counts the sandboxes the study keeps for
|
|
61
|
+
* the whole run besides its participants' desktops, such as a shared world's app, and `minimum` is
|
|
62
|
+
* the fewest at once the route can run. A declared value the E2B plan cannot run, or a cap under
|
|
63
|
+
* `minimum`, is a refusal naming the setting. `lowered` is the warning a plan records when the cap
|
|
64
|
+
* or the E2B limit lowers the count; when both do, it is the E2B limit's, since that sets the
|
|
65
|
+
* count. `loweredFrom` is the count the cap lowered, when the cap sets the count.
|
|
56
66
|
*/
|
|
57
67
|
export declare function participantsAtOnce(study: {
|
|
58
68
|
readonly concurrency: number;
|
|
@@ -60,10 +70,14 @@ export declare function participantsAtOnce(study: {
|
|
|
60
70
|
readonly participants: number;
|
|
61
71
|
readonly alongside?: number;
|
|
62
72
|
readonly minimum?: number;
|
|
63
|
-
},
|
|
73
|
+
}, limits: {
|
|
74
|
+
readonly sandboxes?: ConcurrentSandboxes;
|
|
75
|
+
readonly cap?: number | undefined;
|
|
76
|
+
}): {
|
|
64
77
|
readonly ok: true;
|
|
65
78
|
readonly count: number;
|
|
66
79
|
readonly lowered?: string;
|
|
80
|
+
readonly loweredFrom?: number;
|
|
67
81
|
} | {
|
|
68
82
|
readonly ok: false;
|
|
69
83
|
readonly message: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The operator's E2B plan limits humanish plans against: how long a sandbox may live and how many
|
|
2
|
-
// run at once
|
|
2
|
+
// run at once, with the operator's own bound on participants at once (HUMANISH_CUA_MAX_CONCURRENCY).
|
|
3
|
+
// Also the budgets the desktop routes add to a participant's own deadline when they set
|
|
3
4
|
// a sandbox's server-side timeout. A dead host process can never orphan a sandbox past that
|
|
4
5
|
// timeout, so every route derives it from these values.
|
|
5
6
|
import { plural } from "../../run/text.js";
|
|
@@ -88,19 +89,62 @@ export function describeConcurrentSandboxes(limit) {
|
|
|
88
89
|
if (!limit.ok)
|
|
89
90
|
return limit.message;
|
|
90
91
|
return limit.source === "setting"
|
|
91
|
-
? `${limit.count} at once, from ${CONCURRENCY_SETTING}. A study that runs more desktops at once runs them in waves, and an execution.concurrency above it is refused before any sandbox is created.`
|
|
92
|
+
? `${limit.count} at once, from ${CONCURRENCY_SETTING}. A study that runs more desktops at once runs them in waves, and an execution.concurrency above it is refused before any sandbox is created, unless ${CUA_MAX_CONCURRENCY_ENV} lowers it to the limit or below.`
|
|
92
93
|
: `${limit.count} at once, E2B's Hobby limit. On a plan that runs more (Pro runs ${PRO_CONCURRENT_SANDBOXES}), set ${CONCURRENCY_SETTING} to that plan's limit.`;
|
|
93
94
|
}
|
|
95
|
+
/** The setting that lowers how many participants a study runs at once, on any desktop. */
|
|
96
|
+
export const CUA_MAX_CONCURRENCY_ENV = "HUMANISH_CUA_MAX_CONCURRENCY";
|
|
94
97
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* for the whole run besides its participants' desktops, such as a shared world's app, and
|
|
99
|
-
* `minimum` is the fewest at once the route can run. A declared value that does not fit is a
|
|
100
|
-
* refusal naming the setting value that admits it. `lowered` is the warning a plan records when
|
|
101
|
-
* the limit holds an undeclared default to waves.
|
|
98
|
+
* HUMANISH_CUA_MAX_CONCURRENCY when it is a whole number of 1 or more, else undefined: the
|
|
99
|
+
* operator's own bound on participants at once on this machine, which may only lower a study's.
|
|
100
|
+
* Any other value is ignored.
|
|
102
101
|
*/
|
|
103
|
-
export function
|
|
102
|
+
export function concurrencyCap(env) {
|
|
103
|
+
const cap = readPositiveInt(env[CUA_MAX_CONCURRENCY_ENV], 0);
|
|
104
|
+
return cap > 0 ? cap : undefined;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* How many participants a study runs at once: its `concurrency` (the declared
|
|
108
|
+
* execution.concurrency clamped to the roster, else every participant), lowered to `cap`
|
|
109
|
+
* (concurrencyCap) and then to what the E2B plan runs (`sandboxes`, on a hosted desktop).
|
|
110
|
+
* `declared` says whether the study set it. `alongside` counts the sandboxes the study keeps for
|
|
111
|
+
* the whole run besides its participants' desktops, such as a shared world's app, and `minimum` is
|
|
112
|
+
* the fewest at once the route can run. A declared value the E2B plan cannot run, or a cap under
|
|
113
|
+
* `minimum`, is a refusal naming the setting. `lowered` is the warning a plan records when the cap
|
|
114
|
+
* or the E2B limit lowers the count; when both do, it is the E2B limit's, since that sets the
|
|
115
|
+
* count. `loweredFrom` is the count the cap lowered, when the cap sets the count.
|
|
116
|
+
*/
|
|
117
|
+
export function participantsAtOnce(study, limits) {
|
|
118
|
+
const capped = cappedAtOnce(study, limits.cap);
|
|
119
|
+
if (!capped.ok || limits.sandboxes === undefined)
|
|
120
|
+
return capped;
|
|
121
|
+
// A count the cap lowered is the operator's bound and no longer the study's declared value, so
|
|
122
|
+
// the E2B limit runs it in waves as it would an undeclared default.
|
|
123
|
+
const left = capped.loweredFrom === undefined
|
|
124
|
+
? study
|
|
125
|
+
: { ...study, concurrency: capped.count, declared: false };
|
|
126
|
+
const planned = sandboxesAtOnce(left, limits.sandboxes);
|
|
127
|
+
return !planned.ok || planned.count < capped.count ? planned : capped;
|
|
128
|
+
}
|
|
129
|
+
/** participantsAtOnce under the cap alone. */
|
|
130
|
+
function cappedAtOnce(study, cap) {
|
|
131
|
+
if (cap === undefined || cap >= study.concurrency)
|
|
132
|
+
return { ok: true, count: study.concurrency };
|
|
133
|
+
const minimum = study.minimum ?? 1;
|
|
134
|
+
if (cap < minimum)
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
message: `This study runs at least ${minimum} participants at once, and ${CUA_MAX_CONCURRENCY_ENV} allows ${cap}. Set it to ${minimum} or more, or unset it.`,
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
ok: true,
|
|
141
|
+
count: cap,
|
|
142
|
+
loweredFrom: study.concurrency,
|
|
143
|
+
lowered: `This study's ${study.participants} participants run ${cap} at a time, because ${CUA_MAX_CONCURRENCY_ENV} is ${cap}.`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** participantsAtOnce under the E2B plan's limit alone. */
|
|
147
|
+
function sandboxesAtOnce(study, limit) {
|
|
104
148
|
if (!limit.ok)
|
|
105
149
|
return limit;
|
|
106
150
|
const alongside = study.alongside ?? 0;
|
|
@@ -154,6 +154,12 @@ export interface E2BDesktopSandbox {
|
|
|
154
154
|
requestTimeoutMs?: number;
|
|
155
155
|
useOctetStream?: boolean;
|
|
156
156
|
}): Promise<unknown>;
|
|
157
|
+
/** A file's size as the sandbox sees it. A relative path is under the sandbox user's home. */
|
|
158
|
+
getInfo?(path: string, options?: {
|
|
159
|
+
requestTimeoutMs?: number;
|
|
160
|
+
}): Promise<{
|
|
161
|
+
size: number;
|
|
162
|
+
}>;
|
|
157
163
|
};
|
|
158
164
|
launch(application: string, uri?: string): Promise<void>;
|
|
159
165
|
/** Open a file or URL with the desktop's default application (present on @e2b/desktop >= 1.x). */
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RunBundle } from "../run/bundle.js";
|
|
2
|
+
/**
|
|
3
|
+
* One finding per recorded page that holds more than an origin and a path, in each finished
|
|
4
|
+
* trace's items and summary and each running participant's items. A finding names where the page
|
|
5
|
+
* is, never what it says.
|
|
6
|
+
*/
|
|
7
|
+
export declare function pageFindings(bundle: RunBundle): string[];
|
|
@@ -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/photo.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`,
|
|
@@ -229,23 +247,51 @@ The rest of this section names keys by their v3 spelling:
|
|
|
229
247
|
against the sandbox ceiling. A rerun starts its selected participants together;
|
|
230
248
|
- `participants[].endAfterMs` and `windDownMs` (computer-use only): when the participant's
|
|
231
249
|
session ends, in whole milliseconds on the `startAfterMs` clock and after its start, and how
|
|
232
|
-
long before that each turn tells it the end time and the minutes left (default
|
|
250
|
+
long before that each turn tells it the end time and the minutes left (default a quarter of
|
|
251
|
+
the window, at most 600,000; `participantSchedule` in `src/study/arrivals.ts`).
|
|
233
252
|
Refused: an end at or before the start, a `windDownMs` without `endAfterMs`, an end on
|
|
234
253
|
`shared-world`, and a window whose sandbox deadline passes the sandbox ceiling. The
|
|
235
|
-
participant's session budget is its window plus 5 minutes (`
|
|
254
|
+
participant's session budget is its window plus 5 minutes (`participantSession` in
|
|
236
255
|
`src/study/arrivals.ts`) in place of `execution.timeoutMs`, and its sandbox deadline adds the
|
|
237
256
|
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`.
|
|
257
|
+
scheduled_end`) and the session ends `goal_satisfied` with `stopCause: scheduled_end`. The
|
|
258
|
+
outcome an accepted report declares, its `outcome` field or the first line of its summary that
|
|
259
|
+
is not a `>` blockquote, is the trace's `declaredOutcome`. The blocker rule reads a declared
|
|
260
|
+
`reached` or `blocked` before the summary, and after a declared `not_reached` it reads the
|
|
261
|
+
summary. A participant that passes without declaring `reached` counts in the review's
|
|
262
|
+
`participants.ranUntilEnd`, and its completion labels say it ran until its scheduled end. Before
|
|
239
263
|
the end, a turn that requests no action does not end the session: the trace records a
|
|
240
264
|
`participant ended before its scheduled end` notice, the next request says the time left, and
|
|
241
265
|
the turn counts toward the idle backstop. Before the end, a turn that only waits or takes
|
|
242
266
|
screenshots counts toward neither backstop streak (`advanceBackstop`'s `waitingIsTheTask`). The
|
|
243
267
|
plan entry carries `endAfterMs`, and `study check` prints a `windows` row;
|
|
268
|
+
- `participants[].maxSessionMs` (computer-use only, also on `count:` groups): the longest the
|
|
269
|
+
participant's session lasts, in whole milliseconds from 1 to 86,400,000, counted from the start
|
|
270
|
+
of its own session. `windDownMs` applies to it as to `endAfterMs`. Refused: a value outside that
|
|
271
|
+
range, `maxSessionMs` beside `endAfterMs` on one participant, `maxSessionMs` on `shared-world`,
|
|
272
|
+
and a cap whose sandbox deadline passes the sandbox ceiling. The session budget is the cap plus
|
|
273
|
+
5 minutes (`participantSessionMs`) in place of `execution.timeoutMs`, and the schedule holds the
|
|
274
|
+
participant's slot that long. The route reads the cap's end when the session starts
|
|
275
|
+
(`participantEnd` in `src/study/arrivals.ts`, `participantClock` in
|
|
276
|
+
`src/routes/computer-use/participant-model.ts`) and passes it to the loop as
|
|
277
|
+
`clock.end` with `kind: session_cap`. The participant may end its session before the cap, and
|
|
278
|
+
its waiting turns count toward the backstops. At the cap the loop requests the closing report
|
|
279
|
+
(`debrief.trigger: session_cap`) and the session ends `goal_satisfied` with
|
|
280
|
+
`stopCause: session_cap`, its report's declared outcome read as at a scheduled end. The plan
|
|
281
|
+
entry carries `maxSessionMs`, and the plan line shows
|
|
282
|
+
`cap=<duration>`;
|
|
244
283
|
- `participants[].idleWaitMs` (computer-use and shared-world, also on `count:` groups): this
|
|
245
284
|
participant's `actor.idleWaitMs`, in the same range and refused above an explicit
|
|
246
285
|
`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)
|
|
286
|
+
- `actor.clock.timeZone` (computer-use and shared-world): a tz database time zone name, such as `Europe/London`. Each turn's context hint carries
|
|
287
|
+
the local time in it, and a scheduled end or session cap is given in it (UTC without it). On a hosted desktop the
|
|
288
|
+
participant's browser launches with `TZ` set to it (`openParticipantSurface` in
|
|
289
|
+
`src/routes/computer-use/e2b-desktop/start.ts`). `parseClock` in `src/study/parse/clock.ts` refuses a name Intl does not know, a
|
|
290
|
+
UTC offset and a case variant of Intl's spelling, which Chrome's ICU reads as UTC or `Etc/Unknown`. The computer-use planner
|
|
291
|
+
refuses a zone on the local runtime and the in-process route with `HUMANISH_COMPUTER_USE_TIME_ZONE_UNSUPPORTED`;
|
|
292
|
+
- `participants[].clock.timeZone` (computer-use and shared-world, also on `count:` groups): this participant's `actor.clock`,
|
|
293
|
+
with the same rule. The plan's `limits.timeZone` is the participant's zone, else the actor's. `study check` prints a
|
|
294
|
+
`time zones` row, and each participant's stream in `run.json` records `timeZone`;
|
|
249
295
|
- `execution.concurrency` (computer-use E2B routes, including shared-world): a
|
|
250
296
|
cap on participants in flight at once. When omitted, every participant runs
|
|
251
297
|
simultaneously: both routes resolve it when they plan, from the final participant count
|
|
@@ -253,16 +299,21 @@ The rest of this section names keys by their v3 spelling:
|
|
|
253
299
|
`HUMANISH_E2B_MAX_CONCURRENT_SANDBOXES` sandboxes at once (20 when unset; a provisioned
|
|
254
300
|
shared world's app sandbox counts against it), so a larger roster runs in waves and the plan
|
|
255
301
|
records a warning naming the setting. A declared value above that limit is refused at plan
|
|
256
|
-
time
|
|
257
|
-
|
|
302
|
+
time, unless `HUMANISH_CUA_MAX_CONCURRENCY` lowers it first; the limit then lowers what is
|
|
303
|
+
left as it does an undeclared default. Total sessions and spend are identical either way; only
|
|
304
|
+
wall-clock and simultaneity differ. Declaring a value below the participant count runs participants in
|
|
258
305
|
waves and emits a warning saying so, because a green waved run is otherwise
|
|
259
306
|
indistinguishable from the all-live run the author meant. Shared-world studies
|
|
260
307
|
need at least 2 (the host and a follower are live together); `concurrency: 1`
|
|
261
308
|
there is refused at parse, since the sequential turn-taking route was removed
|
|
262
309
|
in 0.106.0. The env override
|
|
263
310
|
`HUMANISH_CUA_MAX_CONCURRENCY` may only lower the effective bound, never
|
|
264
|
-
raise concurrent paid desktops
|
|
265
|
-
the
|
|
311
|
+
raise concurrent paid desktops. The plan applies it before the E2B limit, on every
|
|
312
|
+
computer-use desktop and on shared-world, so the schedule, `study check` and the late-start
|
|
313
|
+
warnings use the lowered count; the plan records a warning and, on computer-use,
|
|
314
|
+
`envLoweredConcurrencyFrom`. When the E2B limit lowers the count further, the plan records
|
|
315
|
+
the E2B limit's warning instead. A shared world refuses a value under 2. Inert
|
|
316
|
+
(warned) on other routes.
|
|
266
317
|
`execution.timeoutMs` is the per-participant session budget on this route (semantics
|
|
267
318
|
change: it was the single-session budget pre-fan-out); there is no run-level
|
|
268
319
|
wall clock. `policies.allowPublicTargets` cannot combine with N>1 against one
|
|
@@ -1011,6 +1062,32 @@ Core-owned fields:
|
|
|
1011
1062
|
click-like `ui_action` items, structured `coord` (`x`/`y`). Both are additive:
|
|
1012
1063
|
absent on older bundles and non-stamping producers, and absence means
|
|
1013
1064
|
"timing/position unrecorded", never zero
|
|
1065
|
+
- optional `items[].page` and `finalPage` (`{ origin, path }`, `TracePage` in
|
|
1066
|
+
`src/actors/contract.ts`): where a computer-use participant's browser was. The loop puts `page`
|
|
1067
|
+
on the `screenshot` item of each observation that read a URL: `turn-00-start` is the page before
|
|
1068
|
+
any action, `turn-01` the page after turn 1's actions, and dwell frames have their own.
|
|
1069
|
+
`finalPage` is the page of the session's last observation, which is a closing observation with
|
|
1070
|
+
no item when tasks are declared; it is absent when that observation read no URL. The screenshot
|
|
1071
|
+
items with a `page`, in item order, then `finalPage`, list the participant's path through the
|
|
1072
|
+
app. `origin` is the URL's scheme, host and port; `path` is its percent-encoded path. A query
|
|
1073
|
+
string, fragment and user info are never kept, and a non-web URL keeps its scheme only
|
|
1074
|
+
(`{ origin: "about:", path: "" }`). Redaction (`tracePage` in `src/evidence/trace-page.ts`):
|
|
1075
|
+
a path segment becomes `[REDACTED_URL_PART]` when it percent-encodes a letter, digit, `-`, `.`,
|
|
1076
|
+
`_`, `~` or `%`, or when any decoded reading of it is email-shaped, holds a run of 16 or more letters,
|
|
1077
|
+
digits, `-` and `_` with a piece of 8 or more characters mixing letters and digits, or changes
|
|
1078
|
+
under the trace text redaction (the study's secrets, then `redactText`). The whole path then
|
|
1079
|
+
goes through that redaction, is cut to 1024 characters ending `[TRUNCATED]`, and is withheld
|
|
1080
|
+
whole when verify's trace scan would still read it as sensitive or opaque. A host label
|
|
1081
|
+
shaped like that token run becomes `[REDACTED_URL_PART]`, then the origin goes through the same
|
|
1082
|
+
redaction (an E2B sandbox host becomes `[REDACTED_SECRET]`). When origin and path together
|
|
1083
|
+
still change under that redaction (a study secret that is a whole link), both become
|
|
1084
|
+
`[REDACTED_URL_PART]`. A token shorter than 16 characters, an
|
|
1085
|
+
all-digit id and personal data in readable words stay, so the sharing rules for synthetic data
|
|
1086
|
+
still apply. Pages come from the browser-state probe, which hosted runs have on Chrome and
|
|
1087
|
+
Chromium only; a Firefox participant, or one whose probe is unavailable, has screenshots without
|
|
1088
|
+
pages. The local browser route and the shared-world route record none. verify reports a page
|
|
1089
|
+
whose path holds `?` or `#`, or whose origin is neither a redaction marker nor a scheme with an
|
|
1090
|
+
optional host and port, as a public-safety finding. Both fields are additive; absence means "page not recorded"
|
|
1014
1091
|
- optional `modelSettings` (`humanish.model-settings.v1`): how the model was
|
|
1015
1092
|
asked to run, alongside `ids.model` which says which model it was.
|
|
1016
1093
|
`reasoningEffort` records the value the request actually carried,
|
|
@@ -1845,8 +1922,13 @@ verification or maintainer acceptance.
|
|
|
1845
1922
|
the run passed also reads `run.json`'s `outcome.ok` ([Run Outcome](run-bundle.md#run-outcome)):
|
|
1846
1923
|
a run whose execution failed shows as failed beside a `pass` verdict. `participants.reachedGoal` retains the count of
|
|
1847
1924
|
recorded successful sessions; it is not an independent adjudication of their
|
|
1848
|
-
claims.
|
|
1849
|
-
|
|
1925
|
+
claims. A computer-use session that passed at its scheduled end or session cap
|
|
1926
|
+
counts there only when its participant declared `reached`; otherwise it counts
|
|
1927
|
+
in `participants.ranUntilEnd`, present when above 0. Bundles written before
|
|
1928
|
+
`ranUntilEnd` existed counted it in `reachedGoal`, and a re-read keeps that
|
|
1929
|
+
count. Computer-use review and Observer labels distinguish a participant's
|
|
1930
|
+
reported completion from a recorded `stopWhen` or dwell condition match, and
|
|
1931
|
+
from a pass at a clock end without a declared reach. A
|
|
1850
1932
|
condition match establishes that condition, not every aspect of the mission.
|
|
1851
1933
|
Missing or malformed completion evidence is labeled unavailable. Other actor
|
|
1852
1934
|
routes retain their own completion semantics. Re-reading a historical review
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "humanish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.127.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",
|