humanish 0.110.4 → 0.111.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/CONTEXT.md +2 -2
- package/dist/actors/codex/restricted-participant-policy.d.ts +2 -0
- package/dist/actors/codex/restricted-participant-policy.js +30 -2
- package/dist/actors/codex/restricted-participant.js +1 -1
- package/dist/actors/computer-use/executor-diagnostic.d.ts +15 -0
- package/dist/actors/computer-use/executor-diagnostic.js +105 -0
- package/dist/actors/computer-use/executor-error.d.ts +18 -3
- package/dist/actors/computer-use/executor-error.js +30 -4
- package/dist/actors/computer-use/loop/actions.d.ts +6 -3
- package/dist/actors/computer-use/loop/actions.js +27 -7
- package/dist/actors/computer-use/loop/ending.js +9 -1
- package/dist/actors/computer-use/loop/types.d.ts +14 -0
- package/dist/actors/computer-use/loop.d.ts +1 -1
- package/dist/actors/computer-use/loop.js +1 -1
- package/dist/actors/computer-use/openai-context.d.ts +8 -2
- package/dist/actors/computer-use/openai-context.js +91 -26
- package/dist/actors/computer-use/openai-provider.js +55 -12
- package/dist/actors/computer-use/openai-requests.d.ts +2 -1
- package/dist/actors/computer-use/openai-requests.js +4 -1
- package/dist/actors/contract.d.ts +7 -1
- package/dist/actors/local-agent/claude-session.js +1 -1
- package/dist/actors/local-agent/cli.js +1 -1
- package/dist/actors/scripted-browser/actor.d.ts +2 -2
- package/dist/browser-control/client.js +5 -5
- package/dist/browser-control/protocol.d.ts +30 -1
- package/dist/browser-control/protocol.js +19 -2
- package/dist/browser-control/transport.d.ts +3 -2
- package/dist/browser-control/transport.js +12 -7
- package/dist/cli/commands/study-route-computer-use.js +2 -2
- package/dist/cli/commands/study-route-shared-world.js +2 -2
- package/dist/cli/commands/study.js +1 -4
- package/dist/cli/doctor.js +11 -0
- package/dist/evidence/encoded-text.d.ts +3 -3
- package/dist/evidence/encoded-text.js +10 -5
- package/dist/evidence/redaction.d.ts +2 -2
- package/dist/evidence/redaction.js +18 -7
- package/dist/guest/browser-tools.js +5 -1
- package/dist/guest/chromium-text.js +42 -18
- package/dist/guest/desktop-executor.js +5 -1
- package/dist/keys/key-resolution.d.ts +3 -0
- package/dist/keys/key-resolution.js +15 -2
- package/dist/routes/computer-use/bundle-parts.d.ts +4 -0
- package/dist/routes/computer-use/bundle-parts.js +7 -2
- package/dist/routes/computer-use/fanout-bundle.js +4 -2
- package/dist/routes/computer-use/local-vm.js +1 -2
- package/dist/routes/computer-use/participant-model.js +7 -2
- package/dist/routes/computer-use/plan.js +7 -10
- package/dist/routes/computer-use/preflight.js +1 -1
- package/dist/routes/computer-use/result.js +1 -1
- package/dist/routes/computer-use/route.js +2 -3
- package/dist/routes/computer-use/setup.js +2 -2
- package/dist/routes/computer-use/types.d.ts +4 -4
- package/dist/routes/preview.js +1 -0
- package/dist/routes/scripted/plan.js +4 -5
- package/dist/routes/scripted/route.js +3 -3
- package/dist/routes/scripted/scenario.d.ts +1 -1
- package/dist/routes/scripted/scenario.js +3 -3
- package/dist/routes/scripted/types.d.ts +2 -2
- package/dist/routes/shared-world/handoff.js +1 -1
- package/dist/routes/shared-world/participant-specs.js +1 -1
- package/dist/routes/shared-world/plan.js +6 -8
- package/dist/routes/shared-world/route.js +2 -2
- package/dist/routes/shared-world/setup.js +1 -0
- package/dist/routes/shared-world/types.d.ts +1 -1
- package/dist/routes/terminal/bundle.d.ts +3 -3
- package/dist/routes/terminal/dry-run.js +1 -0
- package/dist/routes/terminal/ledger.d.ts +2 -2
- package/dist/routes/terminal/lifetime.js +1 -1
- package/dist/routes/terminal/live-finish.js +25 -6
- package/dist/routes/terminal/live-sandbox.js +5 -1
- package/dist/routes/terminal/plan.js +3 -4
- package/dist/routes/terminal/recorder.d.ts +6 -0
- package/dist/routes/terminal/recorder.js +61 -6
- package/dist/routes/terminal/route.js +2 -3
- package/dist/routes/terminal/session.js +2 -1
- package/dist/routes/terminal/token-usage.d.ts +3 -0
- package/dist/routes/terminal/token-usage.js +5 -0
- package/dist/routes/terminal/types.d.ts +3 -2
- package/dist/routes/terminal/types.js +2 -1
- package/dist/run/dry-run.js +1 -0
- package/dist/run/results.d.ts +3 -1
- package/dist/run/run.d.ts +2 -0
- package/dist/run/run.js +16 -2
- package/dist/run/terminal-contract.d.ts +10 -0
- package/dist/run/terminal-contract.js +27 -9
- package/dist/run-study.d.ts +5 -3
- package/dist/run-study.js +7 -11
- package/dist/study/adapter-scorer-loader.d.ts +1 -1
- package/dist/study/adapter-scorer-loader.js +3 -3
- package/dist/study/composition-rules.js +36 -27
- package/dist/study/config.d.ts +29 -13
- package/dist/study/config.js +174 -157
- package/dist/study/discover.d.ts +1 -1
- package/dist/study/discover.js +1 -1
- package/dist/study/doctor.js +4 -5
- package/dist/study/keys.d.ts +250 -2
- package/dist/study/keys.js +32 -62
- package/dist/study/migrate/convert.js +3 -4
- package/dist/study/migrate/v2.d.ts +22 -7
- package/dist/study/migrate/v2.js +173 -23
- package/dist/study/parse/actors.d.ts +11 -32
- package/dist/study/parse/actors.js +11 -156
- package/dist/study/parse/comms.js +6 -2
- package/dist/study/parse/execution.d.ts +9 -4
- package/dist/study/parse/execution.js +4 -39
- package/dist/study/parse/{study-v3.d.ts → front.d.ts} +6 -17
- package/dist/study/parse/{study-v3.js → front.js} +24 -112
- package/dist/study/parse/subject.d.ts +2 -0
- package/dist/study/parse/subject.js +15 -18
- package/dist/study/parse/url-credentials.d.ts +3 -3
- package/dist/study/parse/url-credentials.js +155 -13
- package/dist/study/persona-resolve.js +7 -9
- package/dist/study/plan-base.d.ts +1 -1
- package/dist/study/plan-base.js +5 -7
- package/dist/study/plan-participants.d.ts +2 -2
- package/dist/study/plan-participants.js +3 -3
- package/dist/study/plan-types.d.ts +7 -5
- package/dist/study/plan.js +12 -6
- package/dist/study/preflight-probes.js +2 -2
- package/dist/study/preflight.d.ts +1 -1
- package/dist/study/preflight.js +1 -1
- package/dist/study/routing.d.ts +5 -5
- package/dist/study/routing.js +16 -16
- package/dist/study/run-study-options.d.ts +1 -1
- package/dist/study/run-study-options.js +2 -2
- package/dist/study/study-deps.d.ts +1 -1
- package/dist/study/study-fields.d.ts +4 -25
- package/dist/study/study-fields.js +15 -44
- package/dist/study/summary.js +13 -8
- package/dist/study/types.d.ts +51 -128
- package/dist/study/types.js +0 -1
- package/dist/study/url-credentials.js +3 -4
- package/dist/study/validation.d.ts +2 -10
- package/dist/study/validation.js +23 -53
- package/dist/study/warnings.d.ts +11 -3
- package/dist/study/warnings.js +56 -110
- package/dist/substrates/local/runtime-config.js +11 -13
- package/docs/architecture/browser-control.md +12 -2
- package/docs/architecture/guest-desktop.md +7 -1
- package/docs/architecture/terminal-product-route.md +3 -2
- package/docs/contracts/schemas.md +39 -9
- package/package.json +1 -1
- package/dist/study/parse/study.d.ts +0 -38
- package/dist/study/parse/study.js +0 -225
package/dist/study/warnings.js
CHANGED
|
@@ -1,73 +1,54 @@
|
|
|
1
1
|
import { isComputerUseComposition, isScriptedBrowserComposition, isSharedWorldComposition, isTerminalProductComposition, } from "./routing.js";
|
|
2
2
|
import { declaredParticipantIds } from "./plan-participants.js";
|
|
3
3
|
import { addressedRecipients } from "./parse/comms.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
//
|
|
7
|
-
// compose the agent prompt and bundle provenance); laneFocus.id/label stay inert. On the computer-use
|
|
8
|
-
// E2B route actors[0].lanes is consumed (the fan-out roster).
|
|
4
|
+
import { participantList, declaredParticipantCount } from "./study-fields.js";
|
|
5
|
+
// The computer-use and terminal routes consume mission/persona/model (they compose the agent prompt
|
|
6
|
+
// and bundle provenance).
|
|
9
7
|
const promptRoute = (routes) => routes.cua || routes.terminal;
|
|
10
|
-
// The scripted-browser route consumes persona and
|
|
11
|
-
//
|
|
8
|
+
// The scripted-browser route consumes persona and surfaces (trace/bundle provenance). The prompt
|
|
9
|
+
// fields can never act there: the scripted actor runs no model.
|
|
12
10
|
const scriptedRoute = (routes) => !promptRoute(routes) && routes.scripted;
|
|
13
11
|
const otherRoute = (routes) => !promptRoute(routes) && !routes.scripted;
|
|
14
|
-
/** Rows for
|
|
12
|
+
/** Rows for the actor and its participants, reported first, in this order. */
|
|
15
13
|
const ACTOR_ROWS = [
|
|
16
14
|
// Shared-world-only fields on the roster: a participant's `entry` is inert anywhere else
|
|
17
15
|
// (claims match mechanism).
|
|
18
16
|
{
|
|
19
|
-
field: "
|
|
20
|
-
reason: "the per-participant loopback entry is a shared-world capability; needs
|
|
21
|
-
applies: (
|
|
17
|
+
field: "participants[].entry",
|
|
18
|
+
reason: "the per-participant loopback entry is a shared-world capability; needs route: shared-world",
|
|
19
|
+
applies: (config, routes) => Boolean(participantList(config)?.some((entry) => entry.entry !== undefined)) &&
|
|
20
|
+
!routes.shared,
|
|
22
21
|
},
|
|
23
22
|
// The host marker acts only on the external-public shared-world route; inert elsewhere.
|
|
24
23
|
{
|
|
25
|
-
field: "
|
|
24
|
+
field: "participants[].host",
|
|
26
25
|
reason: "the designated host-participant marker; needs the external-public shared-world route: app-url × topology shared-world × allowPublicTargets",
|
|
27
|
-
applies: (
|
|
26
|
+
applies: (config, routes) => Boolean(participantList(config)?.some((entry) => entry.host === true)) &&
|
|
27
|
+
!routes.externalPublic,
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
field: "
|
|
31
|
-
applies: (actor, routes) => promptRoute(routes) && Boolean(focusOf(actor)?.id),
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
field: "laneFocus.label",
|
|
35
|
-
applies: (actor, routes) => promptRoute(routes) && Boolean(focusOf(actor)?.label),
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
field: "lanes",
|
|
39
|
-
reason: "only the computer-use route runs more than one participant",
|
|
40
|
-
applies: (actor, routes) => routes.terminal && Boolean(rosterOf(actor)),
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
field: "mission",
|
|
30
|
+
field: "actor.mission",
|
|
44
31
|
reason: "the scripted-browser actor runs no model",
|
|
45
|
-
applies: (
|
|
32
|
+
applies: (config, routes) => scriptedRoute(routes) && Boolean(config.actor?.mission),
|
|
46
33
|
},
|
|
47
34
|
{
|
|
48
|
-
field: "
|
|
35
|
+
field: "actor.model",
|
|
49
36
|
reason: "the scripted-browser actor runs no model",
|
|
50
|
-
applies: (
|
|
37
|
+
applies: (config, routes) => scriptedRoute(routes) && Boolean(config.actor?.model),
|
|
51
38
|
},
|
|
39
|
+
// On every other route the prompt fields and persona are inert.
|
|
52
40
|
{
|
|
53
|
-
field: "
|
|
54
|
-
|
|
55
|
-
applies: (actor, routes) => scriptedRoute(routes) && Boolean(actor.model),
|
|
41
|
+
field: "actor.mission",
|
|
42
|
+
applies: (config, routes) => otherRoute(routes) && Boolean(config.actor?.mission),
|
|
56
43
|
},
|
|
57
44
|
{
|
|
58
|
-
field: "
|
|
59
|
-
|
|
60
|
-
applies: (actor, routes) => scriptedRoute(routes) && Boolean(rosterOf(actor)),
|
|
45
|
+
field: "actor.persona",
|
|
46
|
+
applies: (config, routes) => otherRoute(routes) && Boolean(config.actor?.persona),
|
|
61
47
|
},
|
|
62
|
-
// On every other route the prompt fields, persona and the `lanes[]` roster are inert.
|
|
63
|
-
{ field: "mission", applies: (actor, routes) => otherRoute(routes) && Boolean(actor.mission) },
|
|
64
48
|
{
|
|
65
|
-
field: "
|
|
66
|
-
applies: (
|
|
49
|
+
field: "actor.model",
|
|
50
|
+
applies: (config, routes) => otherRoute(routes) && Boolean(config.actor?.model),
|
|
67
51
|
},
|
|
68
|
-
{ field: "persona", applies: (actor, routes) => otherRoute(routes) && Boolean(actor.persona) },
|
|
69
|
-
{ field: "model", applies: (actor, routes) => otherRoute(routes) && Boolean(actor.model) },
|
|
70
|
-
{ field: "lanes", applies: (actor, routes) => otherRoute(routes) && Boolean(rosterOf(actor)) },
|
|
71
52
|
];
|
|
72
53
|
const TERMINAL_ONLY = "needs subject.source: terminal-product + a registered terminal actor";
|
|
73
54
|
const RESERVED = "reserved; no route reads it yet";
|
|
@@ -91,17 +72,10 @@ const CONFIG_ROWS = [
|
|
|
91
72
|
field: "subject.state",
|
|
92
73
|
applies: (config, routes) => Boolean(config.subject.state) && !routes.cua && !routes.scripted,
|
|
93
74
|
},
|
|
94
|
-
//
|
|
95
|
-
// (incl. an explicit per-lane-worlds, which the computer-use route already is by mechanism) warns
|
|
96
|
-
// inert.
|
|
97
|
-
{
|
|
98
|
-
field: "subject.topology",
|
|
99
|
-
reason: "drives behavior only on the shared-world route; needs subject.topology: shared-world + clone × e2b-desktop × a computer-use actor + a roster of ≥2 participants",
|
|
100
|
-
applies: (config, routes) => config.subject.topology !== undefined && !routes.shared,
|
|
101
|
-
},
|
|
75
|
+
// The checkpoint acts only on the shared-world route.
|
|
102
76
|
{
|
|
103
77
|
field: "subject.state.checkpoint",
|
|
104
|
-
reason: "the shared-world state-checkpoint probe; needs
|
|
78
|
+
reason: "the shared-world state-checkpoint probe; needs route: shared-world",
|
|
105
79
|
applies: (config, routes) => config.subject.state?.checkpoint !== undefined && !routes.shared,
|
|
106
80
|
},
|
|
107
81
|
// publicTarget (the external-public ownership attestation) acts only on the external-public
|
|
@@ -161,18 +135,10 @@ const CONFIG_ROWS = [
|
|
|
161
135
|
field: "execution.concurrency",
|
|
162
136
|
applies: (config, routes) => config.execution?.concurrency !== undefined && !routes.cua,
|
|
163
137
|
},
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
field: "execution.caps",
|
|
169
|
-
reason: "only the computer-use route enforces it, with a computer-use actor on e2b-desktop",
|
|
170
|
-
applies: (config, routes) => Boolean(config.execution?.caps) && !routes.cua,
|
|
171
|
-
},
|
|
172
|
-
// terminal-product consumes subject.product, scenario.caps, execution.{terminal,runtimeAuth}:
|
|
173
|
-
// dry-run records the contract; live execution enforces caps and command-scoped auth. On every
|
|
174
|
-
// other route they are inert and must warn so a
|
|
175
|
-
// misplaced safety/budget field is never trusted to do something it cannot.
|
|
138
|
+
// parse/front.ts refuses a `caps` key the declared route does not read, so no row lists `caps`.
|
|
139
|
+
// terminal-product consumes subject.product and execution.{terminal,runtimeAuth}: dry-run records
|
|
140
|
+
// the contract; live execution enforces command-scoped auth. On every other route they are inert
|
|
141
|
+
// and must warn so a misplaced safety field is never trusted to do something it cannot.
|
|
176
142
|
{
|
|
177
143
|
field: "subject.product",
|
|
178
144
|
reason: "needs subject.source: terminal-product or desktop-cli with the matching actor",
|
|
@@ -180,18 +146,6 @@ const CONFIG_ROWS = [
|
|
|
180
146
|
!routes.terminal &&
|
|
181
147
|
config.subject.source !== "desktop-cli",
|
|
182
148
|
},
|
|
183
|
-
{
|
|
184
|
-
field: "scenario.caps",
|
|
185
|
-
reason: TERMINAL_ONLY,
|
|
186
|
-
applies: (config, routes) => Boolean(config.scenario?.caps) && !routes.terminal,
|
|
187
|
-
},
|
|
188
|
-
// The study-level budget is a CUA-route capability; the terminal route is a single agent whose
|
|
189
|
-
// maxUsd already caps the whole run, so a maxTotalUsd there would be trusted and unenforced.
|
|
190
|
-
{
|
|
191
|
-
field: "scenario.caps.maxTotalUsd",
|
|
192
|
-
reason: "the study-level budget is a computer-use route capability; the terminal route's maxUsd already caps the whole run",
|
|
193
|
-
applies: (config, routes) => config.scenario?.caps?.maxTotalUsd !== undefined && routes.terminal,
|
|
194
|
-
},
|
|
195
149
|
{
|
|
196
150
|
field: "execution.terminal",
|
|
197
151
|
reason: TERMINAL_ONLY,
|
|
@@ -247,13 +201,12 @@ const CONFIG_ROWS = [
|
|
|
247
201
|
reason: "no current route reads it",
|
|
248
202
|
applies: (config) => config.execution?.desktop?.codexAppServer !== undefined,
|
|
249
203
|
},
|
|
250
|
-
// scenario
|
|
204
|
+
// `scenario` is consumed on the scripted-browser route (required there); forward-declared
|
|
251
205
|
// everywhere else.
|
|
252
206
|
{
|
|
253
|
-
field: "scenario
|
|
254
|
-
applies: (config, routes) => Boolean(
|
|
207
|
+
field: "scenario",
|
|
208
|
+
applies: (config, routes) => Boolean(config.scenario) && !routes.scripted,
|
|
255
209
|
},
|
|
256
|
-
{ field: "scenario.inline", applies: (config) => Boolean(config.scenario?.inline) },
|
|
257
210
|
// review.{scoring,milestones,vocabulary} stay forward-declared on every route.
|
|
258
211
|
// review.scorer is consumed (loaded + wired, or fail-closed at load) on every scorer-capable
|
|
259
212
|
// route, so it does not warn there; on the scripted-browser route the actor carries no scorer seam,
|
|
@@ -278,7 +231,6 @@ const CONFIG_ROWS = [
|
|
|
278
231
|
reason: "the scripted-browser actor has no adopter-scorer seam; declare it on a terminal / computer-use / shared-world route",
|
|
279
232
|
applies: (config, routes) => Boolean(config.review?.scorer) && routes.scripted,
|
|
280
233
|
},
|
|
281
|
-
{ field: "personas", applies: (config) => Boolean(config.personas) },
|
|
282
234
|
];
|
|
283
235
|
function routesOf(config) {
|
|
284
236
|
const cua = isComputerUseComposition(config);
|
|
@@ -295,44 +247,35 @@ function routesOf(config) {
|
|
|
295
247
|
config.execution?.target === "e2b-desktop",
|
|
296
248
|
};
|
|
297
249
|
}
|
|
298
|
-
function
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
: `${prefix}${row.field} (${row.reason})`;
|
|
250
|
+
function inertRows(config) {
|
|
251
|
+
const routes = routesOf(config);
|
|
252
|
+
return [...ACTOR_ROWS, ...CONFIG_ROWS].filter((row) => row.applies(config, routes));
|
|
302
253
|
}
|
|
303
254
|
/**
|
|
304
|
-
* The fields a config sets that its route does not read, each as `field` or `field (reason)`.
|
|
305
|
-
*
|
|
255
|
+
* The fields a config sets that its route does not read, each as `field` or `field (reason)`.
|
|
256
|
+
* parseStudy refuses a study for any of them.
|
|
306
257
|
*/
|
|
307
258
|
export function inertFieldLabels(config) {
|
|
308
|
-
|
|
309
|
-
const inert = [];
|
|
310
|
-
for (const [index, actor] of actorsOf(config).entries()) {
|
|
311
|
-
for (const row of ACTOR_ROWS)
|
|
312
|
-
if (row.applies(actor, routes))
|
|
313
|
-
inert.push(rowLabel(row, `actors[${index}].`));
|
|
314
|
-
}
|
|
315
|
-
for (const row of CONFIG_ROWS)
|
|
316
|
-
if (row.applies(config, routes))
|
|
317
|
-
inert.push(rowLabel(row));
|
|
318
|
-
return inert;
|
|
259
|
+
return inertRows(config).map((row) => row.reason === undefined ? row.field : `${row.field} (${row.reason})`);
|
|
319
260
|
}
|
|
320
261
|
/**
|
|
321
262
|
* The same fields as `inertFieldLabels`, as paths without their reasons, such as
|
|
322
|
-
* `
|
|
263
|
+
* `participants[].entry` or `execution.timeoutMs`. `humanish migrate` drops each one.
|
|
323
264
|
*/
|
|
324
265
|
export function inertFieldPaths(config) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
266
|
+
return inertRows(config).map((row) => row.field);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The warning for `execution.egressAllow` on a route that ignores it, or undefined. Only the
|
|
270
|
+
* terminal route creates its sandbox with the list as an outbound allowlist; on any other route the
|
|
271
|
+
* study author would trust a list that blocks nothing. `humanish study check` and every run print
|
|
272
|
+
* it, and the run records it in its bundle.
|
|
273
|
+
* Removed in 0.112.0: deprecated on 2026-10-05. 0.112.0 refuses the field off the terminal route.
|
|
274
|
+
*/
|
|
275
|
+
export function egressAllowIgnoredWarning(config) {
|
|
276
|
+
if (config.execution?.egressAllow === undefined || config.route === "terminal")
|
|
277
|
+
return undefined;
|
|
278
|
+
return `\`execution.egressAllow\` is ignored on route: ${config.route}. Only route: terminal creates its sandbox with that outbound allowlist, and this route's browser desktops can reach any site. Remove the field: humanish 0.112.0 refuses it on every route except terminal.`;
|
|
336
279
|
}
|
|
337
280
|
// Report fields that are present but not yet consumed by the engine, so a user never trusts a
|
|
338
281
|
// setting that silently does nothing.
|
|
@@ -344,6 +287,9 @@ export function forwardDeclaredWarnings(config) {
|
|
|
344
287
|
: [
|
|
345
288
|
`These fields are set, but no route reads them yet, so they have no effect: ${inert.join(", ")}.`,
|
|
346
289
|
];
|
|
290
|
+
const egressIgnored = egressAllowIgnoredWarning(config);
|
|
291
|
+
if (egressIgnored !== undefined)
|
|
292
|
+
warnings.push(egressIgnored);
|
|
347
293
|
// A declared cap below the participant count is legal but loud: the roster promises N live actors
|
|
348
294
|
// and the cap delivers waves of M. Say so up front (inspect + dry-run + run): a green run in
|
|
349
295
|
// waves is otherwise indistinguishable from the all-live run the author meant.
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { participantList } from "../../study/study-fields.js";
|
|
2
2
|
// The runtime image enforces a 30-minute lifetime; reserve setup/teardown room.
|
|
3
3
|
export const LOCAL_BROWSER_LIFETIME_MS = 30 * 60_000;
|
|
4
4
|
const MAX_SESSION_MS = 20 * 60_000;
|
|
5
5
|
export function isLocalBrowserStudy(config) {
|
|
6
6
|
return (config.subject.source === "app-url" &&
|
|
7
7
|
config.execution?.target === "local" &&
|
|
8
|
-
["local-agent", "openai-computer-use"].includes(
|
|
8
|
+
["local-agent", "openai-computer-use"].includes(config.actor?.type ?? ""));
|
|
9
9
|
}
|
|
10
10
|
/** Defaults for the explicitly selected local substrate; hosted configurations are untouched. */
|
|
11
11
|
export function localBrowserDefaults(config) {
|
|
12
|
-
|
|
12
|
+
const actor = config.actor;
|
|
13
|
+
if (!isLocalBrowserStudy(config) || actor === undefined)
|
|
13
14
|
return config;
|
|
14
15
|
return {
|
|
15
16
|
...config,
|
|
16
|
-
|
|
17
|
+
actor: {
|
|
17
18
|
...actor,
|
|
18
19
|
model: actor.model ?? "gpt-6-astra",
|
|
19
20
|
reasoningEffort: actor.reasoningEffort ?? "low",
|
|
20
|
-
}
|
|
21
|
+
},
|
|
21
22
|
execution: {
|
|
22
23
|
...config.execution,
|
|
23
24
|
timeoutMs: config.execution?.timeoutMs ?? MAX_SESSION_MS,
|
|
@@ -26,19 +27,18 @@ export function localBrowserDefaults(config) {
|
|
|
26
27
|
resolution: config.execution?.desktop?.resolution ?? [960, 720],
|
|
27
28
|
},
|
|
28
29
|
},
|
|
29
|
-
...(config.review?.analysis === undefined &&
|
|
30
|
+
...(config.review?.analysis === undefined && actor.type === "local-agent"
|
|
30
31
|
? { review: { ...config.review, analysis: { provider: "codex" } } }
|
|
31
32
|
: {}),
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
export function localBrowserUnsupportedReason(config) {
|
|
35
|
-
const actor =
|
|
36
|
+
const actor = config.actor;
|
|
36
37
|
const desktop = config.execution?.desktop;
|
|
37
38
|
if ((config.execution?.timeoutMs ?? MAX_SESSION_MS) > MAX_SESSION_MS) {
|
|
38
39
|
return "Local browser sessions currently support at most 20 minutes, within the runtime's 30-minute lifetime.";
|
|
39
40
|
}
|
|
40
|
-
if (
|
|
41
|
-
!actor ||
|
|
41
|
+
if (!actor ||
|
|
42
42
|
(actor.type === "local-agent" && actor.localAgent !== undefined && actor.localAgent !== "codex")) {
|
|
43
43
|
return "Local browser studies support only Codex local-agent or openai-computer-use participants.";
|
|
44
44
|
}
|
|
@@ -63,10 +63,8 @@ export function localBrowserUnsupportedReason(config) {
|
|
|
63
63
|
actor.reasoningEffort !== "low" ||
|
|
64
64
|
participantList(config)?.some((entry) => entry.reasoningEffort !== undefined && entry.reasoningEffort !== "low") ||
|
|
65
65
|
actor.maxOutputTokens !== undefined ||
|
|
66
|
-
config.
|
|
67
|
-
config.
|
|
68
|
-
config.scenario?.caps?.maxUsd !== undefined ||
|
|
69
|
-
config.scenario?.caps?.maxTotalUsd !== undefined)) {
|
|
66
|
+
config.caps?.maxUsd !== undefined ||
|
|
67
|
+
config.caps?.maxTotalUsd !== undefined)) {
|
|
70
68
|
return "Local Codex participants currently use gpt-6-astra at low effort. Account dollar/output-token caps are unavailable; use API participants for those controls.";
|
|
71
69
|
}
|
|
72
70
|
for (const target of [
|
|
@@ -62,8 +62,15 @@ runtime-management method. Initial target navigation remains an adapter-owned
|
|
|
62
62
|
operation.
|
|
63
63
|
|
|
64
64
|
Replies acknowledge completion or contain a finite `ComputerUseExecutorError` code and
|
|
65
|
-
`not_dispatched` / `outcome_uncertain` disposition.
|
|
65
|
+
`not_dispatched` / `outcome_uncertain` disposition. An `action_rejected` /
|
|
66
|
+
`not_dispatched` refusal may add a `reason` from a fixed list (`extra_tab`), which
|
|
67
|
+
the study loop turns into a hint the participant can act on. They never include raw
|
|
66
68
|
exception prose, typed text, page URLs, or browser errors in their error fields.
|
|
69
|
+
A failure the driver did not declare may add a `diagnostic` of two fixed words: the
|
|
70
|
+
step that failed (such as `insert_text`) and a category (`target_closed`,
|
|
71
|
+
`detached`, `navigation`, `timeout`, `protocol_error`, `channel_closed` or
|
|
72
|
+
`unknown`). The guest picks the category from the error's class and known message
|
|
73
|
+
shapes and then drops the message; the host prints a fixed sentence for each word.
|
|
67
74
|
A generic driver exception after invocation is uncertain; only a genuine typed
|
|
68
75
|
driver declaration can attest that input was never dispatched.
|
|
69
76
|
|
|
@@ -96,7 +103,10 @@ Observation requires a PNG and state signature. It may include bounded URL,
|
|
|
96
103
|
title, text, fractional scroll position and heard speech; those remain
|
|
97
104
|
runtime-only under the existing loop contract. Arbitrary `appState` is refused
|
|
98
105
|
because v1 has no closed schema for it. The protocol does not truncate strings,
|
|
99
|
-
round coordinates, or silently drop unsupported state.
|
|
106
|
+
round coordinates, or silently drop unsupported state. A Codex participant's
|
|
107
|
+
`humanish_ui` call is checked before it reaches the protocol: a wait longer than 30 seconds is
|
|
108
|
+
shortened to 30 seconds there, and the trace records a `wait shortened` notice with both
|
|
109
|
+
durations.
|
|
100
110
|
|
|
101
111
|
Click, double-click, move, scroll and drag may carry `heldKeys`, which the
|
|
102
112
|
OpenAI provider maps from its computer tool's `keys`. The Codex participant's
|
|
@@ -38,6 +38,11 @@ world checks actual document focus and binds preparation to the editable element
|
|
|
38
38
|
page changes, additional tabs, dialogs, iframe focus and ambiguous focus are
|
|
39
39
|
refused. The owner acquires the one page and native window before untrusted
|
|
40
40
|
navigation. The driver never chooses another tab or brings one to the front.
|
|
41
|
+
Playwright emulates focus in every page it drives, so the focus probe also passes
|
|
42
|
+
in a background tab and cannot tell which tab is in front; text therefore needs the
|
|
43
|
+
owned page to be the context's only page. That refusal carries the fixed reason
|
|
44
|
+
`extra_tab` over browser control, and the study loop tells the participant to close
|
|
45
|
+
the other tab.
|
|
41
46
|
Focus can change between protocol messages; this is not atomic element-targeted
|
|
42
47
|
input or a general multi-window driver.
|
|
43
48
|
|
|
@@ -56,7 +61,8 @@ text paths likewise need application readback to establish task outcomes.
|
|
|
56
61
|
Native children use fixed paths and a minimal explicit environment. Operator
|
|
57
62
|
credentials and inherited Xauthority are not forwarded. Native address-bar text travels over the
|
|
58
63
|
helper's stdin; web text travels over the private browser pipe, never a shell. Helpers have bounded deadlines and output;
|
|
59
|
-
raw diagnostic text is never returned as an executor error.
|
|
64
|
+
raw diagnostic text is never returned as an executor error. A text port failure the
|
|
65
|
+
driver did not declare carries only a fixed step and category. Only an acquired,
|
|
60
66
|
still-live child handle authorizes termination. If capture-helper exit cannot
|
|
61
67
|
be confirmed, its private files stay for runtime-owner reclamation.
|
|
62
68
|
|
|
@@ -104,8 +104,9 @@ execution:
|
|
|
104
104
|
Codex command receives the nonsecret value `humanish-egress-auth-placeholder`
|
|
105
105
|
under `CODEX_API_KEY`, plus `CODEX_CA_CERTIFICATE` pointing at E2B's existing
|
|
106
106
|
system CA bundle (`/etc/ssl/certs/ca-certificates.crt`) so TLS verification trusts
|
|
107
|
-
the platform's proxy CA. humanish
|
|
108
|
-
|
|
107
|
+
the platform's proxy CA. `humanish doctor` run inside the sandbox reports that
|
|
108
|
+
`CODEX_API_KEY` holds this placeholder and that no usable key is available.
|
|
109
|
+
humanish does not disable TLS verification or download an unauthenticated CA. The sandbox receives no raw runtime key in command env, sandbox env,
|
|
109
110
|
files, metadata, or captured evidence. The host still scrubs the actual key from
|
|
110
111
|
output and errors, including errors during sandbox creation.
|
|
111
112
|
|
|
@@ -59,10 +59,19 @@ workflow without leaking private upstream truth into core.
|
|
|
59
59
|
|
|
60
60
|
## Study File
|
|
61
61
|
|
|
62
|
-
Schema: `humanish.study.v3`. `src/study/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
`humanish migrate <path>`; migrate
|
|
62
|
+
Schema: `humanish.study.v3`. `src/study/config.ts` parses a v3 document into `StudyConfig`
|
|
63
|
+
(`src/study/types.ts`), which has the file's keys; `src/study/parse/front.ts` checks the route and
|
|
64
|
+
the forms of `participants`, `surfaces` and `caps` it takes. humanish refuses a `humanish.lab.v2`
|
|
65
|
+
file with `HUMANISH_STUDY_V2_UNSUPPORTED`, whose message names `humanish migrate <path>`; migrate
|
|
66
|
+
converts the file to v3.
|
|
67
|
+
|
|
68
|
+
The parsed config is the file with defaults filled: a `participants` entry with a `count` expanded
|
|
69
|
+
into its participants, `comms.email.recipients` and a shared world's `execution.concurrency`
|
|
70
|
+
filled, `kind: real` added beside an email `connection`, and a local browser study's model,
|
|
71
|
+
effort, timeout and resolution. `humanish study show --json` prints that config, and `parseStudy`
|
|
72
|
+
reads it back to the same config. Before 0.111.0, `StudyConfig` had the `humanish.lab.v2` keys
|
|
73
|
+
(`actors`, `execution.caps`, the `scenario` object, `subject.topology`); the 0.111.0 release notes
|
|
74
|
+
map each one to its v3 key.
|
|
66
75
|
|
|
67
76
|
A study declares its route and composes code primitives; it is not a hardcoded kind. The
|
|
68
77
|
top-level keys:
|
|
@@ -969,14 +978,25 @@ Core-owned fields:
|
|
|
969
978
|
kept it through `previous_response_id`) or `explicit_context` (the server kept
|
|
970
979
|
none). `explicitReason` says why: `configured` (`zeroDataRetention`) or
|
|
971
980
|
`zdr_rejection` (the organization rejected server-side state; `switchedAt` and
|
|
972
|
-
`switchedAtRequest` say when).
|
|
981
|
+
`switchedAtRequest` say when). With `zdr_rejection`, `rejection` names the
|
|
982
|
+
answer that made the switch: `zero_data_retention` (the error named it),
|
|
983
|
+
`previous_response` (a 400 that refused or could not find the
|
|
984
|
+
`previous_response_id`) or `stored_item` (a 404 for an item the server never
|
|
985
|
+
kept, "Item with id '...' not found"). In `explicit_context` each request carries the
|
|
973
986
|
conversation from the client, with `store: false` and the reasoning returned
|
|
974
987
|
in encrypted form. It carries the opening message, every exchange of a reply
|
|
975
988
|
with the outputs that answered it, and the latest reply. Past an estimated
|
|
976
|
-
64,000 input tokens
|
|
977
|
-
exchanges become lines of a note,
|
|
978
|
-
|
|
979
|
-
|
|
989
|
+
64,000 input tokens it is cut down to 32,000 in one step: the opening
|
|
990
|
+
screenshot is dropped first, then the oldest exchanges become lines of a note,
|
|
991
|
+
sent as an assistant message, that keeps their reasoning summaries, messages
|
|
992
|
+
and actions as text, with any text that came back with their screenshots.
|
|
993
|
+
Past 32,000 characters the note keeps its first four lines and its newest
|
|
994
|
+
ones, and names the turns between them as not listed.
|
|
995
|
+
The two newest exchanges are always carried whole, even when they alone pass
|
|
996
|
+
the budget. Between cuts each request starts with the whole previous request,
|
|
997
|
+
so the provider's prompt cache can serve all of it; the request after a cut is
|
|
998
|
+
billed as new input. `tokenUsage.turns` has each request's `cachedInput` and
|
|
999
|
+
`cacheWriteInput`. `summarizedTurns` counts the earlier exchanges the latest
|
|
980
1000
|
`explicit_context` request no longer carried whole: lines of the note, or
|
|
981
1001
|
only counted once the note reached its cap.
|
|
982
1002
|
`requests[]` has one entry per participant request: its `mode` and, in
|
|
@@ -1552,6 +1572,16 @@ Refusals:
|
|
|
1552
1572
|
a preview or scripted study, `createProvider` off computer use, or `prepareDesktop` where no E2B
|
|
1553
1573
|
desktop exists. The message names the option and the route. The same code refuses a field
|
|
1554
1574
|
`RunStudyOptions` no longer has (below); that message names the field and where its job went.
|
|
1575
|
+
- `HUMANISH_STUDY_V2_UNSUPPORTED`: the config is `humanish.lab.v2`, or it still sets a
|
|
1576
|
+
`StudyConfig` field of humanish 0.110 that 0.111.0 renamed: `actors`, a `scenario` object,
|
|
1577
|
+
`execution.caps`, `subject.topology`, `personas`, or `count`, `lanes`, `roster` or `laneFocus` on
|
|
1578
|
+
`actor`. The message names each one's v3 field. The
|
|
1579
|
+
planners read only the v3 fields, so a budget left in `execution.caps` would otherwise run
|
|
1580
|
+
uncapped.
|
|
1581
|
+
- `HUMANISH_STUDY_INVALID`: the config has no `actor` object, or its `route` is missing or is not
|
|
1582
|
+
the route its subject and actor take. The message is the one `parseStudy` gives.
|
|
1583
|
+
|
|
1584
|
+
`runStudy` checks the config before the options, so a config refusal comes first.
|
|
1555
1585
|
|
|
1556
1586
|
`rerun.laneIds`, the older name of `rerun.participantIds`, was removed in 0.107.0. A caller that
|
|
1557
1587
|
still passes it is refused with `HUMANISH_STUDY_OPTION_UNSUPPORTED`; use `rerun.participantIds`.
|
package/package.json
CHANGED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { type StudyConfig, type StudyParseFailure, type StudyScenarioCaps, type StudyV3 } from "../types.js";
|
|
2
|
-
interface StudyV3Options {
|
|
3
|
-
/**
|
|
4
|
-
* `report`, for `humanish migrate`: list the fields the route does not read in `inert`, as v2
|
|
5
|
-
* paths, in place of refusing the study for them, and give the checks' messages as they wrote
|
|
6
|
-
* them, in the v2 spelling of the file migrate converts. A shared world may then give
|
|
7
|
-
* `participants` as a count or leave it out, as a v2 file could (readStudyFront).
|
|
8
|
-
*/
|
|
9
|
-
readonly inert?: "refuse" | "report";
|
|
10
|
-
}
|
|
11
|
-
type StudyV3ParseResult = {
|
|
12
|
-
ok: true;
|
|
13
|
-
config: StudyV3;
|
|
14
|
-
warnings: string[];
|
|
15
|
-
inert?: string[];
|
|
16
|
-
} | StudyParseFailure;
|
|
17
|
-
type Parsed<T> = {
|
|
18
|
-
ok: true;
|
|
19
|
-
value: T;
|
|
20
|
-
} | StudyParseFailure;
|
|
21
|
-
/**
|
|
22
|
-
* Validate a parsed humanish.study.v3 document into a StudyV3, with the messages parseStudy gives.
|
|
23
|
-
* Pure: the caller owns file IO. parseStudy refuses a humanish.lab.v2 document before this runs.
|
|
24
|
-
*/
|
|
25
|
-
export declare function parseStudyV3(raw: unknown, options?: StudyV3Options): StudyV3ParseResult;
|
|
26
|
-
/**
|
|
27
|
-
* A v3 study in the v2 spelling: the actor holds its participants, `caps` moves under `execution`
|
|
28
|
-
* or `scenario`, and a shared world is `subject.topology: shared-world`. The checks that read more
|
|
29
|
-
* than one section run on this view until StudyConfig has the v3 shape.
|
|
30
|
-
*/
|
|
31
|
-
export declare function toLegacy(study: StudyV3): StudyConfig;
|
|
32
|
-
/**
|
|
33
|
-
* A caps block's values. A value that is not a non-negative number refuses the study: a cap that
|
|
34
|
-
* does nothing must not look like one that holds. readStudyFront has checked the keys against the
|
|
35
|
-
* route; migrate checks a v2 file's two blocks with it.
|
|
36
|
-
*/
|
|
37
|
-
export declare function parseCaps(raw: unknown): Parsed<StudyScenarioCaps | undefined>;
|
|
38
|
-
export {};
|