humanish 0.112.0 → 0.114.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 (140) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/CONTEXT.md +1 -0
  3. package/README.md +12 -1
  4. package/TELEMETRY.md +21 -0
  5. package/dist/actors/codex/restricted-participant-policy.d.ts +21 -1
  6. package/dist/actors/codex/restricted-participant-policy.js +20 -2
  7. package/dist/actors/codex/restricted-participant.d.ts +1 -1
  8. package/dist/actors/codex/restricted-participant.js +2 -1
  9. package/dist/actors/computer-use/loop/debrief.d.ts +12 -4
  10. package/dist/actors/computer-use/loop/debrief.js +57 -20
  11. package/dist/actors/computer-use/loop/ending.d.ts +5 -2
  12. package/dist/actors/computer-use/loop/impressions.d.ts +14 -0
  13. package/dist/actors/computer-use/loop/impressions.js +40 -0
  14. package/dist/actors/computer-use/loop/session.d.ts +3 -1
  15. package/dist/actors/computer-use/loop/session.js +2 -0
  16. package/dist/actors/computer-use/loop/trace.js +2 -0
  17. package/dist/actors/computer-use/loop/types.d.ts +8 -1
  18. package/dist/actors/computer-use/loop.d.ts +2 -1
  19. package/dist/actors/computer-use/loop.js +15 -3
  20. package/dist/actors/computer-use/openai-conversation.d.ts +9 -4
  21. package/dist/actors/computer-use/openai-conversation.js +60 -29
  22. package/dist/actors/computer-use/openai-provider.js +18 -12
  23. package/dist/actors/contract.d.ts +47 -5
  24. package/dist/actors/contract.js +13 -0
  25. package/dist/analysis/admission.d.ts +37 -0
  26. package/dist/analysis/admission.js +60 -0
  27. package/dist/analysis/automatic-completion.d.ts +0 -6
  28. package/dist/analysis/automatic-completion.js +3 -3
  29. package/dist/analysis/automatic-config.d.ts +10 -2
  30. package/dist/analysis/automatic-config.js +29 -14
  31. package/dist/analysis/automatic.js +7 -0
  32. package/dist/analysis/evidence-sources.d.ts +3 -1
  33. package/dist/analysis/evidence-sources.js +10 -1
  34. package/dist/analysis/evidence.js +2 -2
  35. package/dist/analysis/execute.d.ts +20 -6
  36. package/dist/analysis/execute.js +61 -28
  37. package/dist/analysis/image-tokens.d.ts +2 -0
  38. package/dist/analysis/image-tokens.js +1 -1
  39. package/dist/analysis/job.d.ts +9 -1
  40. package/dist/analysis/job.js +11 -2
  41. package/dist/analysis/service.js +6 -3
  42. package/dist/cli/commands/analyze.js +7 -2
  43. package/dist/cli/commands/notes.d.ts +3 -0
  44. package/dist/cli/commands/notes.js +144 -0
  45. package/dist/cli/commands/observe.js +1 -1
  46. package/dist/cli/commands/runs.js +33 -10
  47. package/dist/cli/commands/setup.js +95 -6
  48. package/dist/cli/commands/study-format.js +7 -4
  49. package/dist/cli/commands/study-run.js +2 -1
  50. package/dist/cli/commands/study.js +1 -1
  51. package/dist/cli/commands/tui.js +1 -1
  52. package/dist/cli/doctor.d.ts +2 -0
  53. package/dist/cli/doctor.js +18 -0
  54. package/dist/cli/findings.js +4 -1
  55. package/dist/cli/io.d.ts +3 -0
  56. package/dist/cli/io.js +11 -12
  57. package/dist/cli/program.js +37 -1
  58. package/dist/cli/secret-prompt.d.ts +5 -1
  59. package/dist/cli/secret-prompt.js +6 -2
  60. package/dist/cli/update-check-worker.d.ts +1 -0
  61. package/dist/cli/update-check-worker.js +5 -0
  62. package/dist/cli/update-check.d.ts +46 -0
  63. package/dist/cli/update-check.js +203 -0
  64. package/dist/evidence/encoded-text.d.ts +15 -2
  65. package/dist/evidence/encoded-text.js +68 -4
  66. package/dist/evidence/secret-scrub.d.ts +1 -1
  67. package/dist/evidence/secret-scrub.js +23 -2
  68. package/dist/feedback/draft.d.ts +19 -0
  69. package/dist/feedback/draft.js +69 -1
  70. package/dist/feedback/export-bundle.js +2 -2
  71. package/dist/feedback/export.js +47 -3
  72. package/dist/feedback/feedback.js +42 -2
  73. package/dist/keys/key-resolution.js +2 -0
  74. package/dist/keys/key-status.d.ts +37 -0
  75. package/dist/keys/key-status.js +68 -0
  76. package/dist/observer/artifact.d.ts +5 -0
  77. package/dist/observer/artifact.js +18 -2
  78. package/dist/observer/notes-route.d.ts +21 -0
  79. package/dist/observer/notes-route.js +140 -0
  80. package/dist/observer/pinned-files.d.ts +8 -1
  81. package/dist/observer/pinned-files.js +32 -3
  82. package/dist/observer/render.js +38 -14
  83. package/dist/observer/run-routes.d.ts +8 -2
  84. package/dist/observer/run-routes.js +47 -6
  85. package/dist/observer/serve.d.ts +3 -0
  86. package/dist/observer/serve.js +28 -3
  87. package/dist/observer-app.html +7 -6
  88. package/dist/routes/computer-use/local-vm.js +9 -2
  89. package/dist/routes/computer-use/participant-desktop.d.ts +6 -0
  90. package/dist/routes/computer-use/participant-execution.js +9 -1
  91. package/dist/routes/computer-use/rerun-selection.js +23 -21
  92. package/dist/routes/computer-use/result.js +3 -2
  93. package/dist/routes/computer-use/self-report.js +7 -1
  94. package/dist/routes/computer-use/setup.js +14 -0
  95. package/dist/routes/computer-use/types.d.ts +4 -1
  96. package/dist/routes/shared-world/bundle-records.js +8 -7
  97. package/dist/routes/shared-world/bundle.js +1 -1
  98. package/dist/run/contained-output.d.ts +7 -0
  99. package/dist/run/contained-output.js +35 -1
  100. package/dist/run/detail.js +4 -4
  101. package/dist/run/note-files.d.ts +73 -0
  102. package/dist/run/note-files.js +184 -0
  103. package/dist/run/notes.d.ts +37 -0
  104. package/dist/run/notes.js +198 -0
  105. package/dist/run/participant-caption.d.ts +11 -1
  106. package/dist/run/participant-caption.js +30 -1
  107. package/dist/run/participant-records.d.ts +3 -1
  108. package/dist/run/participant-records.js +6 -0
  109. package/dist/run-study.js +17 -1
  110. package/dist/study/discover.js +6 -1
  111. package/dist/study/doctor.d.ts +2 -0
  112. package/dist/study/doctor.js +46 -8
  113. package/dist/study/init-templates.js +10 -5
  114. package/dist/study/plan.js +3 -2
  115. package/dist/study/preflight-result.d.ts +1 -0
  116. package/dist/study/preflight-result.js +3 -2
  117. package/dist/study/preflight.d.ts +2 -0
  118. package/dist/study/preflight.js +54 -14
  119. package/dist/study/summary.js +2 -2
  120. package/dist/study/warnings.d.ts +5 -0
  121. package/dist/study/warnings.js +79 -2
  122. package/dist/substrates/local/capacity.d.ts +75 -0
  123. package/dist/substrates/local/capacity.js +143 -0
  124. package/dist/substrates/local/firecracker-desktop.d.ts +8 -0
  125. package/dist/substrates/local/firecracker-desktop.js +34 -3
  126. package/dist/substrates/local/runtime-host.d.ts +16 -3
  127. package/dist/substrates/local/runtime-host.js +49 -4
  128. package/dist/substrates/local/runtime.d.ts +10 -0
  129. package/dist/substrates/local/runtime.js +44 -5
  130. package/dist/tui-app.js +58 -58
  131. package/docs/architecture/actor-contract.md +20 -6
  132. package/docs/contracts/errors.md +2 -2
  133. package/docs/contracts/feedback.md +8 -1
  134. package/docs/contracts/run-bundle.md +57 -0
  135. package/docs/contracts/schemas.md +62 -36
  136. package/docs/contracts/study-analysis.md +41 -8
  137. package/docs/principles/invariants-and-defaults.md +2 -2
  138. package/docs/product/automatic-analysis.md +22 -16
  139. package/package.json +2 -2
  140. package/skills/humanish/SKILL.md +26 -10
@@ -9,6 +9,7 @@ import { listRuns } from "../run/stored-runs.js";
9
9
  import { readAnalysisAccounting } from "../run/costs.js";
10
10
  import { bindExistingRunArtifactPaths, isPathInside, isSafeRunIdSegment, } from "../run/paths.js";
11
11
  import { isRunOutcome } from "../run/bundle-shape.js";
12
+ import { decodeRunNote, listNoteEntries, MAX_NOTE_FILE_BYTES, noteListing, RUN_NOTES_DIR, runNoteFile, } from "../run/note-files.js";
12
13
  import { analysisCostOf, runCost, runCostLabel } from "../run/run-cost.js";
13
14
  import { RUN_STATUS_FILE, RUN_STATUS_STALE_MS, isRunStatusRecord, } from "../run/status.js";
14
15
  import { renderObserverHtml } from "./artifact.js";
@@ -16,7 +17,7 @@ import { buildObserverData, recordedStreamEmbed, withObserverEndings, } from "./
16
17
  import { buildArtifactSecurityHeaders } from "./http.js";
17
18
  import { assertPinnedDirectory, openContainedFile, pinDirectChildDirectory, readContainedFile, relativeToRoot, sha256OfOpenedFile, } from "./pinned-files.js";
18
19
  /** The run's artifact paths, only when they are still the pinned directory being served. */
19
- async function servedRunPaths(runRoot) {
20
+ export async function servedRunPaths(runRoot) {
20
21
  const runId = path.basename(runRoot.physicalPath);
21
22
  const cwd = path.dirname(path.dirname(path.dirname(runRoot.physicalPath)));
22
23
  const prepared = await bindExistingRunArtifactPaths(cwd, runId);
@@ -57,8 +58,40 @@ async function readServedAnalysisSpend(runRoot) {
57
58
  return undefined;
58
59
  }
59
60
  }
60
- /** internal: consumed by src/observer/serve.ts */
61
- export async function serveRunPath(runRoot, relativePath, response, runtimeStreamUrls = [], request) {
61
+ /**
62
+ * The run's reviewer notes as served: notes/ listed, and each note file read within
63
+ * MAX_NOTE_FILE_BYTES through the root's checks, so a --safe root's admission and hash checks
64
+ * apply to every note. A file that is not a readable note of the run is skipped and counted.
65
+ */
66
+ async function readServedNotes(runRoot) {
67
+ const runId = path.basename(runRoot.physicalPath);
68
+ let listing;
69
+ try {
70
+ await assertPinnedDirectory(runRoot);
71
+ listing = await listNoteEntries(path.join(runRoot.physicalPath, RUN_NOTES_DIR));
72
+ }
73
+ catch {
74
+ return { runId, notes: [], skipped: [`${RUN_NOTES_DIR} is not a plain folder.`] };
75
+ }
76
+ if (listing === null)
77
+ return { runId, notes: [], skipped: [] };
78
+ const { ids, skipped } = noteListing(listing);
79
+ const notes = [];
80
+ for (const id of ids) {
81
+ const bytes = await readContainedFile(runRoot, path.join(runRoot.physicalPath, runNoteFile(id)), { maxBytes: MAX_NOTE_FILE_BYTES });
82
+ const note = bytes === null ? null : decodeRunNote(bytes, runId, id);
83
+ if (note === null)
84
+ skipped.push(`${runNoteFile(id)} was skipped.`);
85
+ else
86
+ notes.push(note);
87
+ }
88
+ return { runId, notes, skipped };
89
+ }
90
+ /**
91
+ * internal: consumed by src/observer/serve.ts. `notesToken` goes into the run's Observer page so
92
+ * it can add notes; a server passes it only for a run it takes notes for.
93
+ */
94
+ export async function serveRunPath(runRoot, relativePath, response, runtimeStreamUrls = [], request, notesToken) {
62
95
  const root = runRoot.physicalPath;
63
96
  const filePath = path.resolve(root, relativePath === "" ? "observer/index.html" : relativePath);
64
97
  if (!isPathInside(root, filePath)) {
@@ -87,7 +120,12 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
87
120
  return;
88
121
  }
89
122
  const analysis = await readObserverAnalysis(runRoot);
90
- writeResponse(response, 200, renderObserverHtml(observerData, { analysis }), "text/html; charset=utf-8");
123
+ const notes = await readServedNotes(runRoot);
124
+ writeResponse(response, 200, renderObserverHtml(observerData, {
125
+ analysis,
126
+ notes,
127
+ ...(notesToken === undefined ? {} : { notesToken }),
128
+ }), "text/html; charset=utf-8");
91
129
  return;
92
130
  }
93
131
  if (cleanedRelativePath === "observer/observer-data.json") {
@@ -103,7 +141,10 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
103
141
  writeResponse(response, 200, JSON.stringify(await readObserverAnalysis(runRoot)), "application/json; charset=utf-8");
104
142
  return;
105
143
  }
106
- if (path.extname(filePath).toLowerCase() === ".mp4") {
144
+ // Every file under notes/ is read within the note limit, whatever its name, so media handling
145
+ // never streams one.
146
+ const notePath = derivedRoot === RUN_NOTES_DIR;
147
+ if (!notePath && path.extname(filePath).toLowerCase() === ".mp4") {
107
148
  await serveContainedMedia(runRoot, filePath, response, request);
108
149
  return;
109
150
  }
@@ -112,7 +153,7 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
112
153
  return;
113
154
  }
114
155
  try {
115
- const body = await readContainedFile(runRoot, filePath);
156
+ const body = await readContainedFile(runRoot, filePath, notePath ? { maxBytes: MAX_NOTE_FILE_BYTES } : {});
116
157
  if (!body) {
117
158
  writeResponse(response, 404, "Not found", "text/plain; charset=utf-8");
118
159
  return;
@@ -1,4 +1,5 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import { type NotesWriter } from "./notes-route.js";
2
3
  import type { PinnedDirectory } from "./pinned-files.js";
3
4
  import { type AdmittedRun } from "./run-inventory.js";
4
5
  import type { LibraryHistory } from "./library.js";
@@ -70,6 +71,8 @@ export declare function createShareSafetyAdmission(cwd: string, options?: {
70
71
  }): ShareSafetyAdmission;
71
72
  export interface ServeRequestHandlerOptions {
72
73
  proofRoot: PinnedDirectory;
74
+ /** Takes reviewer notes on loopback; without it the library refuses them. */
75
+ notes?: NotesWriter;
73
76
  safe: boolean;
74
77
  admit: (runId: string) => Promise<AdmittedRun | null>;
75
78
  hostAllowlist: ReadonlySet<string>;
@@ -3,7 +3,9 @@ import { runNotFoundMessage } from "../run/run-not-found.js";
3
3
  import { createServer } from "node:http";
4
4
  import path from "node:path";
5
5
  import { pinDirectChildDirectory, pinDirectory } from "./pinned-files.js";
6
- import { buildHistoryIndex, matchRunRoute, serveRunPath } from "./run-routes.js";
6
+ import { createNotesWriter, NOTES_PATH } from "./notes-route.js";
7
+ import { renderObserver } from "./render.js";
8
+ import { buildHistoryIndex, matchRunRoute, servedRunPaths, serveRunPath } from "./run-routes.js";
7
9
  import { hashRunInventory, inventoryRoot, readRunInventory, } from "./run-inventory.js";
8
10
  import { renderLibraryHtml } from "./library.js";
9
11
  import { buildServeSecurityHeaders, hostAllowed, parsePublicOrigin, } from "./http.js";
@@ -104,6 +106,13 @@ export function createServeRequestHandler(options) {
104
106
  for (const [name, value] of Object.entries(buildServeSecurityHeaders())) {
105
107
  response.setHeader(name, value);
106
108
  }
109
+ if (new URL(request.url ?? "/", "http://127.0.0.1").pathname === NOTES_PATH) {
110
+ if (options.notes)
111
+ await options.notes.handle(request, response);
112
+ else
113
+ writeText(response, 405, "Method Not Allowed");
114
+ return;
115
+ }
107
116
  const method = request.method ?? "GET";
108
117
  if (method !== "GET" && method !== "HEAD") {
109
118
  writeText(response, 405, "Method Not Allowed");
@@ -161,7 +170,7 @@ export function createServeRequestHandler(options) {
161
170
  }
162
171
  // The explicit empty runtimeStreamUrls keeps a future refactor from
163
172
  // reintroducing auth-keyed stream injection on the serve surface.
164
- await serveRunPath(targetRoot, runRoute.relativePath || "observer/index.html", response, [], request);
173
+ await serveRunPath(targetRoot, runRoute.relativePath || "observer/index.html", response, [], request, options.notes?.pageToken(request));
165
174
  return;
166
175
  }
167
176
  writeText(response, 404, "Not found");
@@ -260,8 +269,24 @@ export async function serveObserverLibrary(cwdInput, options) {
260
269
  }
261
270
  const declaredOrigin = options.publicOrigin ? parsePublicOrigin(options.publicOrigin) : null;
262
271
  const hostAllowlist = new Set();
272
+ let port = 0;
273
+ const notes = createNotesWriter({
274
+ exposed: mode !== "loopback",
275
+ port: () => port,
276
+ resolveRun: async (runId) => {
277
+ const pinned = await pinDirectChildDirectory(proofRoot, runId);
278
+ if (!pinned || (options.safe && (await admission.admit(runId)) === null))
279
+ return null;
280
+ return servedRunPaths(pinned);
281
+ },
282
+ refresh: (prepared) => renderObserver(cwd, path.basename(prepared.physicalRunRoot), {
283
+ open: false,
284
+ expectedRun: prepared,
285
+ }),
286
+ });
263
287
  const handler = createServeRequestHandler({
264
288
  proofRoot,
289
+ notes,
265
290
  safe: options.safe,
266
291
  admit: (runId) => admission.admit(runId),
267
292
  hostAllowlist,
@@ -276,7 +301,6 @@ export async function serveObserverLibrary(cwdInput, options) {
276
301
  const server = createServer((request, response) => {
277
302
  void handler(request, response);
278
303
  });
279
- let port;
280
304
  try {
281
305
  port = await listenOnLoopback(server, options.port);
282
306
  }
@@ -314,6 +338,7 @@ export async function serveObserverLibrary(cwdInput, options) {
314
338
  hostAllowlist.add(parsed.host);
315
339
  },
316
340
  close: async () => {
341
+ await notes.settled();
317
342
  const closed = closeServer(server);
318
343
  // Keep-alive sockets would otherwise keep close() pending past the point
319
344
  // the operator believes Ctrl-C tore the server down.