humanish 0.122.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/CONTEXT.md +2 -2
  3. package/README.md +33 -6
  4. package/dist/actors/computer-use/loop/trace.js +6 -1
  5. package/dist/actors/computer-use/openai-conversation.d.ts +0 -6
  6. package/dist/actors/computer-use/openai-conversation.js +0 -8
  7. package/dist/actors/computer-use/openai-provider.js +5 -8
  8. package/dist/analysis/automatic.js +2 -1
  9. package/dist/analysis/evidence.js +41 -21
  10. package/dist/cli/commands/study-run.js +9 -3
  11. package/dist/evidence/image.js +82 -3
  12. package/dist/feedback/derivative-text.d.ts +12 -0
  13. package/dist/feedback/derivative-text.js +130 -0
  14. package/dist/feedback/export-bundle.js +39 -136
  15. package/dist/feedback/export.js +36 -9
  16. package/dist/observer/artifact.js +2 -9
  17. package/dist/observer/data.d.ts +9 -2
  18. package/dist/observer/data.js +17 -2
  19. package/dist/observer/page-data.d.ts +5 -0
  20. package/dist/observer/page-data.js +21 -0
  21. package/dist/observer/participant-items.d.ts +45 -0
  22. package/dist/observer/participant-items.js +85 -0
  23. package/dist/observer/render.js +2 -1
  24. package/dist/observer/run-routes.js +75 -22
  25. package/dist/observer-app.html +4 -4
  26. package/dist/run/bundle-shape.js +2 -2
  27. package/dist/run/bundle.d.ts +5 -2
  28. package/dist/run/bundle.js +4 -1
  29. package/dist/run/detail.d.ts +6 -0
  30. package/dist/run/detail.js +14 -1
  31. package/dist/run/locate.js +3 -1
  32. package/dist/run/projection.d.ts +7 -0
  33. package/dist/run/projection.js +10 -0
  34. package/dist/run/run.js +9 -2
  35. package/dist/run/stored-runs.js +10 -4
  36. package/dist/run/stream-shape.d.ts +11 -1
  37. package/dist/run/stream-shape.js +26 -2
  38. package/dist/run/stream-trace.d.ts +68 -0
  39. package/dist/run/stream-trace.js +260 -0
  40. package/dist/run/streams.d.ts +20 -0
  41. package/dist/study/bundle-estimate.d.ts +48 -0
  42. package/dist/study/bundle-estimate.js +159 -0
  43. package/dist/study/doctor.js +4 -0
  44. package/dist/study/plan-types.d.ts +3 -0
  45. package/dist/study/plan.js +42 -2
  46. package/dist/study/preflight.js +8 -0
  47. package/dist/tui-app.js +87 -87
  48. package/dist/verify/artifacts.d.ts +17 -10
  49. package/dist/verify/artifacts.js +22 -13
  50. package/dist/verify/scan-limit.d.ts +6 -0
  51. package/dist/verify/scan-limit.js +25 -0
  52. package/dist/verify/verify.d.ts +1 -1
  53. package/dist/verify/verify.js +68 -18
  54. package/docs/architecture/github-feedback-loop.md +1 -1
  55. package/docs/architecture/observer.md +24 -5
  56. package/docs/contracts/README.md +1 -1
  57. package/docs/contracts/run-bundle.md +89 -6
  58. package/docs/contracts/schemas.md +14 -7
  59. package/docs/contracts/study-analysis.md +1 -1
  60. package/docs/decisions/0002-observer-is-one-self-contained-file.md +2 -1
  61. package/package.json +7 -4
@@ -1,6 +1,7 @@
1
1
  // HTTP handlers for one run's Observer: the page, its data with live status and runtime stream
2
2
  // URLs, its analysis, and its media with byte ranges. serveObserver (render.ts) and the library
3
3
  // server (serve.ts) both route requests through them.
4
+ import { lstat } from "node:fs/promises";
4
5
  import path from "node:path";
5
6
  import { isAnalysisRecordPath, projectShareCheckedAnalysis } from "../analysis/sharing.js";
6
7
  import { loadAnalysis } from "../analysis/load.js";
@@ -9,11 +10,14 @@ import { listRuns } from "../run/stored-runs.js";
9
10
  import { readAnalysisAccounting } from "../run/costs.js";
10
11
  import { bindExistingRunArtifactPaths, isPathInside, isSafeRunIdSegment, } from "../run/paths.js";
11
12
  import { isRunOutcome } from "../run/bundle-shape.js";
13
+ import { displayedTraces, loadStreamTraces } from "../run/stream-trace.js";
14
+ import { isNodeError, isRecord } from "../run/type-guards.js";
12
15
  import { MAX_NOTE_FILE_BYTES, readRunNotes, RUN_NOTES_DIR, } from "../run/note-files.js";
13
16
  import { analysisCostOf, runCost, runCostLabel } from "../run/run-cost.js";
14
17
  import { RUN_STATUS_FILE, RUN_STATUS_STALE_MS, isRunStatusRecord, } from "../run/status.js";
15
18
  import { renderObserverHtml } from "./artifact.js";
16
- import { buildObserverData, recordedStreamEmbed, withObserverEndings, } from "./data.js";
19
+ import { splitObserverData } from "./participant-items.js";
20
+ import { buildObserverData, observerDataFromSaved, recordedStreamEmbed, withObserverEndings, } from "./data.js";
17
21
  import { buildArtifactSecurityHeaders } from "./http.js";
18
22
  import { assertPinnedDirectory, openContainedFile, pinDirectChildDirectory, readContainedFile, relativeToRoot, sha256OfOpenedFile, } from "./pinned-files.js";
19
23
  /** The run's artifact paths, only when they are still the pinned directory being served. */
@@ -99,9 +103,9 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
99
103
  return;
100
104
  }
101
105
  if (cleanedRelativePath === "observer/index.html") {
102
- const observerData = await readObserverData(runRoot, runtimeStreamUrls);
103
- if (!observerData) {
104
- writeResponse(response, 404, "Observer data not found", "text/plain; charset=utf-8");
106
+ const observerData = await observerDataOrReason(runRoot, runtimeStreamUrls);
107
+ if ("unavailable" in observerData) {
108
+ writeResponse(response, 404, `Observer data not found: ${observerData.unavailable}`, "text/plain; charset=utf-8");
105
109
  return;
106
110
  }
107
111
  const analysis = await readObserverAnalysis(runRoot);
@@ -113,13 +117,19 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
113
117
  }), "text/html; charset=utf-8");
114
118
  return;
115
119
  }
116
- if (cleanedRelativePath === "observer/observer-data.json") {
117
- const observerData = await readObserverData(runRoot, runtimeStreamUrls);
118
- if (!observerData) {
119
- writeResponse(response, 404, "Observer data not found", "text/plain; charset=utf-8");
120
+ // observer-data.json is the run without its participants' items, as the run saves it. The page
121
+ // polls observer-feed.json, the same data with the items, so one projection serves each poll.
122
+ if (cleanedRelativePath === "observer/observer-data.json" ||
123
+ cleanedRelativePath === "observer/observer-feed.json") {
124
+ const observerData = await observerDataOrReason(runRoot, runtimeStreamUrls);
125
+ if ("unavailable" in observerData) {
126
+ writeResponse(response, 404, `Observer data not found: ${observerData.unavailable}`, "text/plain; charset=utf-8");
120
127
  return;
121
128
  }
122
- writeResponse(response, 200, JSON.stringify(observerData, null, 2), "application/json; charset=utf-8");
129
+ const { shell, items } = splitObserverData(observerData);
130
+ writeResponse(response, 200, cleanedRelativePath === "observer/observer-data.json"
131
+ ? JSON.stringify(shell, null, 2)
132
+ : JSON.stringify({ data: shell, items }), "application/json; charset=utf-8");
123
133
  return;
124
134
  }
125
135
  if (cleanedRelativePath === "observer/study-analysis.json") {
@@ -153,29 +163,72 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
153
163
  writeResponse(response, 404, "Not found", "text/plain; charset=utf-8");
154
164
  }
155
165
  }
166
+ /** The run's trace files, read through its pinned folder. */
167
+ function pinnedTraceReader(runRoot) {
168
+ return async (relativePath, maxBytes) => {
169
+ const bytes = await readContainedFile(runRoot, path.join(runRoot.physicalPath, relativePath), {
170
+ maxBytes,
171
+ }).catch(() => null);
172
+ return bytes === null ? { refused: `${relativePath} could not be read` } : { bytes };
173
+ };
174
+ }
156
175
  async function readObserverData(runRoot, runtimeStreamUrls = []) {
176
+ const read = await observerDataOrReason(runRoot, runtimeStreamUrls);
177
+ return "unavailable" in read ? null : read;
178
+ }
179
+ /**
180
+ * The run's Observer data, or why there is none. run.json is the source: each trace is loaded
181
+ * through the loader, and a participant whose trace it refuses shows as unavailable, with no
182
+ * fallback. The saved observer-data.json is read only while the run has no run.json at all, or in
183
+ * safe serving, where only its admitted bytes can be read. Otherwise a run.json that is there and
184
+ * cannot be read, parsed or used as a bundle (which a browser poll can meet mid-write) gives the
185
+ * reason, a 404 the poller retries, never the saved projection.
186
+ */
187
+ async function observerDataOrReason(runRoot, runtimeStreamUrls) {
157
188
  const record = await readLocalRunStatus(runRoot);
158
- // Best-effort load from either source. Both reads swallow all errors on
159
- // purpose: this runs on every browser poll of a live run, where run.json may
160
- // be absent, still being written (a partial-JSON parse error), or superseded
161
- // by observer-data.json. A transient failure just falls through to the next
162
- // source, or to null -> a 404 the poller retries; it must not surface a 500.
189
+ const runJson = path.join(runRoot.physicalPath, "run.json");
190
+ const bundleBytes = await readContainedFile(runRoot, runJson).catch(() => null);
191
+ const absent = bundleBytes === null &&
192
+ (await lstat(runJson).then(() => false, (error) => isNodeError(error) && error.code === "ENOENT"));
193
+ // A root admitted for safe serving checks every read against the identity and digest admitted
194
+ // with the run's share_ready verification, so a run.json changed since then is refused, and the
195
+ // saved projection it may serve is the one admitted with the verified run.
196
+ if (absent || (bundleBytes === null && runRoot.admitsContent !== undefined))
197
+ return savedObserverData(runRoot, record, runtimeStreamUrls);
198
+ if (bundleBytes === null)
199
+ return { unavailable: "run.json is there but could not be read: it is a link or changed." };
200
+ let stored;
201
+ try {
202
+ const parsed = JSON.parse(bundleBytes.toString("utf8"));
203
+ if (!isRecord(parsed) || !Array.isArray(parsed.streams))
204
+ throw new Error("not a run bundle");
205
+ stored = parsed;
206
+ }
207
+ catch {
208
+ return { unavailable: "run.json is not a run bundle; it may be mid-write, so try again." };
209
+ }
163
210
  try {
164
- const bundleBytes = await readContainedFile(runRoot, path.join(runRoot.physicalPath, "run.json"));
165
- if (!bundleBytes)
166
- throw new Error("run.json unavailable");
167
- const bundle = JSON.parse(bundleBytes.toString("utf8"));
211
+ const bundle = displayedTraces(await loadStreamTraces(pinnedTraceReader(runRoot), stored));
168
212
  return withRuntimeStreamUrls(withLocalRunStatus(record, buildObserverData(bundle, undefined, record), bundle.outcome), runtimeStreamUrls);
169
213
  }
170
- catch { }
214
+ catch {
215
+ return { unavailable: "run.json could not be shown as a run." };
216
+ }
217
+ }
218
+ /** The saved observer-data.json of a run that has written no run.json yet. */
219
+ async function savedObserverData(runRoot, record, runtimeStreamUrls) {
171
220
  try {
172
221
  const observerBytes = await readContainedFile(runRoot, path.join(runRoot.physicalPath, "observer", "observer-data.json"));
173
222
  if (!observerBytes)
174
223
  throw new Error("observer-data.json unavailable");
175
- return withRuntimeStreamUrls(withLocalRunStatus(record, withObserverEndings(JSON.parse(observerBytes.toString("utf8")))), runtimeStreamUrls);
224
+ const saved = observerDataFromSaved(JSON.parse(observerBytes.toString("utf8")));
225
+ if (saved === null)
226
+ throw new Error("observer-data.json holds no participant's items");
227
+ return withRuntimeStreamUrls(withLocalRunStatus(record, withObserverEndings(saved)), runtimeStreamUrls);
228
+ }
229
+ catch {
230
+ return { unavailable: "The run has written no run.json or Observer data yet." };
176
231
  }
177
- catch { }
178
- return null;
179
232
  }
180
233
  /** The run directory's own status record, when it is well formed and names that directory. */
181
234
  async function readLocalRunStatus(runRoot) {