humanish 0.114.0 → 0.116.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 (204) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/CONTEXT.md +1 -1
  3. package/README.md +7 -6
  4. package/TELEMETRY.md +6 -0
  5. package/dist/actors/closing-report-limits.d.ts +11 -0
  6. package/dist/actors/closing-report-limits.js +13 -0
  7. package/dist/actors/closing-report.d.ts +68 -0
  8. package/dist/actors/closing-report.js +55 -0
  9. package/dist/actors/codex/app-server-ui.js +11 -4
  10. package/dist/actors/codex/restricted-participant-policy.d.ts +4 -47
  11. package/dist/actors/codex/restricted-participant-policy.js +12 -40
  12. package/dist/actors/computer-use/loop/debrief.d.ts +1 -10
  13. package/dist/actors/computer-use/loop/debrief.js +68 -70
  14. package/dist/actors/computer-use/loop/impressions.d.ts +2 -2
  15. package/dist/actors/computer-use/loop/impressions.js +6 -1
  16. package/dist/actors/computer-use/loop.d.ts +0 -1
  17. package/dist/actors/computer-use/loop.js +3 -3
  18. package/dist/actors/computer-use/openai-conversation.d.ts +1 -1
  19. package/dist/actors/computer-use/openai-conversation.js +3 -29
  20. package/dist/actors/computer-use/openai-provider.js +11 -5
  21. package/dist/actors/contract.d.ts +2 -20
  22. package/dist/actors/contract.js +0 -13
  23. package/dist/actors/scripted-browser/steps.js +6 -5
  24. package/dist/analysis/admission.d.ts +14 -4
  25. package/dist/analysis/admission.js +14 -9
  26. package/dist/analysis/analysis-limits.d.ts +35 -0
  27. package/dist/analysis/analysis-limits.js +41 -0
  28. package/dist/analysis/execute.d.ts +5 -0
  29. package/dist/analysis/execute.js +3 -0
  30. package/dist/analysis/finding-lead.d.ts +27 -0
  31. package/dist/analysis/finding-lead.js +23 -0
  32. package/dist/analysis/service.js +5 -3
  33. package/dist/analysis/validation.js +14 -21
  34. package/dist/cli/commands/analyze.js +2 -1
  35. package/dist/cli/commands/notes.js +5 -3
  36. package/dist/cli/commands/run-signals.js +5 -1
  37. package/dist/cli/commands/runs.js +2 -1
  38. package/dist/cli/commands/setup.js +3 -3
  39. package/dist/cli/commands/study-format.d.ts +5 -2
  40. package/dist/cli/commands/study-format.js +29 -9
  41. package/dist/cli/commands/study-route-computer-use.js +2 -2
  42. package/dist/cli/commands/study-route-scripted.js +1 -1
  43. package/dist/cli/commands/tui.js +59 -17
  44. package/dist/cli/doctor.js +9 -2
  45. package/dist/cli/findings-text.d.ts +5 -0
  46. package/dist/cli/findings-text.js +169 -0
  47. package/dist/cli/findings.d.ts +0 -4
  48. package/dist/cli/findings.js +11 -195
  49. package/dist/cli/io.js +10 -8
  50. package/dist/cli/telemetry.js +10 -22
  51. package/dist/cli/update-check.js +10 -19
  52. package/dist/cli/user-config.d.ts +11 -0
  53. package/dist/cli/user-config.js +25 -0
  54. package/dist/comms/connections.js +4 -3
  55. package/dist/comms/email-catch.js +7 -31
  56. package/dist/comms/setup.js +18 -12
  57. package/dist/evidence/encoded-text.d.ts +12 -11
  58. package/dist/evidence/encoded-text.js +81 -70
  59. package/dist/evidence/redaction.d.ts +11 -0
  60. package/dist/evidence/redaction.js +30 -11
  61. package/dist/evidence/secret-scrub.d.ts +26 -3
  62. package/dist/evidence/secret-scrub.js +172 -38
  63. package/dist/feedback/draft.d.ts +1 -1
  64. package/dist/feedback/draft.js +13 -16
  65. package/dist/feedback/export-bundle.js +21 -17
  66. package/dist/feedback/export.js +32 -46
  67. package/dist/feedback/feedback.js +31 -26
  68. package/dist/guest/desktop-keys.js +59 -5
  69. package/dist/keys/key-resolution.d.ts +9 -0
  70. package/dist/keys/key-resolution.js +62 -55
  71. package/dist/keys/key-status.d.ts +2 -0
  72. package/dist/keys/key-status.js +9 -16
  73. package/dist/observer/artifact.d.ts +1 -1
  74. package/dist/observer/http.d.ts +7 -0
  75. package/dist/observer/http.js +19 -1
  76. package/dist/observer/notes-route.d.ts +0 -1
  77. package/dist/observer/notes-route.js +24 -44
  78. package/dist/observer/pinned-files.js +4 -19
  79. package/dist/observer/render.js +6 -4
  80. package/dist/observer/run-routes.js +13 -28
  81. package/dist/observer/serve.js +2 -1
  82. package/dist/observer-app.html +7 -7
  83. package/dist/routes/computer-use/bundle-parts.d.ts +4 -4
  84. package/dist/routes/computer-use/bundle-parts.js +2 -2
  85. package/dist/routes/computer-use/bundle.d.ts +4 -5
  86. package/dist/routes/computer-use/bundle.js +1 -3
  87. package/dist/routes/computer-use/fanout-records.js +10 -11
  88. package/dist/routes/computer-use/live-phase.d.ts +1 -1
  89. package/dist/routes/computer-use/live-phase.js +0 -1
  90. package/dist/routes/computer-use/participant-execution.d.ts +3 -2
  91. package/dist/routes/computer-use/participant-runs.d.ts +6 -2
  92. package/dist/routes/computer-use/participant-runs.js +9 -5
  93. package/dist/routes/computer-use/plan.d.ts +3 -0
  94. package/dist/routes/computer-use/plan.js +14 -11
  95. package/dist/routes/computer-use/rerun-selection.js +2 -2
  96. package/dist/routes/computer-use/result.js +3 -6
  97. package/dist/routes/computer-use/setup.d.ts +4 -6
  98. package/dist/routes/computer-use/setup.js +2 -6
  99. package/dist/routes/computer-use/single-bundle.d.ts +4 -5
  100. package/dist/routes/computer-use/single-bundle.js +22 -25
  101. package/dist/routes/computer-use/subject-projection.d.ts +4 -8
  102. package/dist/routes/computer-use/subject-projection.js +9 -31
  103. package/dist/routes/computer-use/types.d.ts +4 -41
  104. package/dist/routes/scripted/scenario.js +28 -12
  105. package/dist/routes/scripted/session-result.js +6 -3
  106. package/dist/routes/shared-world/bundle-records.js +2 -8
  107. package/dist/routes/shared-world/participant-specs.d.ts +0 -2
  108. package/dist/routes/shared-world/participant-specs.js +1 -20
  109. package/dist/routes/shared-world/plan.d.ts +3 -0
  110. package/dist/routes/shared-world/plan.js +23 -3
  111. package/dist/routes/shared-world/setup.js +2 -2
  112. package/dist/routes/terminal/ledger.d.ts +9 -9
  113. package/dist/routes/terminal/ledger.js +63 -40
  114. package/dist/routes/terminal/live-finish.js +7 -10
  115. package/dist/routes/terminal/plan.d.ts +3 -0
  116. package/dist/routes/terminal/plan.js +8 -5
  117. package/dist/routes/terminal/token-usage.d.ts +1 -2
  118. package/dist/routes/terminal/token-usage.js +4 -6
  119. package/dist/routes/terminal/types.d.ts +9 -1
  120. package/dist/run/bundle-shape.js +6 -1
  121. package/dist/run/bundle.d.ts +5 -3
  122. package/dist/run/contained-output.d.ts +59 -10
  123. package/dist/run/contained-output.js +160 -68
  124. package/dist/run/detail.d.ts +2 -1
  125. package/dist/run/detail.js +12 -7
  126. package/dist/run/escape-sequences.d.ts +11 -0
  127. package/dist/run/escape-sequences.js +93 -0
  128. package/dist/run/evidence-files.d.ts +8 -1
  129. package/dist/run/evidence-files.js +24 -18
  130. package/dist/run/locate.d.ts +32 -3
  131. package/dist/run/locate.js +45 -40
  132. package/dist/run/note-files.d.ts +26 -45
  133. package/dist/run/note-files.js +86 -58
  134. package/dist/run/note-shape.d.ts +35 -0
  135. package/dist/run/note-shape.js +20 -0
  136. package/dist/run/notes.d.ts +3 -14
  137. package/dist/run/notes.js +23 -75
  138. package/dist/run/outcomes.d.ts +24 -0
  139. package/dist/run/outcomes.js +19 -0
  140. package/dist/run/participant-records.d.ts +2 -0
  141. package/dist/run/participant-records.js +17 -2
  142. package/dist/run/paths.d.ts +2 -0
  143. package/dist/run/paths.js +5 -1
  144. package/dist/run/preflight-receipts.js +13 -8
  145. package/dist/run/project.js +7 -4
  146. package/dist/run/reclaim-outcomes.d.ts +8 -6
  147. package/dist/run/reclaim-outcomes.js +10 -6
  148. package/dist/run/reclaim.d.ts +5 -0
  149. package/dist/run/reclaim.js +43 -16
  150. package/dist/run/run-clock.d.ts +50 -0
  151. package/dist/run/run-clock.js +66 -0
  152. package/dist/run/run.js +5 -2
  153. package/dist/run/sandbox-ids.d.ts +7 -2
  154. package/dist/run/sandbox-ids.js +27 -11
  155. package/dist/run/secrets.d.ts +2 -1
  156. package/dist/run/secrets.js +18 -22
  157. package/dist/run/stored-runs.js +14 -7
  158. package/dist/run/transient-comms-secrets.d.ts +6 -5
  159. package/dist/run/transient-comms-secrets.js +23 -39
  160. package/dist/run-study.js +22 -13
  161. package/dist/study/adapter-scorer-loader.js +6 -4
  162. package/dist/study/discover.js +9 -6
  163. package/dist/study/init-paths.js +7 -5
  164. package/dist/study/persona-resolve.d.ts +2 -2
  165. package/dist/study/persona-resolve.js +9 -5
  166. package/dist/study/plan-types.d.ts +2 -2
  167. package/dist/study/plan.d.ts +5 -3
  168. package/dist/study/plan.js +18 -9
  169. package/dist/study/preflight-probes.js +11 -3
  170. package/dist/study/preflight.js +9 -5
  171. package/dist/study/summary.d.ts +12 -7
  172. package/dist/study/summary.js +24 -24
  173. package/dist/study/warnings.d.ts +19 -12
  174. package/dist/study/warnings.js +39 -17
  175. package/dist/substrates/e2b/connection.d.ts +12 -0
  176. package/dist/substrates/e2b/connection.js +35 -0
  177. package/dist/substrates/e2b/desktop-executor.d.ts +1 -3
  178. package/dist/substrates/e2b/desktop-executor.js +42 -9
  179. package/dist/substrates/e2b/lifetime.d.ts +24 -4
  180. package/dist/substrates/e2b/lifetime.js +41 -4
  181. package/dist/substrates/e2b/sandbox.d.ts +4 -3
  182. package/dist/substrates/e2b/sandbox.js +13 -18
  183. package/dist/substrates/e2b/sdk.d.ts +20 -9
  184. package/dist/substrates/local/capacity.d.ts +18 -2
  185. package/dist/substrates/local/capacity.js +21 -2
  186. package/dist/substrates/local/runtime-host.js +21 -14
  187. package/dist/substrates/local/runtime.js +3 -11
  188. package/dist/tui/contract.d.ts +30 -5
  189. package/dist/tui/contract.js +4 -1
  190. package/dist/tui-app.js +149 -147
  191. package/dist/verify/artifacts.d.ts +9 -2
  192. package/dist/verify/artifacts.js +37 -13
  193. package/dist/verify/liveness.js +19 -5
  194. package/dist/verify/verify.js +54 -27
  195. package/docs/architecture/actor-contract.md +3 -3
  196. package/docs/architecture/guest-desktop.md +7 -2
  197. package/docs/architecture/restricted-codex-analysis.md +1 -1
  198. package/docs/architecture/state-driven-executor.md +5 -3
  199. package/docs/contracts/run-bundle.md +25 -13
  200. package/docs/contracts/schemas.md +38 -29
  201. package/docs/contracts/study-analysis.md +22 -5
  202. package/docs/product/automatic-analysis.md +7 -1
  203. package/package.json +8 -7
  204. package/skills/humanish/SKILL.md +1 -2
package/CHANGELOG.md CHANGED
@@ -8,6 +8,47 @@ The Unreleased section holds the full notes for the next version until it is tag
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 0.116.0: Hosted keyboard input, E2B sandbox ceiling, bounded run reads, run header, TUI live starts (2026-10-09)
12
+
13
+ humanish 0.116.0 delivers a hosted participant's arrow keys and punctuation, lets the operator
14
+ raise the E2B sandbox lifetime, and reads run files within size limits. On hosted E2B desktops,
15
+ `ARROWDOWN`, Page Up, Page Down, `?`, `/` and other names missing from `@e2b/desktop`'s key table
16
+ reached xdotool unchanged, which ignored them while the run recorded the keypress as completed;
17
+ both desktops now translate key names through one table and refuse a name outside it.
18
+ `HUMANISH_E2B_MAX_SANDBOX_MINUTES` sets the longest sandbox lifetime the planners allow, 60 minutes
19
+ when unset and up to 1440 on E2B's Pro plan. A refusal names the value that admits the study, and
20
+ `humanish doctor` shows the ceiling. A route's sandbox kill, and the kills, checks and listings of
21
+ `humanish reclaim`, carry the run's E2B key. `verify` reads at most 32 MiB of a run file and lists
22
+ a larger file, or a run past 10,000 entries, as unscanned, and every contained read tells a missing
23
+ file from one it refused. A run whose analysis did not complete reads `live run finished; the
24
+ analysis did not complete`, a run refused before it was created prints no `analysis:` line, and a
25
+ `desktop-cli` run names its product on the `subject:` line and in run.json. In `humanish tui`, the
26
+ cursor follows the selected row through a refresh, a live start of a `mode: dry-run` file says
27
+ `needs mode: live`, Run again on a live run asks for a second Enter, the confirm prompt restates
28
+ the study's cost, and `c` lists every provider key. `NO_UPDATE_NOTIFIER` turns the update check
29
+ off.
30
+
31
+ [Release notes](https://github.com/danielgwilson/humanish/releases/tag/v0.116.0)
32
+
33
+ ## 0.115.0: Closing report limits, admitted analysis cost, concurrent reviewer notes, known-value scrub (2026-10-08)
34
+
35
+ humanish 0.115.0 asks the OpenAI computer-use participant for its closing report and impressions
36
+ within the limits its reply is checked against, keeps every reviewer note added at the same time,
37
+ and finds more encoded forms of a registered secret. The participant's request schema now carries
38
+ the limits the parser already enforced: a summary of up to 4,000 characters, up to 8 friction
39
+ reports of up to 2,000 characters and up to 6 impressions of up to 500 characters, so a longer
40
+ reply is no longer requested and then dropped. `analyze --json` adds `admission.admittedCostUsd`,
41
+ the smallest `--max-cost` that admits the analysis, and `humanish analyze` names its refusal
42
+ command as `npx humanish analyze` where humanish is a dev dependency. A reviewer note is created
43
+ under its own name, so notes added together from the Observer and `humanish notes --add` are all
44
+ kept. `export --local-only` keeps a `blocked` run `blocked` when its analysis fails the export's
45
+ own check. The known-value scrub exempts only the markers humanish writes, reads text in the same
46
+ five ways as `verify`, and finds a value that overlaps itself in time linear in the text. The TUI's
47
+ study screen shows the expected analysis cost line that `study check` prints, telemetry reads
48
+ `CI=false` as off, and `humanish keys set constructor` is refused with the list of provider keys.
49
+
50
+ [Release notes](https://github.com/danielgwilson/humanish/releases/tag/v0.115.0)
51
+
11
52
  ## 0.114.0: Reviewer notes, expected analysis cost, percent-encoded UTF-8 in verify (2026-10-08)
12
53
 
13
54
  humanish 0.114.0 lets a reviewer note a moment of a recorded run, admits post-run analysis on its
package/CONTEXT.md CHANGED
@@ -41,7 +41,7 @@ bundle and output fields keep older spellings; the second table maps them.
41
41
  | analysis | An independent model reading of a finished run's retained evidence, kept apart from participant feedback. Supported live runs request it automatically, and `humanish analyze` runs it on demand. It records findings and observations as `humanish.study-analysis.v1`. | `src/analysis/`; `runAutomaticAnalysis` in `src/analysis/automatic.ts`; `analyzeRun` in `src/analysis/service.ts`; `ANALYSIS_SCHEMA` in `src/analysis/types.ts` |
42
42
  | finding | A result of analysis, with citations to evidence in the run. Each finding groups the observations that support it. | `src/analysis/` |
43
43
  | observation | One claim in analysis with its basis, evidence ids and limitation. `concernReviews[]` gives each material concern's observation a disposition: `finding`, `context` or `unsupported`. | `src/analysis/` |
44
- | reviewer note | Free text a person adds at a moment of a recorded run, for one participant or the whole run, kept in the run directory as its own file, `notes/<id>.json` (`humanish.run-note.v1`). It is not participant feedback or analysis. `humanish notes` and an Observer served on loopback add notes. | `addRunNote` in `src/run/notes.ts`; `readRunNotes` in `src/run/note-files.ts`; `createNotesWriter` in `src/observer/notes-route.ts` |
44
+ | reviewer note | Free text a person adds at a moment of a recorded run, for one participant or the whole run, kept in the run directory as its own file, `notes/<id>.json` (`humanish.run-note.v1`). It is not participant feedback or analysis. `humanish notes` and an Observer served on loopback add notes. | `RunNote` and its limits in `src/run/note-shape.ts`; `addRunNote` in `src/run/notes.ts`; `readRunNotes` in `src/run/note-files.ts`; `createNotesWriter` in `src/observer/notes-route.ts` |
45
45
 
46
46
  ## Read these field spellings as the concept they name
47
47
 
package/README.md CHANGED
@@ -276,9 +276,10 @@ sensitive to capture in the first place.
276
276
  **What the automated gate enforces.** `humanish verify` scans public-bound
277
277
  artifacts and fails closed on secret, key, and token shapes and on known local
278
278
  path shapes. It reads text only, judged by a file's bytes rather than its name:
279
- any file in the run folder that is not UTF-8 text, other than a stream
280
- screenshot or recording, keeps the run `local_only` (`UNSCANNED_ARTIFACT`). It
281
- matches after undoing escapes, percent-encoding, HTML entities and base64. It
279
+ any file in the run folder that is not UTF-8 text or is larger than 32 MiB,
280
+ other than a stream screenshot or recording, keeps the run `local_only`
281
+ (`UNSCANNED_ARTIFACT`). It matches after undoing escapes, percent-encoding, HTML
282
+ entities and base64. It
282
283
  does not yet detect free-form PII or PHI such as names, emails,
283
284
  phone numbers, dates of birth, or medical identifiers. Keeping those out depends
284
285
  on using synthetic data and on review, so `redaction: passed` means the
@@ -483,9 +484,9 @@ At most once a day, a command run at a terminal also asks the npm registry for
483
484
  the latest humanish version, and a later command prints one line on stderr when
484
485
  yours is older. That request is a plain `GET` of humanish's dist-tags with no
485
486
  machine id, version, command or path. It is skipped in CI, without a terminal,
486
- for `--json`, and with `DO_NOT_TRACK=1`, `HUMANISH_TELEMETRY_DISABLED=1` or
487
- `HUMANISH_NO_UPDATE_CHECK=1`. [TELEMETRY.md](TELEMETRY.md#the-update-check)
488
- describes it.
487
+ for `--json`, and with `DO_NOT_TRACK=1`, `HUMANISH_TELEMETRY_DISABLED=1`,
488
+ `HUMANISH_NO_UPDATE_CHECK=1` or update-notifier's `NO_UPDATE_NOTIFIER=1`.
489
+ [TELEMETRY.md](TELEMETRY.md#the-update-check) describes it.
489
490
 
490
491
  ## Contribute
491
492
 
package/TELEMETRY.md CHANGED
@@ -132,4 +132,10 @@ participant, and when any of these is set:
132
132
  DO_NOT_TRACK=1
133
133
  HUMANISH_TELEMETRY_DISABLED=1
134
134
  HUMANISH_NO_UPDATE_CHECK=1
135
+ NO_UPDATE_NOTIFIER=1
135
136
  ```
137
+
138
+ `NO_UPDATE_NOTIFIER` is the variable the update-notifier package reads, so a
139
+ machine set up to silence the CLIs built on it gets no humanish notice either.
140
+ humanish reads it as update-notifier does: any value turns the check off,
141
+ including an empty one, `0` or `false`.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * How much a participant's closing report may hold. Participants are asked for no more, and a
3
+ * reply that holds more is rejected. Characters are counted as `String.length` counts them.
4
+ */
5
+ export declare const CLOSING_REPORT_LIMITS: Readonly<{
6
+ summaryChars: 4000;
7
+ frictionReports: 8;
8
+ frictionReportChars: 2000;
9
+ impressions: 6;
10
+ impressionChars: 500;
11
+ }>;
@@ -0,0 +1,13 @@
1
+ // The closing report's limits, kept apart from its zod schema in closing-report.ts so the
2
+ // Observer can read them: it bundles this module, and zod stays out of its page.
3
+ /**
4
+ * How much a participant's closing report may hold. Participants are asked for no more, and a
5
+ * reply that holds more is rejected. Characters are counted as `String.length` counts them.
6
+ */
7
+ export const CLOSING_REPORT_LIMITS = Object.freeze({
8
+ summaryChars: 4000,
9
+ frictionReports: 8,
10
+ frictionReportChars: 2000,
11
+ impressions: 6,
12
+ impressionChars: 500,
13
+ });
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ declare const impressionSchema: z.ZodObject<{
3
+ kind: z.ZodEnum<{
4
+ liked: "liked";
5
+ missing: "missing";
6
+ unclear: "unclear";
7
+ unfinished: "unfinished";
8
+ unlike_my_work: "unlike_my_work";
9
+ untrustworthy: "untrustworthy";
10
+ }>;
11
+ text: z.ZodString;
12
+ }, z.core.$strict>;
13
+ /** The impressions a participant gave; an empty list means it had none. */
14
+ export declare const participantImpressionsSchema: z.ZodArray<z.ZodObject<{
15
+ kind: z.ZodEnum<{
16
+ liked: "liked";
17
+ missing: "missing";
18
+ unclear: "unclear";
19
+ unfinished: "unfinished";
20
+ unlike_my_work: "unlike_my_work";
21
+ untrustworthy: "untrustworthy";
22
+ }>;
23
+ text: z.ZodString;
24
+ }, z.core.$strict>>;
25
+ /**
26
+ * A closing report. A report without impressions is still valid: third-party provider ports and
27
+ * stored traces do not have them.
28
+ */
29
+ export declare const closingReportSchema: z.ZodObject<{
30
+ summary: z.ZodString;
31
+ frictionReports: z.ZodArray<z.ZodString>;
32
+ impressions: z.ZodOptional<z.ZodArray<z.ZodObject<{
33
+ kind: z.ZodEnum<{
34
+ liked: "liked";
35
+ missing: "missing";
36
+ unclear: "unclear";
37
+ unfinished: "unfinished";
38
+ unlike_my_work: "unlike_my_work";
39
+ untrustworthy: "untrustworthy";
40
+ }>;
41
+ text: z.ZodString;
42
+ }, z.core.$strict>>>;
43
+ }, z.core.$strict>;
44
+ /** The reply to an impressions-only request: the impressions and nothing else. */
45
+ export declare const impressionsReplySchema: z.ZodObject<{
46
+ impressions: z.ZodArray<z.ZodObject<{
47
+ kind: z.ZodEnum<{
48
+ liked: "liked";
49
+ missing: "missing";
50
+ unclear: "unclear";
51
+ unfinished: "unfinished";
52
+ unlike_my_work: "unlike_my_work";
53
+ untrustworthy: "untrustworthy";
54
+ }>;
55
+ text: z.ZodString;
56
+ }, z.core.$strict>>;
57
+ }, z.core.$strict>;
58
+ /** The participant's account, not an independently confirmed product diagnosis. */
59
+ export type ParticipantClosingReport = z.infer<typeof closingReportSchema>;
60
+ /** One first-person impression about something the participant saw in the session. */
61
+ export type ParticipantImpression = z.infer<typeof impressionSchema>;
62
+ /**
63
+ * The JSON Schema a strict structured-output request sends for `schema`. Strict mode needs every
64
+ * field, so optional ones become required. `$schema` is left out, so the request carries only
65
+ * keywords both providers' strict modes are known to accept.
66
+ */
67
+ export declare function strictOutputSchema(schema: z.ZodObject): Record<string, unknown>;
68
+ export {};
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+ import { CLOSING_REPORT_LIMITS as LIMITS } from "./closing-report-limits.js";
3
+ // What a participant says at the end of a session: the closing report and its impressions. This
4
+ // schema is the one description of them. The providers send the JSON Schema made from it, and
5
+ // every reply is parsed with it, so a participant is asked for exactly what is accepted.
6
+ /**
7
+ * What a participant can say about the product at the end of a session, beyond friction.
8
+ * `unlike_my_work` compares the screen with how the persona does the same task in its own work or
9
+ * life.
10
+ */
11
+ const PARTICIPANT_IMPRESSION_KINDS = [
12
+ "unclear",
13
+ "unfinished",
14
+ "untrustworthy",
15
+ "liked",
16
+ "missing",
17
+ "unlike_my_work",
18
+ ];
19
+ /**
20
+ * Text the participant wrote. The JSON Schema carries the length limits; the check that it is not
21
+ * only whitespace runs on the reply.
22
+ */
23
+ const said = (maxChars) => z
24
+ .string()
25
+ .min(1)
26
+ .max(maxChars)
27
+ .refine((text) => text.trim() !== "");
28
+ const impressionSchema = z.strictObject({
29
+ kind: z.enum(PARTICIPANT_IMPRESSION_KINDS),
30
+ text: said(LIMITS.impressionChars),
31
+ });
32
+ /** The impressions a participant gave; an empty list means it had none. */
33
+ export const participantImpressionsSchema = z.array(impressionSchema).max(LIMITS.impressions);
34
+ /**
35
+ * A closing report. A report without impressions is still valid: third-party provider ports and
36
+ * stored traces do not have them.
37
+ */
38
+ export const closingReportSchema = z.strictObject({
39
+ summary: said(LIMITS.summaryChars),
40
+ frictionReports: z.array(said(LIMITS.frictionReportChars)).max(LIMITS.frictionReports),
41
+ impressions: participantImpressionsSchema.optional(),
42
+ });
43
+ /** The reply to an impressions-only request: the impressions and nothing else. */
44
+ export const impressionsReplySchema = z.strictObject({
45
+ impressions: participantImpressionsSchema,
46
+ });
47
+ /**
48
+ * The JSON Schema a strict structured-output request sends for `schema`. Strict mode needs every
49
+ * field, so optional ones become required. `$schema` is left out, so the request carries only
50
+ * keywords both providers' strict modes are known to accept.
51
+ */
52
+ export function strictOutputSchema(schema) {
53
+ const { $schema: _dialect, ...json } = z.toJSONSchema(schema.required(), { io: "input" });
54
+ return json;
55
+ }
@@ -2,7 +2,7 @@ import { createServer } from "node:http";
2
2
  import path from "node:path";
3
3
  import { digestText } from "../../evidence/redaction.js";
4
4
  import { CODEX_APP_SERVER_ARTIFACTS, CODEX_APP_SERVER_DIRECTORY, runCodexAppServerSessionInPreparedRoot, } from "./app-server.js";
5
- import { prepareContainedOutputDirectory, prepareContainedOutputFile, prepareManagedHumanishOutputDirectory, prepareSelectedOutputDirectory, prepareSelectedOutputFile, readContainedRegularFile, writeContainedOutputFile, writePreparedSelectedOutputFile, } from "../../run/contained-output.js";
5
+ import { prepareContainedOutputDirectory, prepareContainedOutputFile, prepareManagedHumanishOutputDirectory, prepareSelectedOutputDirectory, prepareSelectedOutputFile, readContainedRegularFile, refusalText, RUN_ARTIFACT_MAX_BYTES, writeContainedOutputFile, writePreparedSelectedOutputFile, } from "../../run/contained-output.js";
6
6
  const CODEX_APP_SERVER_UI_SCHEMA = "humanish.codex-app-server-ui.v1";
7
7
  export async function startCodexAppServerUi(options) {
8
8
  const cwd = path.resolve(options.cwd);
@@ -128,13 +128,20 @@ export async function startCodexAppServerUi(options) {
128
128
  };
129
129
  }
130
130
  async function serveArtifact(args) {
131
- const body = await readContainedRegularFile(args.runRoot, args.requestPath);
132
- if (body) {
131
+ const read = await readContainedRegularFile(args.runRoot, args.requestPath, RUN_ARTIFACT_MAX_BYTES);
132
+ if (read.status === "read") {
133
133
  args.response.writeHead(200, {
134
134
  "cache-control": "no-store",
135
135
  "content-type": contentTypeFor(args.requestPath),
136
136
  });
137
- args.response.end(body);
137
+ args.response.end(read.bytes);
138
+ return;
139
+ }
140
+ // A file over the limit is there: a 404 would tell the page it does not exist. A link, or a
141
+ // path outside the run, is served as absent, as it always was.
142
+ if (read.status === "refused" && read.reason === "too-large") {
143
+ args.response.writeHead(403, { "content-type": "text/plain; charset=utf-8" });
144
+ args.response.end(`${refusalText(args.requestPath, read)}.`);
138
145
  return;
139
146
  }
140
147
  args.response.writeHead(404);
@@ -1,5 +1,6 @@
1
- import { type CuaTurn } from "../computer-use/loop.js";
2
- import { type ActorExecutionProfile, type ParticipantClosingReport } from "../contract.js";
1
+ import { type ParticipantClosingReport } from "../closing-report.js";
2
+ import type { CuaTurn } from "../computer-use/loop.js";
3
+ import type { ActorExecutionProfile } from "../contract.js";
3
4
  /** The declared profile. A participant replaces cliVersion with its detected CLI release, or with
4
5
  * this host's newest qualified release before its first launch. */
5
6
  export declare const PARTICIPANT_PROFILE: Readonly<ActorExecutionProfile>;
@@ -16,51 +17,7 @@ export declare const PARTICIPANT_LIMITS: Readonly<{
16
17
  }>;
17
18
  export declare function participantToolSchema(speechEnabled?: boolean): Record<string, unknown>;
18
19
  export declare const PARTICIPANT_TOOL_SCHEMA: Record<string, unknown>;
19
- export declare const PARTICIPANT_FINAL_SCHEMA: {
20
- type: string;
21
- additionalProperties: boolean;
22
- required: string[];
23
- properties: {
24
- outcome: {
25
- type: string;
26
- enum: string[];
27
- };
28
- summary: {
29
- type: string;
30
- minLength: number;
31
- maxLength: number;
32
- };
33
- frictionReports: {
34
- type: string;
35
- maxItems: number;
36
- items: {
37
- type: string;
38
- minLength: number;
39
- maxLength: number;
40
- };
41
- };
42
- impressions: {
43
- type: string;
44
- maxItems: number;
45
- items: {
46
- type: string;
47
- additionalProperties: boolean;
48
- required: string[];
49
- properties: {
50
- kind: {
51
- type: string;
52
- enum: ("liked" | "missing" | "unclear" | "unfinished" | "unlike_my_work" | "untrustworthy")[];
53
- };
54
- text: {
55
- type: string;
56
- minLength: number;
57
- maxLength: number;
58
- };
59
- };
60
- };
61
- };
62
- };
63
- };
20
+ export declare const PARTICIPANT_FINAL_SCHEMA: Record<string, unknown>;
64
21
  /** humanish tool arguments, validated before the shared executor sees a batch. */
65
22
  export declare function parseParticipantTool(value: unknown, speechEnabled?: boolean): CuaTurn;
66
23
  export declare function parseParticipantFinal(value: unknown): CuaTurn & {
@@ -1,7 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { BROWSER_CONTROL_LIMITS, browserControlActionSchema, browserOnlyControlActionSchema, validateBrowserControlAction, } from "../../browser-control/protocol.js";
3
- import { validClosingReport } from "../computer-use/loop.js";
4
- import { PARTICIPANT_IMPRESSION_KINDS, } from "../contract.js";
3
+ import { closingReportSchema, strictOutputSchema, } from "../closing-report.js";
5
4
  /** The declared profile. A participant replaces cliVersion with its detected CLI release, or with
6
5
  * this host's newest qualified release before its first launch. */
7
6
  export const PARTICIPANT_PROFILE = Object.freeze({
@@ -38,33 +37,12 @@ export function participantToolSchema(speechEnabled = false) {
38
37
  return z.toJSONSchema(toolInput(speechEnabled), { io: "input" });
39
38
  }
40
39
  export const PARTICIPANT_TOOL_SCHEMA = participantToolSchema();
41
- export const PARTICIPANT_FINAL_SCHEMA = {
42
- type: "object",
43
- additionalProperties: false,
44
- required: ["outcome", "summary", "frictionReports", "impressions"],
45
- properties: {
46
- outcome: { type: "string", enum: ["reached", "not_reached", "blocked"] },
47
- summary: { type: "string", minLength: 1, maxLength: 4000 },
48
- frictionReports: {
49
- type: "array",
50
- maxItems: 8,
51
- items: { type: "string", minLength: 1, maxLength: 2000 },
52
- },
53
- impressions: {
54
- type: "array",
55
- maxItems: 6,
56
- items: {
57
- type: "object",
58
- additionalProperties: false,
59
- required: ["kind", "text"],
60
- properties: {
61
- kind: { type: "string", enum: [...PARTICIPANT_IMPRESSION_KINDS] },
62
- text: { type: "string", minLength: 1, maxLength: 500 },
63
- },
64
- },
65
- },
66
- },
67
- };
40
+ /** The participant's final account: how the task went, then its closing report. */
41
+ const participantFinal = z.strictObject({
42
+ outcome: z.enum(["reached", "not_reached", "blocked"]),
43
+ ...closingReportSchema.shape,
44
+ });
45
+ export const PARTICIPANT_FINAL_SCHEMA = strictOutputSchema(participantFinal);
68
46
  /**
69
47
  * Shorten each wait longer than one browser-control request may wait. A failed tool call ends the
70
48
  * native run, so a wait that asks too much would otherwise end the whole session.
@@ -107,22 +85,16 @@ export function parseParticipantTool(value, speechEnabled = false) {
107
85
  };
108
86
  }
109
87
  export function parseParticipantFinal(value) {
110
- if (!value || typeof value !== "object" || Array.isArray(value))
111
- throw new Error("invalid_response");
112
- const { outcome, ...report } = value;
113
- if ((outcome !== "reached" && outcome !== "not_reached" && outcome !== "blocked") ||
114
- !validClosingReport(report))
88
+ const final = participantFinal.safeParse(value);
89
+ if (!final.success)
115
90
  throw new Error("invalid_response");
91
+ const { outcome, ...closingReport } = final.data;
116
92
  return {
117
93
  actions: [],
118
94
  pendingSafetyChecks: [],
119
95
  done: true,
120
96
  outcome,
121
- message: [report.summary, ...report.frictionReports].join("\n"),
122
- closingReport: {
123
- summary: report.summary,
124
- frictionReports: [...report.frictionReports],
125
- ...(report.impressions === undefined ? {} : { impressions: [...report.impressions] }),
126
- },
97
+ message: [closingReport.summary, ...closingReport.frictionReports].join("\n"),
98
+ closingReport,
127
99
  };
128
100
  }
@@ -1,4 +1,4 @@
1
- import { type ActorTrace, type ParticipantClosingReport, type ParticipantImpression } from "../../contract.js";
1
+ import type { ActorTrace } from "../../contract.js";
2
2
  import type { DebriefTrigger } from "./ending.js";
3
3
  import type { LoopSession } from "./session.js";
4
4
  import type { CuaTurnRequest } from "./types.js";
@@ -9,15 +9,6 @@ export interface DebriefContext {
9
9
  readonly previousExecution: CuaTurnRequest["previousExecution"];
10
10
  readonly acknowledgedSafetyChecks: CuaTurnRequest["acknowledgedSafetyChecks"];
11
11
  }
12
- /**
13
- * Runtime validation is also required for third-party provider ports. A report without
14
- * impressions stays valid: older providers and stored traces do not have them.
15
- */
16
- export declare function validClosingReport(value: unknown): value is ParticipantClosingReport;
17
- /** The reply to an impressions-only request: an object with the impressions and nothing else. */
18
- export declare function validImpressionsReply(value: unknown): value is {
19
- impressions: ParticipantImpression[];
20
- };
21
12
  /**
22
13
  * Ask the participant for its closing report after a structured stop, or for its impressions only
23
14
  * after it ended the session itself, or record why the request was skipped. Either way the result