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.
- package/CHANGELOG.md +29 -0
- package/CONTEXT.md +2 -2
- package/README.md +33 -6
- package/dist/actors/computer-use/loop/trace.js +6 -1
- package/dist/actors/computer-use/openai-conversation.d.ts +0 -6
- package/dist/actors/computer-use/openai-conversation.js +0 -8
- package/dist/actors/computer-use/openai-provider.js +5 -8
- package/dist/analysis/automatic.js +2 -1
- package/dist/analysis/evidence.js +41 -21
- package/dist/cli/commands/study-run.js +9 -3
- package/dist/evidence/image.js +82 -3
- package/dist/feedback/derivative-text.d.ts +12 -0
- package/dist/feedback/derivative-text.js +130 -0
- package/dist/feedback/export-bundle.js +39 -136
- package/dist/feedback/export.js +36 -9
- package/dist/observer/artifact.js +2 -9
- package/dist/observer/data.d.ts +9 -2
- package/dist/observer/data.js +17 -2
- package/dist/observer/page-data.d.ts +5 -0
- package/dist/observer/page-data.js +21 -0
- package/dist/observer/participant-items.d.ts +45 -0
- package/dist/observer/participant-items.js +85 -0
- package/dist/observer/render.js +2 -1
- package/dist/observer/run-routes.js +75 -22
- package/dist/observer-app.html +4 -4
- package/dist/run/bundle-shape.js +2 -2
- package/dist/run/bundle.d.ts +5 -2
- package/dist/run/bundle.js +4 -1
- package/dist/run/detail.d.ts +6 -0
- package/dist/run/detail.js +14 -1
- package/dist/run/locate.js +3 -1
- package/dist/run/projection.d.ts +7 -0
- package/dist/run/projection.js +10 -0
- package/dist/run/run.js +9 -2
- package/dist/run/stored-runs.js +10 -4
- package/dist/run/stream-shape.d.ts +11 -1
- package/dist/run/stream-shape.js +26 -2
- package/dist/run/stream-trace.d.ts +68 -0
- package/dist/run/stream-trace.js +260 -0
- package/dist/run/streams.d.ts +20 -0
- package/dist/study/bundle-estimate.d.ts +48 -0
- package/dist/study/bundle-estimate.js +159 -0
- package/dist/study/doctor.js +4 -0
- package/dist/study/plan-types.d.ts +3 -0
- package/dist/study/plan.js +42 -2
- package/dist/study/preflight.js +8 -0
- package/dist/tui-app.js +87 -87
- package/dist/verify/artifacts.d.ts +17 -10
- package/dist/verify/artifacts.js +22 -13
- package/dist/verify/scan-limit.d.ts +6 -0
- package/dist/verify/scan-limit.js +25 -0
- package/dist/verify/verify.d.ts +1 -1
- package/dist/verify/verify.js +68 -18
- package/docs/architecture/github-feedback-loop.md +1 -1
- package/docs/architecture/observer.md +24 -5
- package/docs/contracts/README.md +1 -1
- package/docs/contracts/run-bundle.md +89 -6
- package/docs/contracts/schemas.md +14 -7
- package/docs/contracts/study-analysis.md +1 -1
- package/docs/decisions/0002-observer-is-one-self-contained-file.md +2 -1
- 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 {
|
|
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
|
|
103
|
-
if (
|
|
104
|
-
writeResponse(response, 404,
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
//
|
|
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
|
|
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
|
-
|
|
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) {
|