humanish 0.111.1 → 0.111.3

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 (78) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/CHANGELOG.md +29 -0
  3. package/dist/cli/commands/study-route-computer-use.js +3 -8
  4. package/dist/cli/commands/study-route-preview.js +4 -6
  5. package/dist/cli/commands/study-route-shared-world.js +6 -7
  6. package/dist/evidence/redaction.d.ts +2 -2
  7. package/dist/evidence/redaction.js +3 -3
  8. package/dist/observer-app.html +6 -6
  9. package/dist/routes/computer-use/bundle-parts.d.ts +1 -32
  10. package/dist/routes/computer-use/bundle-parts.js +5 -92
  11. package/dist/routes/computer-use/e2b-desktop/prepare.js +11 -4
  12. package/dist/routes/computer-use/fanout-bundle.js +3 -2
  13. package/dist/routes/computer-use/live-phase.js +3 -7
  14. package/dist/routes/computer-use/participant-deps.d.ts +46 -0
  15. package/dist/routes/computer-use/participant-deps.js +48 -0
  16. package/dist/routes/computer-use/participant-facts.d.ts +5 -4
  17. package/dist/routes/computer-use/participant-facts.js +37 -2
  18. package/dist/routes/computer-use/participant-feedback.d.ts +33 -0
  19. package/dist/routes/computer-use/participant-feedback.js +92 -0
  20. package/dist/routes/computer-use/participant-model.d.ts +11 -0
  21. package/dist/routes/computer-use/participant-model.js +15 -0
  22. package/dist/routes/computer-use/preflight.js +33 -65
  23. package/dist/routes/computer-use/result.js +13 -52
  24. package/dist/routes/computer-use/route.js +6 -7
  25. package/dist/routes/computer-use/setup.d.ts +2 -6
  26. package/dist/routes/computer-use/setup.js +47 -93
  27. package/dist/routes/computer-use/single-bundle.js +3 -2
  28. package/dist/routes/computer-use/types.d.ts +2 -2
  29. package/dist/routes/preview.js +2 -8
  30. package/dist/routes/scripted/bundle.d.ts +2 -6
  31. package/dist/routes/scripted/result.d.ts +2 -1
  32. package/dist/routes/scripted/result.js +4 -4
  33. package/dist/routes/scripted/route.js +13 -38
  34. package/dist/routes/scripted/setup.d.ts +2 -1
  35. package/dist/routes/scripted/setup.js +21 -28
  36. package/dist/routes/scripted/subject.d.ts +3 -18
  37. package/dist/routes/scripted/subject.js +22 -78
  38. package/dist/routes/scripted/types.d.ts +2 -2
  39. package/dist/routes/shared-world/bundle.js +36 -1
  40. package/dist/routes/shared-world/comms.d.ts +2 -1
  41. package/dist/routes/shared-world/comms.js +3 -7
  42. package/dist/routes/shared-world/handoff.d.ts +2 -2
  43. package/dist/routes/shared-world/handoff.js +1 -1
  44. package/dist/routes/shared-world/participant-specs.d.ts +4 -5
  45. package/dist/routes/shared-world/participant-specs.js +19 -25
  46. package/dist/routes/shared-world/plan.js +11 -7
  47. package/dist/routes/shared-world/provisioned.d.ts +2 -1
  48. package/dist/routes/shared-world/provisioned.js +37 -80
  49. package/dist/routes/shared-world/result.js +17 -35
  50. package/dist/routes/shared-world/route.js +33 -36
  51. package/dist/routes/shared-world/setup.d.ts +2 -3
  52. package/dist/routes/shared-world/setup.js +11 -29
  53. package/dist/routes/shared-world/types.d.ts +6 -15
  54. package/dist/routes/terminal/dry-run.js +14 -24
  55. package/dist/routes/terminal/live-finish.d.ts +0 -1
  56. package/dist/routes/terminal/live-finish.js +2 -2
  57. package/dist/routes/terminal/route.js +9 -15
  58. package/dist/routes/terminal/session.d.ts +2 -3
  59. package/dist/routes/terminal/session.js +28 -40
  60. package/dist/routes/terminal/trace.d.ts +1 -1
  61. package/dist/routes/terminal/types.d.ts +2 -2
  62. package/dist/run/run.d.ts +55 -1
  63. package/dist/run/run.js +38 -2
  64. package/dist/run/secrets.d.ts +22 -0
  65. package/dist/run/secrets.js +33 -0
  66. package/dist/run/study-result.d.ts +34 -0
  67. package/dist/run/study-result.js +16 -0
  68. package/dist/study/requirements.d.ts +60 -5
  69. package/dist/study/requirements.js +88 -2
  70. package/dist/study/run-study-events.d.ts +4 -3
  71. package/dist/study/run-study-events.js +9 -14
  72. package/dist/study/run-study-options.d.ts +4 -1
  73. package/dist/study/run-study-options.js +22 -32
  74. package/dist/substrates/e2b/subject-sandbox.d.ts +58 -0
  75. package/dist/substrates/e2b/subject-sandbox.js +94 -0
  76. package/docs/contracts/core.md +5 -4
  77. package/docs/contracts/feedback.md +12 -5
  78. package/package.json +1 -1
@@ -0,0 +1,94 @@
1
+ // The subject sandbox of a provisioned clone: the one E2B desktop that serves the app, on the
2
+ // scripted clone route and the shared-world provisioned plane. Both routes create, measure and
3
+ // release it through this class, so a change to its request, its size check or its release
4
+ // reading is one edit. What runs inside the sandbox (the caller's prepare hook, provisioning,
5
+ // getHost) stays with each route.
6
+ import { redactText } from "../../evidence/redaction.js";
7
+ import { DEFAULT_STATE_STEP_TIMEOUT_MS } from "../../subject/state.js";
8
+ import { observeDesktopResources } from "./desktop-resources.js";
9
+ import { SANDBOX_TIMEOUT_BUFFER_MS, SUBJECT_PROVISION_BUDGET_MS } from "./lifetime.js";
10
+ import { acquireE2BDesktopSandbox, readE2BRelease } from "./sandbox.js";
11
+ export class E2BSubjectSandbox {
12
+ sandboxId;
13
+ killed = false;
14
+ /** The scrubbed release warning when the release is unconfirmed. */
15
+ releaseWarning;
16
+ run;
17
+ allocation;
18
+ createdAtMs;
19
+ tornDownAtMs;
20
+ resources;
21
+ constructor(run) {
22
+ this.run = run;
23
+ }
24
+ /**
25
+ * Creates the sandbox and reads its size. The receipt is on disk before this returns, so
26
+ * `humanish reclaim` can kill the sandbox by exact id if the process dies during provisioning.
27
+ */
28
+ async acquire(request) {
29
+ const { envs } = request;
30
+ const { warnings, scrub } = this.run;
31
+ const timeoutMs = request.sessionTimeoutMs +
32
+ SUBJECT_PROVISION_BUDGET_MS +
33
+ request.seed.reduce((sum, step) => sum + (step.timeoutMs ?? DEFAULT_STATE_STEP_TIMEOUT_MS), 0) +
34
+ SANDBOX_TIMEOUT_BUFFER_MS;
35
+ const subject = await acquireE2BDesktopSandbox({
36
+ module: request.module,
37
+ options: {
38
+ apiKey: request.apiKey,
39
+ requestTimeoutMs: request.requestTimeoutMs,
40
+ timeoutMs,
41
+ metadata: request.metadata,
42
+ ...(Object.keys(envs).length > 0 ? { envs } : {}),
43
+ dpi: 96,
44
+ lifecycle: { onTimeout: "kill" },
45
+ },
46
+ template: request.template,
47
+ retry: {
48
+ // A failed first attempt may have allocated a sandbox whose id never reached this run;
49
+ // its own kill-on-timeout reclaims it.
50
+ onRetry: (reason) => {
51
+ warnings.push(`Subject sandbox create retried once after a transient provider error (${redactText(scrub(reason))}). A sandbox the failed attempt may have allocated is reclaimed by its ${timeoutMs} ms timeout.`);
52
+ },
53
+ },
54
+ receipt: { root: request.root, participantId: "subject" },
55
+ });
56
+ this.allocation = subject.allocation;
57
+ this.sandboxId = subject.allocation.resourceId;
58
+ this.createdAtMs = this.run.now();
59
+ const resources = await observeDesktopResources(subject.sandbox);
60
+ this.resources = resources;
61
+ if ("reason" in resources)
62
+ warnings.push(`Subject sandbox resource size unavailable (${resources.reason}); its compute cost remains unpriced.`);
63
+ return subject.sandbox;
64
+ }
65
+ /** Releases the sandbox by exact id, when one was acquired. An unconfirmed release is reported. */
66
+ async release() {
67
+ const { warnings, scrub, now } = this.run;
68
+ if (this.allocation === undefined)
69
+ return;
70
+ const released = await this.allocation.close();
71
+ const reading = readE2BRelease(released, { label: "Subject sandbox", scrub, costSpan: true });
72
+ this.killed = reading.released;
73
+ if (reading.warning)
74
+ warnings.push(reading.warning);
75
+ if (!reading.released)
76
+ this.releaseWarning = reading.warning ?? "Subject sandbox release is unconfirmed.";
77
+ // Without a kill method, or in E2B debug mode, no kill reached E2B, so there is no teardown
78
+ // time to record.
79
+ if (released.status !== "unconfirmed" || released.reason !== "release_unavailable")
80
+ this.tornDownAtMs = now();
81
+ }
82
+ /** The billed span and size, when a sandbox was acquired. */
83
+ desktopUsage() {
84
+ if (this.sandboxId === undefined)
85
+ return undefined;
86
+ return {
87
+ durationMs: this.createdAtMs === undefined || this.tornDownAtMs === undefined
88
+ ? undefined
89
+ : Math.max(0, this.tornDownAtMs - this.createdAtMs),
90
+ observation: this.resources,
91
+ killed: this.killed,
92
+ };
93
+ }
94
+ }
@@ -1,9 +1,10 @@
1
1
  # Core Contract
2
2
 
3
- Each route mints its run id and builds its bundle, and starts the run
4
- through the run scope in `src/run/run.ts`. `startRun` creates
5
- `.humanish/runs/<id>` and begins `status.json`; the `Run` it returns publishes
6
- `run.json`, its projections and the latest pointer. `src/run/paths.ts` holds the
3
+ Each route builds its bundle and starts the run through the run scope in
4
+ `src/run/run.ts`. `startRun` takes the caller's run id or mints one from the
5
+ route's prefix, creates `.humanish/runs/<id>`, begins `status.json` and captures
6
+ the run's source; preview mints its own id and builds its source first. The
7
+ `Run` it returns publishes `run.json`, its projections and the latest pointer. `src/run/paths.ts` holds the
7
8
  shared path rules and `src/run/git-state.ts` captures git state. The table
8
9
  below lists the records current bundles write.
9
10
 
@@ -50,16 +50,23 @@ humanish feedback issue-url --run latest --repo owner/repo
50
50
  ### `list`
51
51
 
52
52
  Reads feedback candidates from the run bundle. Does not mutate. Candidates come
53
- from the computer-use route, when a participant reports friction or abandons the
54
- study goal, and from an adapter's `deriveFeedback` hook.
53
+ from the computer-use and shared-world routes, when a participant reports
54
+ friction or abandons the study goal, and from an adapter's `deriveFeedback` hook.
55
+ Both routes build them with `participantFeedbackCandidates`
56
+ (`src/routes/computer-use/participant-feedback.ts`): one candidate per reporting
57
+ participant, named by its participant id, with that participant's own
58
+ instructions as `expected` and no merging of reports that match.
59
+ A shared-world candidate also cites the run's comms thread when the study
60
+ captured one, since that thread belongs to the shared app. Adapter candidates
61
+ follow the participant candidates.
55
62
 
56
63
  The list also shows any saved draft. An empty `candidates` array means no usable
57
64
  candidate was recorded; it does not mean a run-summary draft is unavailable.
58
65
  Listing does not create a draft or check public-sharing eligibility.
59
66
 
60
- For completed computer-use sessions, participant-reported friction comes from
61
- the redacted closing report, a completed debrief's `frictionReports`, and
62
- observed-report clauses in earlier messages.
67
+ For completed computer-use and shared-world sessions, participant-reported
68
+ friction comes from the redacted closing report, a completed debrief's
69
+ `frictionReports`, and observed-report clauses in earlier messages.
63
70
  Matching `stopWhen` or ending a dwell window does not discard an earlier
64
71
  report or change the successful completion verdict. Exact repeated reports
65
72
  appear once in the participant's candidate. Harness notices, reasoning, and observed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanish",
3
- "version": "0.111.1",
3
+ "version": "0.111.3",
4
4
  "description": "Synthetic user research for apps, CLIs, and agent-facing product flows.",
5
5
  "keywords": [
6
6
  "cli",