humanish 0.110.4 → 0.111.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/CONTEXT.md +2 -2
  3. package/dist/actors/local-agent/claude-session.js +1 -1
  4. package/dist/actors/local-agent/cli.js +1 -1
  5. package/dist/actors/scripted-browser/actor.d.ts +2 -2
  6. package/dist/cli/commands/study.js +2 -4
  7. package/dist/routes/computer-use/participant-model.js +1 -1
  8. package/dist/routes/computer-use/plan.js +4 -6
  9. package/dist/routes/computer-use/preflight.js +1 -1
  10. package/dist/routes/computer-use/result.js +1 -1
  11. package/dist/routes/computer-use/route.js +1 -1
  12. package/dist/routes/computer-use/types.d.ts +4 -4
  13. package/dist/routes/scripted/route.js +1 -1
  14. package/dist/routes/scripted/scenario.d.ts +1 -1
  15. package/dist/routes/scripted/scenario.js +3 -3
  16. package/dist/routes/scripted/types.d.ts +2 -2
  17. package/dist/routes/shared-world/handoff.js +1 -1
  18. package/dist/routes/shared-world/participant-specs.js +1 -1
  19. package/dist/routes/shared-world/plan.js +3 -4
  20. package/dist/routes/shared-world/types.d.ts +1 -1
  21. package/dist/routes/terminal/bundle.d.ts +3 -3
  22. package/dist/routes/terminal/ledger.d.ts +2 -2
  23. package/dist/routes/terminal/lifetime.js +1 -1
  24. package/dist/routes/terminal/live-finish.js +1 -1
  25. package/dist/routes/terminal/plan.js +1 -1
  26. package/dist/routes/terminal/route.js +1 -1
  27. package/dist/routes/terminal/types.d.ts +1 -1
  28. package/dist/run/results.d.ts +1 -1
  29. package/dist/run-study.d.ts +5 -3
  30. package/dist/run-study.js +7 -11
  31. package/dist/study/adapter-scorer-loader.d.ts +1 -1
  32. package/dist/study/adapter-scorer-loader.js +3 -3
  33. package/dist/study/composition-rules.js +21 -11
  34. package/dist/study/config.d.ts +29 -13
  35. package/dist/study/config.js +174 -157
  36. package/dist/study/discover.d.ts +1 -1
  37. package/dist/study/discover.js +1 -1
  38. package/dist/study/keys.d.ts +250 -2
  39. package/dist/study/keys.js +32 -62
  40. package/dist/study/migrate/convert.js +3 -4
  41. package/dist/study/migrate/v2.d.ts +17 -7
  42. package/dist/study/migrate/v2.js +150 -23
  43. package/dist/study/parse/actors.d.ts +11 -32
  44. package/dist/study/parse/actors.js +11 -156
  45. package/dist/study/parse/comms.js +6 -2
  46. package/dist/study/parse/execution.d.ts +9 -4
  47. package/dist/study/parse/execution.js +4 -39
  48. package/dist/study/parse/{study-v3.d.ts → front.d.ts} +6 -17
  49. package/dist/study/parse/{study-v3.js → front.js} +22 -91
  50. package/dist/study/parse/subject.d.ts +2 -0
  51. package/dist/study/parse/subject.js +15 -18
  52. package/dist/study/persona-resolve.js +7 -9
  53. package/dist/study/plan-base.d.ts +1 -1
  54. package/dist/study/plan-base.js +3 -4
  55. package/dist/study/plan-participants.d.ts +2 -2
  56. package/dist/study/plan-types.d.ts +5 -5
  57. package/dist/study/plan.js +1 -1
  58. package/dist/study/preflight-probes.js +2 -2
  59. package/dist/study/preflight.d.ts +1 -1
  60. package/dist/study/preflight.js +1 -1
  61. package/dist/study/routing.d.ts +5 -5
  62. package/dist/study/routing.js +5 -5
  63. package/dist/study/run-study-options.d.ts +1 -1
  64. package/dist/study/study-deps.d.ts +1 -1
  65. package/dist/study/study-fields.d.ts +12 -21
  66. package/dist/study/study-fields.js +29 -34
  67. package/dist/study/summary.js +9 -4
  68. package/dist/study/types.d.ts +50 -128
  69. package/dist/study/types.js +0 -1
  70. package/dist/study/url-credentials.js +3 -4
  71. package/dist/study/validation.d.ts +2 -10
  72. package/dist/study/validation.js +16 -46
  73. package/dist/study/warnings.d.ts +3 -3
  74. package/dist/study/warnings.js +40 -109
  75. package/dist/substrates/local/runtime-config.js +9 -11
  76. package/docs/contracts/schemas.md +23 -4
  77. package/package.json +1 -1
  78. package/dist/study/parse/study.d.ts +0 -38
  79. package/dist/study/parse/study.js +0 -225
@@ -1,3 +1,251 @@
1
+ export type KeyShape = {
2
+ readonly [key: string]: true | KeyShape;
3
+ };
4
+ export declare const CAPS: {
5
+ maxUsd: true;
6
+ maxTotalUsd: true;
7
+ maxJobs: true;
8
+ maxMinutes: true;
9
+ };
10
+ export declare const PARTICIPANT_ENTRY: {
11
+ id: true;
12
+ actorType: true;
13
+ surface: true;
14
+ caseGroup: true;
15
+ persona: true;
16
+ device: true;
17
+ instruction: true;
18
+ target: true;
19
+ entry: true;
20
+ host: true;
21
+ reasoningEffort: true;
22
+ stopWhen: {
23
+ any: {
24
+ id: true;
25
+ urlIncludes: true;
26
+ urlPathEquals: true;
27
+ textIncludes: true;
28
+ appStatePathEquals: {
29
+ path: true;
30
+ equals: true;
31
+ };
32
+ };
33
+ };
34
+ dwell: {
35
+ ms: true;
36
+ everyMs: true;
37
+ then: true;
38
+ when: {
39
+ any: {
40
+ id: true;
41
+ urlIncludes: true;
42
+ urlPathEquals: true;
43
+ textIncludes: true;
44
+ appStatePathEquals: {
45
+ path: true;
46
+ equals: true;
47
+ };
48
+ };
49
+ };
50
+ };
51
+ };
52
+ export declare const SUBJECT: {
53
+ source: true;
54
+ exposure: true;
55
+ appUrl: true;
56
+ repos: true;
57
+ env: true;
58
+ envValues: true;
59
+ clone: {
60
+ depth: true;
61
+ fanout: true;
62
+ keep: true;
63
+ };
64
+ localTree: {
65
+ exclude: true;
66
+ keep: true;
67
+ maxArchiveBytes: true;
68
+ };
69
+ product: {
70
+ name: true;
71
+ install: true;
72
+ workdir: true;
73
+ upload: true;
74
+ publicSurfaces: true;
75
+ };
76
+ publicTarget: {
77
+ owner: true;
78
+ authorized: true;
79
+ };
80
+ serve: {
81
+ install: true;
82
+ installTimeoutMs: true;
83
+ build: true;
84
+ buildTimeoutMs: true;
85
+ start: true;
86
+ url: true;
87
+ readyTimeoutMs: true;
88
+ };
89
+ state: {
90
+ seed: {
91
+ name: true;
92
+ command: true;
93
+ when: true;
94
+ timeoutMs: true;
95
+ };
96
+ external: true;
97
+ checkpoint: {
98
+ name: true;
99
+ command: true;
100
+ redact: true;
101
+ };
102
+ };
103
+ };
104
+ export declare const ACTOR: {
105
+ type: true;
106
+ persona: true;
107
+ mission: true;
108
+ model: true;
109
+ maxOutputTokens: true;
110
+ localAgent: true;
111
+ reasoningEffort: true;
112
+ stopWhen: {
113
+ any: {
114
+ id: true;
115
+ urlIncludes: true;
116
+ urlPathEquals: true;
117
+ textIncludes: true;
118
+ appStatePathEquals: {
119
+ path: true;
120
+ equals: true;
121
+ };
122
+ };
123
+ };
124
+ dwell: {
125
+ ms: true;
126
+ everyMs: true;
127
+ then: true;
128
+ when: {
129
+ any: {
130
+ id: true;
131
+ urlIncludes: true;
132
+ urlPathEquals: true;
133
+ textIncludes: true;
134
+ appStatePathEquals: {
135
+ path: true;
136
+ equals: true;
137
+ };
138
+ };
139
+ };
140
+ };
141
+ tasks: {
142
+ id: true;
143
+ goal: true;
144
+ success: {
145
+ any: {
146
+ id: true;
147
+ urlIncludes: true;
148
+ urlPathEquals: true;
149
+ textIncludes: true;
150
+ appStatePathEquals: {
151
+ path: true;
152
+ equals: true;
153
+ };
154
+ };
155
+ };
156
+ };
157
+ };
158
+ export declare const EXECUTION: {
159
+ target: true;
160
+ runtime: {
161
+ version: true;
162
+ };
163
+ runtimeAuth: true;
164
+ concurrency: true;
165
+ timeoutMs: true;
166
+ completionTimeoutMs: true;
167
+ egressAllow: true;
168
+ terminal: {
169
+ stdin: true;
170
+ transport: true;
171
+ };
172
+ desktop: {
173
+ template: true;
174
+ browser: true;
175
+ device: true;
176
+ resolution: true;
177
+ sandboxTimeoutMs: true;
178
+ codexAppServer: true;
179
+ recording: true;
180
+ fidelity: {
181
+ mobileEmulation: true;
182
+ deviceScaleFactor: true;
183
+ touch: true;
184
+ userAgent: true;
185
+ };
186
+ media: {
187
+ camera: {
188
+ source: true;
189
+ };
190
+ microphone: {
191
+ source: true;
192
+ };
193
+ };
194
+ };
195
+ };
196
+ /** The sections a humanish.lab.v2 file shares with a v3 one. */
197
+ export declare const SHARED_SECTIONS: {
198
+ policies: {
199
+ redactRepos: true;
200
+ redactScreenshots: true;
201
+ allowPublicTargets: true;
202
+ mediaPermission: true;
203
+ allowPrivateRepoAccess: true;
204
+ allowProviderCredentials: true;
205
+ allowPaymentCredentials: true;
206
+ allowGitHubMutation: true;
207
+ };
208
+ review: {
209
+ scoring: true;
210
+ milestones: true;
211
+ vocabulary: true;
212
+ scorer: {
213
+ ref: true;
214
+ };
215
+ analysis: true;
216
+ };
217
+ defaults: {
218
+ open: true;
219
+ };
220
+ comms: {
221
+ email: {
222
+ kind: true;
223
+ connection: true;
224
+ port: true;
225
+ smtp: {
226
+ hostEnv: true;
227
+ portEnv: true;
228
+ port: true;
229
+ userEnv: true;
230
+ user: true;
231
+ passwordEnv: true;
232
+ password: true;
233
+ };
234
+ external: {
235
+ catchBaseUrl: true;
236
+ inboxBaseUrl: true;
237
+ authTokenEnv: true;
238
+ };
239
+ injectEnv: true;
240
+ linkOrigin: true;
241
+ allowedOrigins: true;
242
+ recipients: {
243
+ lane: true;
244
+ address: true;
245
+ };
246
+ };
247
+ };
248
+ };
1
249
  /**
2
250
  * Whether a dotted key path, such as `caps.maxUsd` or `participants[0].persona`, names a key a
3
251
  * humanish.study.v3 file can set. A path below a leaf is not one: `scenario.ref`, where `scenario`
@@ -10,7 +258,7 @@ export declare function isStudyKeyPath(path: string): boolean;
10
258
  * study file reference to these paths.
11
259
  */
12
260
  export declare function studyKeyPaths(depth: number): string[];
13
- /** The first key in `raw` that V2_KEYS does not list, as an error message; undefined if none. */
14
- export declare function findUnknownV2Key(raw: unknown): string | undefined;
15
261
  /** The first key in a humanish.study.v3 document that the format does not have; undefined if none. */
16
262
  export declare function findUnknownStudyKey(raw: unknown): string | undefined;
263
+ /** The first key in `raw` that `shape` does not list, as an error message; undefined if none. */
264
+ export declare function findUnknownKey(raw: unknown, shape: KeyShape): string | undefined;
@@ -1,7 +1,8 @@
1
1
  // Every mapping key parseStudy reads, by path. A key that is not listed fails the study as
2
2
  // unknown, so a typo stops the run instead of being dropped. `true` marks a leaf: its parser
3
3
  // checks the value (scalars, lists, and mappings that check their own keys). Each level satisfies
4
- // the parsed type's keys, so a field added to a Lab* interface must be added here too.
4
+ // the parsed type's keys, so a field added to a Study* interface must be added here too.
5
+ // migrate/v2.ts builds the humanish.lab.v2 table from the exported sections.
5
6
  const STOP_WHEN = {
6
7
  any: {
7
8
  id: true,
@@ -17,17 +18,17 @@ const STOP_WHEN = {
17
18
  const DWELL = {
18
19
  ms: true,
19
20
  everyMs: true,
20
- // oxlint-disable-next-line unicorn/no-thenable -- `then` is the documented dwell field of humanish.lab.v2
21
+ // oxlint-disable-next-line unicorn/no-thenable -- `then` is the documented dwell field of a study
21
22
  then: true,
22
23
  when: STOP_WHEN,
23
24
  };
24
- const CAPS = {
25
+ export const CAPS = {
25
26
  maxUsd: true,
26
27
  maxTotalUsd: true,
27
28
  maxJobs: true,
28
29
  maxMinutes: true,
29
30
  };
30
- const PARTICIPANT_ENTRY = {
31
+ export const PARTICIPANT_ENTRY = {
31
32
  id: true,
32
33
  actorType: true,
33
34
  surface: true,
@@ -42,9 +43,8 @@ const PARTICIPANT_ENTRY = {
42
43
  stopWhen: STOP_WHEN,
43
44
  dwell: DWELL,
44
45
  };
45
- const SUBJECT = {
46
+ export const SUBJECT = {
46
47
  source: true,
47
- topology: true,
48
48
  exposure: true,
49
49
  appUrl: true,
50
50
  repos: true,
@@ -88,15 +88,8 @@ const SUBJECT = {
88
88
  },
89
89
  },
90
90
  };
91
- const ACTOR = {
91
+ export const ACTOR = {
92
92
  type: true,
93
- count: true,
94
- lanes: PARTICIPANT_ENTRY,
95
- // Roster groups are participants with a count; the parser expands them into `lanes[]`.
96
- roster: {
97
- ...PARTICIPANT_ENTRY,
98
- count: true,
99
- },
100
93
  persona: true,
101
94
  mission: true,
102
95
  model: true,
@@ -106,13 +99,8 @@ const ACTOR = {
106
99
  stopWhen: STOP_WHEN,
107
100
  dwell: DWELL,
108
101
  tasks: { id: true, goal: true, success: STOP_WHEN },
109
- laneFocus: {
110
- id: true,
111
- label: true,
112
- instruction: true,
113
- },
114
102
  };
115
- const EXECUTION = {
103
+ export const EXECUTION = {
116
104
  target: true,
117
105
  runtime: { version: true },
118
106
  runtimeAuth: true,
@@ -120,7 +108,6 @@ const EXECUTION = {
120
108
  timeoutMs: true,
121
109
  completionTimeoutMs: true,
122
110
  egressAllow: true,
123
- caps: CAPS,
124
111
  terminal: { stdin: true, transport: true },
125
112
  desktop: {
126
113
  template: true,
@@ -166,22 +153,25 @@ const EMAIL = {
166
153
  allowedOrigins: true,
167
154
  recipients: { lane: true, address: true },
168
155
  };
169
- const V2_KEYS = {
156
+ // humanish.study.v3. A list entry with a `count` is a group, so `participants` keys an entry's keys
157
+ // and a group's together: parseStudy checks which form the route takes.
158
+ const STUDY_KEYS = {
170
159
  schema: true,
171
160
  id: true,
172
161
  title: true,
173
162
  description: true,
163
+ route: true,
164
+ mode: true,
174
165
  subject: SUBJECT,
175
- actors: ACTOR,
176
- execution: EXECUTION,
177
- // Inline personas are validated by the persona resolver.
178
- personas: true,
179
- scenario: {
180
- ref: true,
181
- mode: true,
182
- inline: true,
183
- caps: CAPS,
166
+ actor: ACTOR,
167
+ participants: {
168
+ ...PARTICIPANT_ENTRY,
169
+ count: true,
184
170
  },
171
+ surfaces: true,
172
+ caps: CAPS,
173
+ execution: EXECUTION,
174
+ scenario: true,
185
175
  policies: {
186
176
  redactRepos: true,
187
177
  redactScreenshots: true,
@@ -203,32 +193,12 @@ const V2_KEYS = {
203
193
  defaults: { open: true },
204
194
  comms: { email: EMAIL },
205
195
  };
206
- function without(shape, ...keys) {
207
- return Object.fromEntries(Object.entries(shape).filter(([key]) => !keys.includes(key)));
208
- }
209
- // humanish.study.v3. The route is declared, so `subject.topology` goes. The participant keys leave
210
- // the actor for `participants`, and `caps` is one top-level block. Top-level `personas` and
211
- // `scenario.inline` are read by no route, and `scenario` is a string.
212
- const STUDY_KEYS = {
213
- schema: true,
214
- id: true,
215
- title: true,
216
- description: true,
217
- route: true,
218
- mode: true,
219
- subject: without(SUBJECT, "topology"),
220
- actor: without(ACTOR, "count", "lanes", "roster", "laneFocus"),
221
- // A count, `{ count, instruction }` or a list of entries. studyToV2 checks which form the route
222
- // takes; an entry with a count is a group.
223
- participants: { ...PARTICIPANT_ENTRY, count: true },
224
- surfaces: true,
225
- caps: CAPS,
226
- execution: without(EXECUTION, "caps"),
227
- scenario: true,
228
- policies: V2_KEYS.policies,
229
- review: V2_KEYS.review,
230
- defaults: V2_KEYS.defaults,
231
- comms: V2_KEYS.comms,
196
+ /** The sections a humanish.lab.v2 file shares with a v3 one. */
197
+ export const SHARED_SECTIONS = {
198
+ policies: STUDY_KEYS.policies,
199
+ review: STUDY_KEYS.review,
200
+ defaults: STUDY_KEYS.defaults,
201
+ comms: STUDY_KEYS.comms,
232
202
  };
233
203
  /**
234
204
  * Whether a dotted key path, such as `caps.maxUsd` or `participants[0].persona`, names a key a
@@ -262,13 +232,13 @@ export function studyKeyPaths(depth) {
262
232
  visit(STUDY_KEYS, "", 1);
263
233
  return paths;
264
234
  }
265
- /** The first key in `raw` that V2_KEYS does not list, as an error message; undefined if none. */
266
- export function findUnknownV2Key(raw) {
267
- return walk(raw, V2_KEYS, "", "study");
268
- }
269
235
  /** The first key in a humanish.study.v3 document that the format does not have; undefined if none. */
270
236
  export function findUnknownStudyKey(raw) {
271
- return walk(raw, STUDY_KEYS, "", "study");
237
+ return findUnknownKey(raw, STUDY_KEYS);
238
+ }
239
+ /** The first key in `raw` that `shape` does not list, as an error message; undefined if none. */
240
+ export function findUnknownKey(raw, shape) {
241
+ return walk(raw, shape, "", "study");
272
242
  }
273
243
  function walk(value, shape, path, noun) {
274
244
  if (Array.isArray(value)) {
@@ -3,13 +3,12 @@
3
3
  // every key it moved and every key it dropped. It refuses a file whose v3 form would parse or plan
4
4
  // differently from the v2 source with the dropped keys removed. v2.ts reads the v2 source.
5
5
  import { isAlias, isMap, isScalar, isSeq, LineCounter, Pair, parseDocument, Scalar, visit, YAMLMap, YAMLSeq, } from "yaml";
6
- import { parseStudy } from "../config.js";
7
- import { parseStudyV3 } from "../parse/study.js";
6
+ import { parseStudy, parseStudyV3 } from "../config.js";
8
7
  import { posInt } from "../parse/values.js";
9
8
  import { planStudy } from "../plan.js";
10
- import { V2_SCHEMA, STUDY_SCHEMA } from "../types.js";
9
+ import { STUDY_SCHEMA } from "../types.js";
11
10
  import { deleteNodeFieldPath, deletePlainFieldPath, pairIndex, readFieldPath, } from "../field-paths.js";
12
- import { checkV2, droppedPaths, v2ShapeReason, v2ToV3Raw } from "./v2.js";
11
+ import { checkV2, droppedPaths, V2_SCHEMA, v2ShapeReason, v2ToV3Raw } from "./v2.js";
13
12
  function isPlainRecord(value) {
14
13
  return typeof value === "object" && value !== null && !Array.isArray(value);
15
14
  }
@@ -1,6 +1,12 @@
1
1
  import { type StudyRoute } from "../routing.js";
2
- import { type StudyConfig, type StudyParticipantEntry, type StudyScenarioCaps } from "../types.js";
2
+ import { type StudyCaps, type StudyConfig, type StudyParticipantEntry } from "../types.js";
3
+ /** The schema of the study format before humanish.study.v3. */
4
+ export declare const V2_SCHEMA = "humanish.lab.v2";
5
+ /** The message for a humanish.lab.v2 document, which the package and the CLI no longer run. */
6
+ export declare const V2_UNSUPPORTED_MESSAGE = "This is a humanish.lab.v2 study, which humanish no longer reads. Convert its file with humanish migrate <path>.";
3
7
  type Raw = Record<string, unknown>;
8
+ /** Where each humanish 0.110 StudyConfig field a config still sets went; empty when it sets none. */
9
+ export declare function v2FieldMoves(config: unknown): string[];
4
10
  type Read<T> = {
5
11
  ok: true;
6
12
  value: T;
@@ -19,21 +25,24 @@ interface V2Scenario {
19
25
  ref?: string;
20
26
  mode?: "dry-run" | "live";
21
27
  inline?: Raw;
22
- caps?: StudyScenarioCaps;
28
+ caps?: StudyCaps;
23
29
  }
24
30
  /** A v2 file that checkV2 accepted, as the v2 parser reads it. */
25
31
  export interface V2Study {
26
32
  readonly route: StudyRoute;
27
33
  /**
28
- * The sections that decide the route and that the v2 parser's first cross-section checks read:
29
- * subject, the actor without its participant keys, execution, policies and comms.
34
+ * The sections that decide the route and that the v2 parser's first cross-section checks read,
35
+ * as a StudyConfig: subject, the actor, execution, policies and comms. Its `route` is the file's
36
+ * route, `shared-world` when `subject.topology` declares one, so routeOf and the composition
37
+ * checks read the declaration as the v2 parser did.
30
38
  */
31
39
  readonly composition: StudyConfig;
40
+ readonly topology: "per-lane-worlds" | "shared-world" | undefined;
32
41
  readonly count: number | undefined;
33
42
  /** `actors[0].lanes`, or the participants `actors[0].roster` expands into. */
34
43
  readonly entries: readonly StudyParticipantEntry[] | undefined;
35
44
  readonly focus: V2Focus | undefined;
36
- readonly executionCaps: StudyScenarioCaps | undefined;
45
+ readonly executionCaps: StudyCaps | undefined;
37
46
  readonly scenario: V2Scenario | undefined;
38
47
  readonly personas: boolean;
39
48
  readonly terminalTimeout: boolean;
@@ -61,8 +70,9 @@ export declare function v2ShapeReason(study: V2Study): string | undefined;
61
70
  export declare function v2ToV3Raw(raw: Raw, route: StudyRoute): Raw;
62
71
  /**
63
72
  * The keys migrate drops: the v2-only rows above and `inert`, the fields parseStudyV3 reports the
64
- * route does not read, then terminal `execution.timeoutMs`, which no terminal code reads. Parents
65
- * come before their children, so a child under a dropped parent is reported with the parent.
73
+ * route does not read (v3 paths, mapped back to the file's), then terminal `execution.timeoutMs`,
74
+ * which no terminal code reads. Parents come before their children, so a child under a dropped
75
+ * parent is reported with the parent.
66
76
  */
67
77
  export declare function droppedPaths(study: V2Study, inert: readonly string[]): string[];
68
78
  export {};