humanish 0.123.0 → 0.124.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.
package/dist/run/run.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import { randomBytes } from "node:crypto";
6
6
  import { redactText } from "../evidence/redaction.js";
7
7
  import { buildObserverData } from "../observer/data.js";
8
+ import { splitObserverData } from "../observer/participant-items.js";
8
9
  import { renderObserver } from "../observer/render.js";
9
10
  import { RUN_BUNDLE_FILE, PUBLIC_TARGET_CWD, buildRunSource, } from "./bundle.js";
10
11
  import { judgeExecution, } from "./judge.js";
@@ -187,7 +188,7 @@ function runPublisher(args) {
187
188
  await writeContainedOutputFile(paths, "review.md", options.renderReview(publicBundle, status), "utf8");
188
189
  const events = publicBundle.events.map((event) => JSON.stringify(event)).join("\n");
189
190
  await writeContainedOutputFile(paths, "events.ndjson", `${events}\n`, "utf8");
190
- await writeContainedOutputFile(paths, "observer/observer-data.json", json(buildObserverData(publicBundle, undefined, status)), "utf8");
191
+ await writeContainedOutputFile(paths, "observer/observer-data.json", json(splitObserverData(buildObserverData(publicBundle, undefined, status)).shell), "utf8");
191
192
  };
192
193
  const snapshot = async (bundle) => {
193
194
  await writeBundleFiles(bundle, undefined, async () => { });
@@ -15,6 +15,7 @@ import { routeOf } from "./routing.js";
15
15
  import { automaticAnalysisRouteReason, taskProtocolValidationReason } from "./validation.js";
16
16
  import { declaredParticipantCount } from "./study-fields.js";
17
17
  import { recordedStudyWarnings } from "./warnings.js";
18
+ import { plural } from "../run/text.js";
18
19
  export { routeOf } from "./routing.js";
19
20
  /** Resolve dry-run: explicit override wins, else the scenario mode, else the given fallback. */
20
21
  export function resolveStudyDryRun(config, override, fallback) {
@@ -85,10 +86,12 @@ export function planStudy(config, options, deps = {}) {
85
86
  const bundle = plannedBundle(result.plan);
86
87
  // The manifest the CLI resolved enters the plan here and nowhere else; the routes read
87
88
  // plan.study for the run's status record and bundle, and plan.warnings for its bundle events.
89
+ const closing = closingReportWarning(result.plan);
88
90
  const warnings = [
89
91
  ...recordedStudyWarnings(study),
90
92
  ...(result.plan.warnings ?? []),
91
93
  ...(bundle.warning === undefined ? [] : [bundle.warning]),
94
+ ...(closing === undefined ? [] : [closing]),
92
95
  ];
93
96
  const plan = {
94
97
  ...result.plan,
@@ -111,6 +114,23 @@ function plannedBundle(plan) {
111
114
  const warning = plan.dryRun ? undefined : bundleLimitReason(estimate);
112
115
  return warning === undefined ? { estimate } : { estimate, warning };
113
116
  }
117
+ /**
118
+ * The warning for participants with a scheduled end on Claude as a local-agent participant model.
119
+ * Its session has no read-only closing request, so each such participant ends with the scheduled
120
+ * end as its reason and no account of its session.
121
+ */
122
+ function closingReportWarning(plan) {
123
+ if (plan.route !== "computer-use")
124
+ return undefined;
125
+ const { brain } = plan.runner;
126
+ if (brain.kind !== "local-agent" || brain.agent !== "claude")
127
+ return undefined;
128
+ const scheduled = plan.runner.participants.filter((participant) => participant.endAfterMs !== undefined).length;
129
+ if (scheduled === 0)
130
+ return undefined;
131
+ const subject = scheduled === 1 ? "it ends" : "each ends";
132
+ return `${plural(scheduled, "participant")} ${scheduled === 1 ? "has" : "have"} a scheduled end (participants[].endAfterMs), and Claude as a local-agent participant model cannot be asked for a closing report, so ${subject} with the scheduled end as its reason and no closing account. A computer-use model (actor.type: openai-computer-use) or Codex (actor.localAgent: codex) writes one.`;
133
+ }
114
134
  function planRoute(route, study, options, input, deps, e2b) {
115
135
  const { sandboxCeiling } = e2b;
116
136
  switch (route) {
@@ -24,9 +24,26 @@ Observer commands may add derived artifacts:
24
24
  observer-data.json
25
25
  ```
26
26
 
27
- `run.json` remains the source bundle. `observer/observer-data.json` is the
28
- normalized view model consumed by the Observer. `events.ndjson` is the appendable
29
- event stream contract that live adapters update while a run is active.
27
+ `run.json` remains the source bundle. `observer/observer-data.json`
28
+ (`humanish.observer-data.v2`) is the normalized view model consumed by the Observer,
29
+ with every participant except their trace items and the screenshot entries of their
30
+ artifact list. A participant working for hours records thousands of items and a
31
+ screenshot each turn, so these are most of a long run's bytes; apart, the saved file
32
+ stays small enough for `verify` to read. The items stay in the run's traces
33
+ (`actors/<stream>.json`). `events.ndjson` is the appendable event stream
34
+ contract that live adapters update while a run is active.
35
+
36
+ `observer/index.html` carries both parts: the data slot holds observer-data.json's
37
+ content, and `<script id="observer-items" type="application/octet-stream"
38
+ data-encoding="gzip">` holds the items and full artifact lists (`humanish.observer-items.v1`, by stream id)
39
+ gzip-compressed in base64, which a `file://` page decodes without a fetch. Real
40
+ trace items compress about eight times. The page joins the two before it renders
41
+ (`joinObserverData` in `src/observer/participant-items.ts`), so every view reads the
42
+ same data as before. A stream whose screenshot entries moved records how many in
43
+ `screenshotArtifacts`, and the page renders nothing from items that are another
44
+ run's or miss a participant's items or screenshot entries. The server answers a
45
+ saved v2 observer-data.json that leaves items out as unavailable, since it has no
46
+ items to join.
30
47
 
31
48
  ## Stream Model
32
49
 
@@ -55,7 +72,9 @@ public-safe metadata.
55
72
  4. opens the served Observer URL;
56
73
  5. keeps the shell attached until Ctrl-C.
57
74
 
58
- The browser polls `observer-data.json` with `no-store` caching. Static
75
+ The browser polls `observer-feed.json` with `no-store` caching: the server's
76
+ projection of the run as `{ data, items }`, observer-data.json's content and the
77
+ items, from one read of the run per poll. Static
59
78
  `file://` opening still works for immutable review, but follow mode is the
60
79
  operator path. Agents and CI should use `humanish watch --json --no-open` for
61
80
  the same fresh evidence without browser open or a long-running process.
@@ -253,7 +272,7 @@ These remain adapter work:
253
272
  ## The rendering layer
254
273
 
255
274
  The renderer is the `observer/` workspace: a Vite single-file build on the
256
- `@humanish` registry tokens, frozen against `humanish.observer-data.v1`
275
+ `@humanish` registry tokens, frozen against `humanish.observer-data.v2`
257
276
  (`tests/observer/data-contract.test.ts`). The root build copies the workspace
258
277
  artifact to `dist/observer-app.html`; in a repo checkout a missing or stale
259
278
  artifact auto-builds, and an unconditional preflight at CLI startup makes a
@@ -77,7 +77,9 @@ used as a bundle answers 404 with the reason, never the saved projection. In saf
77
77
  (`observe --all --safe`) every read is checked against the files admitted with the run's
78
78
  share_ready verification, so a run.json changed after admission is refused and the admitted
79
79
  projection is served.
80
- observer-data.json is built from the loaded bundle and has the same shape as before.
80
+ observer-data.json is built from the loaded bundle. From 0.124.0 it is
81
+ `humanish.observer-data.v2`: the same data without each participant's trace items, which stay in
82
+ the traces (docs/architecture/observer.md).
81
83
 
82
84
  Compatibility: humanish 0.122.0 and earlier read only v1 and refuse a v2 run.json by its schema
83
85
  (`run schema` fails in verify, analysis refuses the source). This release reads v1 bundles as
@@ -367,7 +367,7 @@ and an impression alone supports only a finding about what the participant said.
367
367
 
368
368
  ## Durable records
369
369
 
370
- The frozen `humanish.observer-data.v1` schema is unchanged. A companion
370
+ The `humanish.observer-data.v2` schema is unchanged by analysis. A companion
371
371
  `humanish.study-analysis.v1` artifact records source/config/input hashes,
372
372
  participant context, evidence manifest, coverage, provider/model/prompt version,
373
373
  status, usage and validated findings. The Observer's projection of it,
@@ -13,7 +13,8 @@ exactly those cases.
13
13
  The `observer/` workspace builds with Vite into a single HTML file with fonts inlined and no
14
14
  network references. The root build copies it to `dist/observer-app.html`. `renderObserverHtml`
15
15
  is the one path every surface (observe, observe --all, watch, studies) uses; it injects the run's
16
- `humanish.observer-data.v1` snapshot into that file. There is no second renderer and no feature
16
+ `humanish.observer-data.v2` snapshot into that file, with the participants' trace items beside it
17
+ gzip-compressed (docs/architecture/observer.md). There is no second renderer and no feature
17
18
  flag; rolling back means pinning an older package version.
18
19
 
19
20
  ## Consequences
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanish",
3
- "version": "0.123.0",
3
+ "version": "0.124.0",
4
4
  "description": "Synthetic user research for apps, CLIs, and agent-facing product flows.",
5
5
  "keywords": [
6
6
  "cli",
@@ -94,7 +94,7 @@
94
94
  "comms:agentmail:contract": "node --import tsx scripts/agentmail-contract-check.mjs",
95
95
  "format": "oxfmt",
96
96
  "format:check": "oxfmt --check",
97
- "lint": "node scripts/check-lint-cap.mjs --max-warnings=426",
97
+ "lint": "node scripts/check-lint-cap.mjs --max-warnings=424",
98
98
  "knip": "knip",
99
99
  "prose:check": "node scripts/check-code-prose.mjs",
100
100
  "vocabulary:check": "tsx scripts/check-retired-words.ts",