humanish 0.140.0 → 0.141.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 (80) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/actors/codex/restricted-participant.js +1 -1
  3. package/dist/actors/computer-use/external-handoff.d.ts +22 -0
  4. package/dist/actors/computer-use/external-handoff.js +83 -0
  5. package/dist/actors/computer-use/loop/observation.d.ts +7 -0
  6. package/dist/actors/computer-use/loop/observation.js +17 -1
  7. package/dist/actors/computer-use/loop/types.d.ts +6 -0
  8. package/dist/analysis/analysis-limits.d.ts +6 -0
  9. package/dist/analysis/analysis-limits.js +6 -0
  10. package/dist/analysis/analysis-lock.d.ts +8 -0
  11. package/dist/analysis/analysis-lock.js +129 -0
  12. package/dist/analysis/automatic-completion.js +7 -0
  13. package/dist/analysis/execute.js +1 -1
  14. package/dist/analysis/job.d.ts +1 -0
  15. package/dist/analysis/job.js +6 -2
  16. package/dist/analysis/recording-ended.d.ts +16 -0
  17. package/dist/analysis/recording-ended.js +29 -0
  18. package/dist/analysis/service.d.ts +1 -3
  19. package/dist/analysis/service.js +23 -50
  20. package/dist/analysis/source.d.ts +1 -1
  21. package/dist/browser-control/protocol.d.ts +8 -0
  22. package/dist/browser-control/protocol.js +21 -0
  23. package/dist/cli/commands/analysis-signals.d.ts +10 -4
  24. package/dist/cli/commands/analysis-signals.js +28 -6
  25. package/dist/cli/commands/runs.js +8 -2
  26. package/dist/cli/commands/study-route-computer-use.js +2 -2
  27. package/dist/cli/commands/study-route-scripted.js +2 -2
  28. package/dist/cli/commands/study-route-shared-world.js +2 -2
  29. package/dist/cli/commands/study-route-terminal.js +2 -2
  30. package/dist/cli/findings.d.ts +23 -0
  31. package/dist/cli/findings.js +80 -4
  32. package/dist/cli/io.js +1 -0
  33. package/dist/evidence/browser-hygiene.d.ts +12 -1
  34. package/dist/evidence/browser-hygiene.js +45 -2
  35. package/dist/feedback/export-bundle.js +1 -1
  36. package/dist/guest/external-links.d.ts +14 -0
  37. package/dist/guest/external-links.js +48 -0
  38. package/dist/guest/runtime-desktop.js +5 -2
  39. package/dist/observer/data.d.ts +4 -0
  40. package/dist/observer/run-routes.js +12 -3
  41. package/dist/observer-app.html +2 -2
  42. package/dist/routes/computer-use/e2b-desktop/desktop.js +27 -17
  43. package/dist/routes/computer-use/e2b-desktop/teardown.js +1 -1
  44. package/dist/routes/computer-use/in-process-desktop.js +2 -2
  45. package/dist/routes/computer-use/live-phase.js +1 -1
  46. package/dist/routes/computer-use/setup.js +2 -2
  47. package/dist/routes/shared-world/route.js +1 -1
  48. package/dist/run/display.d.ts +6 -1
  49. package/dist/run/display.js +11 -0
  50. package/dist/run/host-suspension.js +4 -1
  51. package/dist/run/preflight-receipts.d.ts +3 -12
  52. package/dist/run/preflight-receipts.js +7 -67
  53. package/dist/run/process-owner.d.ts +28 -0
  54. package/dist/run/process-owner.js +83 -0
  55. package/dist/run/projection.js +1 -1
  56. package/dist/run/reclaim.d.ts +6 -0
  57. package/dist/run/reclaim.js +32 -2
  58. package/dist/run/route-shell.js +1 -1
  59. package/dist/run/run-index.d.ts +3 -1
  60. package/dist/run/run-index.js +23 -11
  61. package/dist/run/run.d.ts +16 -5
  62. package/dist/run/run.js +25 -10
  63. package/dist/run/status.d.ts +57 -2
  64. package/dist/run/status.js +87 -1
  65. package/dist/run/stored-runs.d.ts +3 -0
  66. package/dist/run/stored-runs.js +9 -4
  67. package/dist/study/run-study-events.d.ts +7 -1
  68. package/dist/substrates/e2b/desktop-browser.js +3 -1
  69. package/dist/substrates/e2b/desktop-executor.d.ts +7 -0
  70. package/dist/substrates/e2b/desktop-executor.js +3 -0
  71. package/dist/substrates/e2b/desktop-handoff.d.ts +22 -0
  72. package/dist/substrates/e2b/desktop-handoff.js +86 -0
  73. package/dist/substrates/local/runtime-release.js +8 -8
  74. package/dist/tui-app.js +115 -115
  75. package/docs/architecture/observer.md +7 -3
  76. package/docs/contracts/schemas.md +24 -3
  77. package/docs/contracts/study-analysis.md +1 -1
  78. package/docs/product/automatic-analysis.md +29 -7
  79. package/package.json +1 -1
  80. package/skills/humanish/SKILL.md +108 -5
@@ -159,9 +159,13 @@ allowlist, contained file reads, read-only routes and no-store security headers.
159
159
  Served data may also include `runtime` with `state`, `observedAt`, and
160
160
  `source: "local-run-status"`. This is a current observation of a contained,
161
161
  matching `status.json`; it never changes the run's recorded verdict or participant
162
- outcomes. A fresh heartbeat means running, an explicitly finalized record means
163
- finished, a record the CLI's signal handler wrote means interrupted, and stale or
164
- invalid timing means unknown. Missing, malformed, or mismatched records omit the
162
+ outcomes. A fresh heartbeat means running, a record its process wrote as `finished` means
163
+ finished, a record written as `interrupted` (by the CLI's signal handler, by a run that ended
164
+ without publishing, or by `humanish reclaim`) means interrupted, and stale or
165
+ invalid timing means unknown. While the run's own process analyzes it after the run ended,
166
+ `runtime.analysis` is `{ startedAt }`: the status band reads **Writing findings** and the
167
+ Findings view shows the analysis as pending, with how long ago it started, before the analysis
168
+ job exists. Missing, malformed, or mismatched records omit the
165
169
  observation. A stale heartbeat alone does not prove interruption, and stored PIDs
166
170
  are neither probed nor returned. Static rendering and export do not create this
167
171
  served-only observation.
@@ -1411,9 +1411,9 @@ proof claims zero on a `null` line, or when known spend exceeds the declared cap
1411
1411
 
1412
1412
  `humanish.run-status.v1` is `status.json`, written inside each run directory by
1413
1413
  every route at run start, refreshed on a fixed cadence while the run is
1414
- alive, and finalized when it ends: `{ schema, runId, state: running |
1414
+ alive, and rewritten when the run ends: `{ schema, runId, state: running |
1415
1415
  finished | interrupted, mode, study?, sandboxes?, pid, startedAt, updatedAt, completedAt?,
1416
- signal?, outcome? }`. A record written by 0.108 or earlier also has `lab`, with the same value as
1416
+ signal?, reason?, outcome?, analysis? }`. A record written by 0.108 or earlier also has `lab`, with the same value as
1417
1417
  `study`, and readers accept it. `sandboxes: none` is written at run start by a route that has no
1418
1418
  way to create a sandbox for the run: a scripted run against an `app-url` subject. A run that may
1419
1419
  create one has no `sandboxes` field. `humanish reclaim` reports such a run `clean` with
@@ -1453,6 +1453,24 @@ with it at publish: false means some participant or desktop usage has no price
1453
1453
  and the estimate is a lower bound. A record without it, as every record before
1454
1454
  0.109, says nothing about completeness, and readers treat it as unknown.
1455
1455
 
1456
+ `finished` means the run published its bundle. A process that leaves the run without publishing
1457
+ one, as a route that throws or returns a refusal after the run started does, writes
1458
+ `interrupted` with `reason: no-publish` and no `signal`; humanish 0.139.1 and earlier wrote
1459
+ `finished` with no `outcome` there. `humanish reclaim --run <id>` (not `--check`) rewrites a
1460
+ `running` record whose heartbeat is stale as `interrupted` with `reason: reclaimed` and
1461
+ `completedAt` set to its last `updatedAt`, and reports `markedInterrupted: true`. Either way the
1462
+ run is excluded from a study's duration and cost medians, which count only finished runs. Readers
1463
+ older than `reason` read such a record as interrupted.
1464
+
1465
+ `analysis` is added to a `finished` record when automatic analysis starts in the run's own
1466
+ process: `{ startedAt, endedAt?, state? }`. While it runs, the heartbeat refreshes `updatedAt`
1467
+ again; `completedAt` stays when the participants ended, so a run's duration excludes its
1468
+ analysis. When analysis ends, `endedAt` and `state` (the automatic analysis outcome's state:
1469
+ `complete`, `partial`, `failed`, `cancelled`, `skipped` or `unknown`) are written. An `analysis`
1470
+ without `endedAt` whose `updatedAt` is older than three touch intervals belonged to a process that
1471
+ stopped during analysis. `runAnalysisPhase` (`src/run/status.ts`) reads it as `running`,
1472
+ `stopped` or `ended`. Readers that predate the field ignore it.
1473
+
1456
1474
  `interrupted` with `signal` (`SIGINT`, `SIGTERM` or `SIGHUP`) is written by
1457
1475
  the CLI's run command when that signal stops a live run outside post-run
1458
1476
  analysis. It first prints one line saying it is stopping and refuses any further sandbox
@@ -1496,7 +1514,10 @@ cheapest-source-first: the `status.json` record, else the bundle, else the run
1496
1514
  directory alone. `{ runId, derivedFrom: status | bundle | directory, liveness,
1497
1515
  mode?, pid?, study?, startedAt?, updatedAt?, completedAt?, verdict?, ok?,
1498
1516
  failure?, participants?, estimatedCostUsd?, estimatedCostComplete?,
1499
- analysisCost?, durationMs? }`. `ok` and `failure` (the first execution failure)
1517
+ analysisCost?, durationMs?, analysis? }`. `analysis` is the status record's automatic analysis as
1518
+ `runAnalysisPhase` reads it, `{ state: running | stopped | ended, startedAt, endedAt?, outcome? }`;
1519
+ `humanish runs` and the TUI add `analyzing` or `analysis stopped` to the run's display from it, and
1520
+ `humanish runs --json` carries it on each run. `ok` and `failure` (the first execution failure)
1500
1521
  are the run's own outcome; `runDisplay` (`src/run/display.ts`) reads them with
1501
1522
  `liveness` and `verdict`, so the TUI and `humanish stats` show a run with
1502
1523
  `ok: false` as failed whatever its verdict. The point is cost: walking
@@ -424,7 +424,7 @@ It contains only the attempt/run IDs, input/config/source digests, prompt versio
424
424
  and timestamp. The live caller retains a binding to that exact directory and
425
425
  publishes the final receipt once. A start without usable final accounting means
426
426
  dispatch and spend remain unresolved; it is not proof that a provider charged.
427
- A cancellation before transport can finalize with `usage.dispatched: false`.
427
+ A cancellation before transport can end with `usage.dispatched: false`.
428
428
  Admission refusals, missing credentials and rejected dispatch guards do not
429
429
  create a potentially paid attempt. Reuse does not create a new start.
430
430
 
@@ -86,13 +86,34 @@ never enables analysis by default and rejects an explicit analysis mapping
86
86
  before execution. `false` is accepted on every route. Dry runs show analysis
87
87
  as skipped, without reading analysis credentials or making a provider request.
88
88
 
89
- Participant execution finishes and its recording is finalized before analysis is
90
- queued. A participant who was blocked or interrupted can still have useful
91
- retained evidence; analysis requires a verified live recording, not a successful
92
- participant outcome. An active, missing or invalid recording is not analyzed.
93
- Default analysis also skips recordings containing only setup or failure records
94
- with no retained participant activity. A desktop startup failure does not start
95
- an analysis request. The original failure remains visible.
89
+ Automatic analysis starts when the run ends: every participant has stopped and the run has
90
+ published its bundle (`run.json` with an outcome, and `status.json` state `finished`). It runs in
91
+ the same process, so `humanish run` returns only after it. A participant who was blocked or
92
+ interrupted can still have useful retained evidence; analysis requires a verified live recording
93
+ whose participant streams all ended, not a successful participant outcome. An active, missing or
94
+ invalid recording is not analyzed. Default analysis also skips recordings containing only setup
95
+ or failure records with no retained participant activity. A desktop startup failure does not
96
+ start an analysis request. The original failure remains visible.
97
+
98
+ While analysis runs, the run reads as analyzing. `status.json` records `analysis.startedAt` and
99
+ its heartbeat keeps refreshing `updatedAt`; `humanish runs` lists the run as `passed, analyzing`
100
+ (or its other result); the TUI adds `analyzing` to the run; the served Observer shows **Writing
101
+ findings** in its status band and the Findings tab in a pending state with how long ago analysis
102
+ started; `humanish review` says the analysis is running and when it started. In a terminal, `run`
103
+ prints `Participants finished. Writing findings; this usually takes 1 to 4 minutes.` to stderr,
104
+ then `Still writing findings: <elapsed> elapsed.` every 20 seconds. With `--json` it prints only
105
+ the first line, on stderr.
106
+
107
+ A run whose process ended before it published (a crash, or a refusal after the run started) is
108
+ recorded `interrupted` with `reason: no-publish`, and one whose process was killed is marked
109
+ `interrupted` by `humanish reclaim`. `humanish analyze` accepts an interrupted run when every
110
+ participant stream ended; when one was still running it refuses with
111
+ `ANALYSIS_RUN_INCOMPLETE` and names `humanish run <study>`. It refuses a run that is still alive
112
+ with `ANALYSIS_RUN_ACTIVE`, and a run whose heartbeat stopped while its pid is still in use with
113
+ `ANALYSIS_RUN_UNCONFIRMED`, naming `humanish reclaim --run <id>`. When the process running an
114
+ automatic analysis stops before it finishes, `review` says so and names the `analyze` command
115
+ that runs it again. That command takes over the run's `.analysis-lock` when the process that
116
+ held it is gone, and says so in a warning.
96
117
 
97
118
  CLI live starts disclose the selected analyst, its expected cost range and cap, or unknown account dollars before execution.
98
119
  `humanish study check <study> --json` and the TUI study screen also expose the
@@ -187,6 +208,7 @@ JSON also has `findings`:
187
208
  | `AUTOMATIC_ANALYSIS_PUBLICATION_FAILED` | `failed` | The analysis could not be published |
188
209
  | `AUTOMATIC_ANALYSIS_CANCELLATION_UNAVAILABLE` | `unknown` | A cancellation could not be recorded |
189
210
  | `AUTOMATIC_ANALYSIS_OUTCOME_UNKNOWN` | `unknown` | The outcome could not be determined |
211
+ | `AUTOMATIC_ANALYSIS_STOPPED` | `unknown` | Read only: the process running it stopped refreshing its job record |
190
212
 
191
213
  A run refused before it was created (`run: not-created`) prints no `analysis:` line, since there
192
214
  is no run to analyze and the refusal says why. Its JSON keeps the record, with
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanish",
3
- "version": "0.140.0",
3
+ "version": "0.141.0",
4
4
  "description": "Synthetic user research for apps, CLIs, and agent-facing product flows.",
5
5
  "keywords": [
6
6
  "cli",
@@ -298,6 +298,63 @@ fails with `HUMANISH_STUDY_RETIRED_DIRECTORY`; move it to the matching `studies/
298
298
  run looks like idle tiles — another reason to leave the cap out unless you
299
299
  need it.
300
300
 
301
+ ### Phone, tablet and desktop in one study
302
+
303
+ When the user asks to test several screen sizes, write one study with one
304
+ `participants` entry per size and set `device` on each entry. The participants run
305
+ at once, the Observer shows them side by side, and the findings compare them.
306
+
307
+ ```yaml
308
+ schema: humanish.study.v3
309
+ id: devices
310
+ title: Phone, tablet and desktop
311
+ route: computer-use
312
+ mode: live
313
+ subject:
314
+ source: app-url
315
+ appUrl: "http://127.0.0.1:3000"
316
+ actor:
317
+ type: local-agent
318
+ localAgent: codex
319
+ persona: synthetic-new-user
320
+ mission: "Sign up and create your first project. Say what was hard to read or reach."
321
+ participants:
322
+ - id: phone
323
+ device: mobile
324
+ - id: tablet
325
+ device: tablet
326
+ - id: desktop
327
+ device: desktop
328
+ execution:
329
+ target: local
330
+ timeoutMs: 600000
331
+ desktop:
332
+ fidelity:
333
+ mobileEmulation: true
334
+ ```
335
+
336
+ This runs on the local browser runtime with the user's Codex login. For a deployed
337
+ app, set `execution.target: e2b-desktop`, put the public URL in `subject.appUrl`,
338
+ add `policies.allowPublicTargets: true`, and give each entry a `target` (below).
339
+
340
+ - `device` takes a preset: `mobile` (414×896), `small-mobile`, `narrow-mobile`,
341
+ `tablet` (820×1180), `desktop` (1440×950) or `wide` (1920×1080). The preset
342
+ sizes the participant's screen and browser window.
343
+ - `fidelity.mobileEmulation: true` gives the phone participants a mobile viewport,
344
+ device pixel ratio, touch and a mobile user agent in Chromium. Tablet and
345
+ desktop participants are left as they are.
346
+ - Do not run one study per size, draw a device frame inside the page, or resize
347
+ the browser from the mission. Those produce desktop-ratio evidence labelled as
348
+ a phone, and three runs the Observer cannot compare.
349
+ - Participants on one public app (`policies.allowPublicTargets: true`) share its
350
+ data, so more than one is refused unless each entry sets `target`: a separate
351
+ test account, tenant or seeded URL per participant, or the same URL when the
352
+ task has each participant create its own account. A loopback or clone subject
353
+ needs no `target`.
354
+ - On a local study, `doctor --study` fails its runtime row when the installed
355
+ local runtime predates per-participant screens; `humanish runtime setup`
356
+ installs the pinned one. Do not move the study to E2B without asking the user.
357
+
301
358
  Use committed `humanish/studies/*.yaml` for public-safe, reproducible studies. Use
302
359
  ignored `.humanish/studies/*.yaml` or `.humanish/local/studies/*.yaml` for private repo
303
360
  targets, local-only dogfood, or machine-specific settings. Never commit private
@@ -472,6 +529,26 @@ Label the participant's own report in the review summary as the participant's ac
472
529
  place of findings; `analysis.next` is null when no command can produce them. `latest` moves with
473
530
  every run, including dry runs, so use the run id the run printed.
474
531
 
532
+ ## Long Studies: `run` Blocks Until Its Findings
533
+
534
+ `humanish run <study>` returns only after every participant has ended and the automatic analysis
535
+ has finished. A participant with `endAfterMs` can keep a study running for up to 24 hours, and
536
+ analysis usually adds 1 to 4 minutes after the last participant ends. A shell timeout that sends
537
+ SIGTERM stops the run: humanish marks it interrupted and stops its sandboxes.
538
+
539
+ - Start a study that can outlive your shell call in tmux, for example
540
+ `tmux new -d -s study 'npx humanish run <study> --json --no-open > run-result.json 2> run.log'`.
541
+ `--detach` only skips the Observer server at the end; it does not move the run to the background.
542
+ - The first stderr line names the run: `Run <id> started. Follow it with humanish observe --run <id>`.
543
+ If you did not keep it, `npx humanish runs --json` lists runs newest first.
544
+ - In `runs --json`, a run whose participants are working has `display.state: running`; once they
545
+ end, its `display` is their result and `analysis.state` is `running` while findings are written.
546
+ - `npx humanish review --run <id> --json` says where the run is: participants running, analysis
547
+ running since a given time, or the findings.
548
+ - If the run's process died, `npx humanish reclaim --run <id>` stops its sandboxes and marks it
549
+ interrupted. `npx humanish analyze --run <id>` then analyzes it if every participant had ended,
550
+ and otherwise names the command that runs the study again.
551
+
475
552
  ## Optional Live E2B Study
476
553
 
477
554
  Live headed E2B desktop participants are optional. Add the substrate dependency only
@@ -481,12 +558,18 @@ when the user explicitly wants live E2B execution:
481
558
  npm i -D @e2b/desktop
482
559
  ```
483
560
 
484
- Then confirm env var names are documented without values:
561
+ Then confirm env var names are documented without values. Which keys a hosted
562
+ study needs depends on its participant and analyst:
485
563
 
486
- ```bash
487
- E2B_API_KEY
488
- OPENAI_API_KEY
489
- ```
564
+ | Participant | Keys |
565
+ | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
566
+ | `openai-computer-use` | `E2B_API_KEY`, `OPENAI_API_KEY` |
567
+ | `local-agent` with `localAgent: codex` | `E2B_API_KEY` and `codex login`; the findings analysis uses the same Codex account |
568
+ | `local-agent` with `localAgent: claude` | `E2B_API_KEY` and `claude auth login`; `OPENAI_API_KEY` for findings; without it, a signed-in Codex can analyze the run (the run prints the command) |
569
+
570
+ A hosted Codex participant does not need `OPENAI_API_KEY`. Do not report a missing
571
+ `OPENAI_API_KEY` as the reason a Codex study on E2B fails; read the
572
+ `doctor --study` row instead.
490
573
 
491
574
  Do not paste values into files, prompts, run bundles, issue drafts, or logs.
492
575
  Load local values only at invocation time:
@@ -502,6 +585,26 @@ declared scenario is developer-experience testing. Private repos are allowed
502
585
  only as explicit maintainer-authorized runs with repo redaction left on; never
503
586
  publish their names, screenshots, logs, source snippets, or operational details.
504
587
 
588
+ ## Setup errors and what they mean
589
+
590
+ Run `npx humanish doctor --study <id>` before the first live run and read every
591
+ `missing` row. The common ones:
592
+
593
+ - `codex_unsafe_configuration` on a hosted Codex participant: the row names the
594
+ setting humanish refused. Report it to the user; never edit, move or rename
595
+ their Codex, agent or skill config to make a run pass. An `AGENTS.md` in the
596
+ Codex home is admitted, and doctor notes that the participant also receives it.
597
+ - `subject.appUrl` must be loopback: a non-loopback URL needs
598
+ `policies.allowPublicTargets: true`, and with more than one participant, a
599
+ `target` on each entry.
600
+ - "Will be skipped: OPENAI_API_KEY is missing" on `post-run analysis` (an
601
+ `openai-computer-use` or Claude participant): the run works without findings,
602
+ and when Codex is signed in, its `next:` line gives the command that analyzes it with Codex.
603
+ `review.analysis.provider: codex` makes that the study's default.
604
+ - A local browser study refuses a non-loopback URL, a port below 1024, hosted
605
+ templates and `sandboxTimeoutMs`. The message names the setting; do not switch the
606
+ study to a hosted target unless the user agrees to E2B usage.
607
+
505
608
  ## Reporting Back
506
609
 
507
610
  Report a live run's findings first, as