@workbench-ai/workbench 0.0.46

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 (39) hide show
  1. package/dist/adapter-project.d.ts +29 -0
  2. package/dist/adapter-project.d.ts.map +1 -0
  3. package/dist/adapter-project.js +363 -0
  4. package/dist/benchmark-fingerprint.d.ts +6 -0
  5. package/dist/benchmark-fingerprint.d.ts.map +1 -0
  6. package/dist/benchmark-fingerprint.js +101 -0
  7. package/dist/command-model.d.ts +5 -0
  8. package/dist/command-model.d.ts.map +1 -0
  9. package/dist/command-model.js +558 -0
  10. package/dist/dev-open/client.css +8157 -0
  11. package/dist/dev-open/client.js +252596 -0
  12. package/dist/dev-open/fonts/geist-cyrillic-wght-normal.woff2 +0 -0
  13. package/dist/dev-open/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
  14. package/dist/dev-open/fonts/geist-latin-wght-normal.woff2 +0 -0
  15. package/dist/dev-open-server.d.ts +57 -0
  16. package/dist/dev-open-server.d.ts.map +1 -0
  17. package/dist/dev-open-server.js +496 -0
  18. package/dist/index.d.ts +10 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +3943 -0
  21. package/dist/init-scaffold.d.ts +22 -0
  22. package/dist/init-scaffold.d.ts.map +1 -0
  23. package/dist/init-scaffold.js +30 -0
  24. package/dist/init-template-pack.d.ts +19 -0
  25. package/dist/init-template-pack.d.ts.map +1 -0
  26. package/dist/init-template-pack.js +250 -0
  27. package/dist/local-archive.d.ts +23 -0
  28. package/dist/local-archive.d.ts.map +1 -0
  29. package/dist/local-archive.js +741 -0
  30. package/dist/project-source.d.ts +51 -0
  31. package/dist/project-source.d.ts.map +1 -0
  32. package/dist/project-source.js +700 -0
  33. package/dist/workbench.d.ts +3 -0
  34. package/dist/workbench.d.ts.map +1 -0
  35. package/dist/workbench.js +4 -0
  36. package/dist/workspace-snapshot.d.ts +10 -0
  37. package/dist/workspace-snapshot.d.ts.map +1 -0
  38. package/dist/workspace-snapshot.js +81 -0
  39. package/package.json +45 -0
@@ -0,0 +1,57 @@
1
+ import { type RunSummary, type SurfaceSnapshotFile } from "@workbench-ai/workbench-core";
2
+ export interface LocalWorkbenchDevServer {
3
+ url: string;
4
+ close: () => Promise<void>;
5
+ }
6
+ export interface LocalWorkbenchDevServerOptions {
7
+ workspace: string;
8
+ host: string;
9
+ port: number;
10
+ assetsRoot?: string;
11
+ }
12
+ export declare function startLocalWorkbenchDevServer(options: LocalWorkbenchDevServerOptions): Promise<LocalWorkbenchDevServer>;
13
+ export declare function localRuntimeSnapshot(workspace: string): Promise<{
14
+ workspaceRoot: string;
15
+ activeId: string | null;
16
+ currentBenchmarkFingerprint: string | null;
17
+ summaries: {
18
+ id: string;
19
+ ordinal: number;
20
+ benchmarkFingerprint: string;
21
+ subjectFingerprint: string;
22
+ ownerUserId?: string;
23
+ ownerUsername?: string;
24
+ visibility?: "private" | "public";
25
+ createdAt: string;
26
+ baseId?: string;
27
+ referenceIds: string[];
28
+ status: import("@workbench-ai/workbench-contract").SubjectStatus;
29
+ fileChanges: string[];
30
+ metrics?: Record<string, number>;
31
+ usage?: import("@workbench-ai/workbench-contract").UsageSummary;
32
+ }[];
33
+ results: {
34
+ id: string;
35
+ runId: string;
36
+ benchmarkFingerprint: string;
37
+ subjectFingerprint: string;
38
+ subjectId: string;
39
+ createdAt: string;
40
+ updatedAt: string;
41
+ status: import("@workbench-ai/workbench-contract").EvaluationStatus;
42
+ sampleCount: number;
43
+ completedSampleCount: number;
44
+ errorSampleCount: number;
45
+ metrics?: Record<string, import("@workbench-ai/workbench-contract").MetricStats>;
46
+ durationMs?: import("@workbench-ai/workbench-contract").MetricStats;
47
+ usage?: import("@workbench-ai/workbench-contract").EvaluationUsageStats;
48
+ error?: string;
49
+ }[];
50
+ events: import("@workbench-ai/workbench-contract").RuntimeEvent[];
51
+ latestRun: RunSummary | null;
52
+ runs: RunSummary[];
53
+ }>;
54
+ export declare function localSpecDocument(workspace: string, benchmarkFingerprint?: string | null): Promise<import("@workbench-ai/workbench-contract").AuthoredWorkbenchSourceDocument>;
55
+ export declare function localSourceFiles(workspace: string): Promise<SurfaceSnapshotFile[]>;
56
+ export declare function localBenchmarkMountedFiles(workspace: string, benchmarkFingerprint?: string | null): Promise<SurfaceSnapshotFile[]>;
57
+ //# sourceMappingURL=dev-open-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-open-server.d.ts","sourceRoot":"","sources":["../src/dev-open-server.ts"],"names":[],"mappings":"AAKA,OAAO,EAUL,KAAK,UAAU,EACf,KAAK,mBAAmB,EAGzB,MAAM,8BAA8B,CAAC;AAgBtC,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAoBD,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC,uBAAuB,CAAC,CAoClC;AA0ND,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAe3D;AAUD,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,uFA6BrC;AAwBD,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAExF;AAED,wBAAsB,0BAA0B,CAC9C,SAAS,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,GACnC,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAahC"}
@@ -0,0 +1,496 @@
1
+ import { promises as fs } from "node:fs";
2
+ import http from "node:http";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { buildSubjectCasePhaseRefs, buildWorkbenchTracePhases, createSubjectFilePreview, createCaseReview, loadAuthoredWorkbenchSourceDocument, summarizeSubjectFiles, } from "@workbench-ai/workbench-core";
6
+ import { loadLocalArchive, localRuntimeDir, readLocalSubject, readLocalSubjectFiles, readLocalExecutionFiles, } from "./local-archive.js";
7
+ import { readLocalProjectSource, WORKBENCH_BENCHMARK_FILE, } from "./project-source.js";
8
+ import { localBenchmarkFingerprint } from "./benchmark-fingerprint.js";
9
+ class LocalApiError extends Error {
10
+ status;
11
+ constructor(message, status = 400) {
12
+ super(message);
13
+ this.status = status;
14
+ }
15
+ }
16
+ const DEV_OPEN_ASSET_DIR = "dev-open";
17
+ export async function startLocalWorkbenchDevServer(options) {
18
+ const workspace = path.resolve(options.workspace);
19
+ const assetsRoot = options.assetsRoot ?? defaultDevOpenAssetsRoot();
20
+ await assertDevOpenAssets(assetsRoot);
21
+ const server = http.createServer((request, response) => {
22
+ void handleLocalWorkbenchRequest({
23
+ request,
24
+ response,
25
+ workspace,
26
+ assetsRoot,
27
+ }).catch((error) => {
28
+ sendError(response, error, request.method);
29
+ });
30
+ });
31
+ server.requestTimeout = 0;
32
+ server.timeout = 0;
33
+ await new Promise((resolve, reject) => {
34
+ server.once("error", reject);
35
+ server.listen(options.port, options.host, () => {
36
+ server.off("error", reject);
37
+ resolve();
38
+ });
39
+ });
40
+ const address = server.address();
41
+ if (!address || typeof address === "string") {
42
+ await closeServer(server);
43
+ throw new Error("Workbench local server did not bind a TCP port.");
44
+ }
45
+ const host = displayHost(options.host);
46
+ return {
47
+ url: `http://${host}:${address.port}/`,
48
+ close: () => closeServer(server),
49
+ };
50
+ }
51
+ function defaultDevOpenAssetsRoot() {
52
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), DEV_OPEN_ASSET_DIR);
53
+ }
54
+ async function assertDevOpenAssets(assetsRoot) {
55
+ await Promise.all([
56
+ fs.stat(path.join(assetsRoot, "client.js")),
57
+ fs.stat(path.join(assetsRoot, "client.css")),
58
+ ]).catch(() => {
59
+ throw new Error(`Workbench local browser assets are missing from ${assetsRoot}. Run pnpm --dir products/workbench/packages/cli build.`);
60
+ });
61
+ }
62
+ async function closeServer(server) {
63
+ await new Promise((resolve, reject) => {
64
+ server.close((error) => error ? reject(error) : resolve());
65
+ });
66
+ }
67
+ async function handleLocalWorkbenchRequest(args) {
68
+ const url = new URL(args.request.url ?? "/", "http://workbench.local");
69
+ if (args.request.method !== "GET" && args.request.method !== "HEAD") {
70
+ sendJson(args.response, { message: "Workbench local open is read-only." }, 405, args.request.method);
71
+ return;
72
+ }
73
+ if (url.pathname.startsWith("/api/")) {
74
+ await handleApiRequest(args.request, args.response, args.workspace, url);
75
+ return;
76
+ }
77
+ if (url.pathname === "/assets/client.js") {
78
+ await sendFile(args.response, path.join(args.assetsRoot, "client.js"), "text/javascript; charset=utf-8", args.request.method);
79
+ return;
80
+ }
81
+ if (url.pathname === "/assets/client.css") {
82
+ await sendFile(args.response, path.join(args.assetsRoot, "client.css"), "text/css; charset=utf-8", args.request.method);
83
+ return;
84
+ }
85
+ if (url.pathname.startsWith("/assets/fonts/")) {
86
+ await sendFontFile(args.response, args.assetsRoot, url, args.request.method);
87
+ return;
88
+ }
89
+ await sendHtml(args.response, args.request.method);
90
+ }
91
+ async function handleApiRequest(request, response, workspace, url) {
92
+ switch (url.pathname) {
93
+ case "/api/snapshot":
94
+ sendJson(response, await localRuntimeSnapshot(workspace), 200, request.method);
95
+ return;
96
+ case "/api/spec":
97
+ sendJson(response, await localSpecDocument(workspace, readOptionalSearchString(url.searchParams, "fingerprint")), 200, request.method);
98
+ return;
99
+ case "/api/source/files":
100
+ sendJson(response, summarizeSubjectFiles(await localBenchmarkMountedFiles(workspace, readOptionalSearchString(url.searchParams, "fingerprint")), []), 200, request.method);
101
+ return;
102
+ case "/api/source/preview":
103
+ sendJson(response, createSubjectFilePreview({
104
+ files: await localBenchmarkMountedFiles(workspace, readOptionalSearchString(url.searchParams, "fingerprint")),
105
+ path: readSearchString(url.searchParams, "path"),
106
+ view: readPreviewMode(url.searchParams),
107
+ }), 200, request.method);
108
+ return;
109
+ case "/api/record":
110
+ sendJson(response, readSubjectForApi(await loadLocalArchive(workspace), readSearchString(url.searchParams, "id")), 200, request.method);
111
+ return;
112
+ case "/api/result":
113
+ sendJson(response, readResultForApi(await loadLocalArchive(workspace), readSearchString(url.searchParams, "id")), 200, request.method);
114
+ return;
115
+ case "/api/subject/files": {
116
+ const snapshot = await loadLocalArchive(workspace);
117
+ const subjectId = readSearchString(url.searchParams, "id");
118
+ const subject = readSubjectForApi(snapshot, subjectId);
119
+ sendJson(response, summarizeSubjectFiles(readSubjectFilesForApi(snapshot, subjectId), subject.fileChanges), 200, request.method);
120
+ return;
121
+ }
122
+ case "/api/subject/preview": {
123
+ const snapshot = await loadLocalArchive(workspace);
124
+ const subjectId = readSearchString(url.searchParams, "id");
125
+ sendJson(response, createSubjectFilePreview({
126
+ files: readSubjectFilesForApi(snapshot, subjectId),
127
+ path: readSearchString(url.searchParams, "path"),
128
+ view: readPreviewMode(url.searchParams),
129
+ }), 200, request.method);
130
+ return;
131
+ }
132
+ case "/api/case-review": {
133
+ const snapshot = await loadLocalArchive(workspace);
134
+ const subjectId = readSearchString(url.searchParams, "id");
135
+ const caseId = readSearchString(url.searchParams, "case");
136
+ const jobs = await loadLocalJobs(workspace);
137
+ sendJson(response, createCaseReview({
138
+ subject: readSubjectForApi(snapshot, subjectId),
139
+ caseId,
140
+ phases: buildSubjectCasePhaseRefs({ jobs, subjectId, caseId }),
141
+ }), 200, request.method);
142
+ return;
143
+ }
144
+ case "/api/run":
145
+ sendJson(response, await localRunDetail(workspace, readSearchString(url.searchParams, "id")), 200, request.method);
146
+ return;
147
+ case "/api/traces": {
148
+ const traceRunId = readSearchString(url.searchParams, "run");
149
+ const traceJobs = await loadLocalJobs(workspace);
150
+ sendJson(response, {
151
+ projectId: "local",
152
+ runId: traceRunId,
153
+ phases: buildWorkbenchTracePhases({
154
+ jobs: traceJobs.filter((job) => job.runId === traceRunId),
155
+ traceIdPrefix: "local-phase",
156
+ traceForJob: readLocalTrace,
157
+ }),
158
+ }, 200, request.method);
159
+ return;
160
+ }
161
+ case "/api/execution/files": {
162
+ const execRunId = readSearchString(url.searchParams, "run");
163
+ const execJobId = readSearchString(url.searchParams, "id");
164
+ const execFiles = await loadExecutionFiles(workspace, execRunId, execJobId);
165
+ sendJson(response, execFiles, 200, request.method);
166
+ return;
167
+ }
168
+ case "/api/execution/preview": {
169
+ const previewRunId = readSearchString(url.searchParams, "run");
170
+ const previewJobId = readSearchString(url.searchParams, "id");
171
+ const previewFilePath = readSearchString(url.searchParams, "path");
172
+ const previewFiles = await readExecutionFilesForRun(workspace, previewRunId, previewJobId);
173
+ sendJson(response, createSubjectFilePreview({
174
+ files: previewFiles,
175
+ path: previewFilePath,
176
+ view: readPreviewMode(url.searchParams),
177
+ }), 200, request.method);
178
+ return;
179
+ }
180
+ default:
181
+ throw new LocalApiError(`Unknown Workbench local API route: ${url.pathname}`, 404);
182
+ }
183
+ }
184
+ export async function localRuntimeSnapshot(workspace) {
185
+ const snapshot = await loadLocalArchive(workspace);
186
+ const summaries = snapshot.subjects.map(subjectSummary);
187
+ const activeId = snapshot.activeId;
188
+ const currentBenchmarkFingerprint = await readCurrentBenchmarkFingerprint(workspace);
189
+ return {
190
+ workspaceRoot: path.resolve(workspace),
191
+ activeId,
192
+ currentBenchmarkFingerprint,
193
+ summaries,
194
+ results: snapshot.evaluations.map(resultSummary),
195
+ events: snapshot.events,
196
+ latestRun: snapshot.runs.at(-1) ?? null,
197
+ runs: snapshot.runs,
198
+ };
199
+ }
200
+ async function readCurrentBenchmarkFingerprint(workspace) {
201
+ return await readLocalProjectSource(workspace)
202
+ .then(localBenchmarkFingerprint)
203
+ .catch(() => null);
204
+ }
205
+ export async function localSpecDocument(workspace, benchmarkFingerprint) {
206
+ const projectSource = await readLocalProjectSource(workspace).catch(() => null);
207
+ const requestedFingerprint = normalizeOptionalFingerprint(benchmarkFingerprint);
208
+ const currentFingerprint = projectSource
209
+ ? await readCurrentBenchmarkFingerprint(workspace).catch(() => null)
210
+ : null;
211
+ if (requestedFingerprint &&
212
+ currentFingerprint &&
213
+ requestedFingerprint !== currentFingerprint) {
214
+ const snapshot = await loadLocalArchive(workspace);
215
+ const document = localHistoricalBenchmarkDocument(snapshot, requestedFingerprint);
216
+ if (document) {
217
+ return document;
218
+ }
219
+ throw new LocalApiError(`Benchmark version not found: ${requestedFingerprint}`, 404);
220
+ }
221
+ const sourceYaml = projectSource?.specSource ?? "";
222
+ const cases = projectSource
223
+ ? caseSummaryFilesFromEngineCases(projectSource.engineCases, projectSource.engineResolveFiles)
224
+ : [];
225
+ return loadAuthoredWorkbenchSourceDocument({
226
+ sourceYaml,
227
+ path: WORKBENCH_BENCHMARK_FILE,
228
+ sourceFiles: projectSource?.sourceFiles,
229
+ cases,
230
+ });
231
+ }
232
+ function caseSummaryFilesFromEngineCases(engineCases, files) {
233
+ const existingCaseIds = new Set(files.flatMap((file) => {
234
+ const normalized = file.path.replace(/\\/gu, "/").replace(/^\/+/u, "");
235
+ const slash = normalized.indexOf("/");
236
+ return slash > 0 ? [normalized.slice(0, slash)] : [];
237
+ }));
238
+ return [
239
+ ...files.map((file) => ({ ...file })),
240
+ ...engineCases
241
+ .filter((engineCase) => !existingCaseIds.has(engineCase.id))
242
+ .map((engineCase) => ({
243
+ path: `${engineCase.id}/.workbench-case.json`,
244
+ encoding: "utf8",
245
+ content: `${JSON.stringify({ id: engineCase.id })}\n`,
246
+ executable: false,
247
+ })),
248
+ ];
249
+ }
250
+ export async function localSourceFiles(workspace) {
251
+ return (await readLocalProjectSource(workspace)).sourceFiles;
252
+ }
253
+ export async function localBenchmarkMountedFiles(workspace, benchmarkFingerprint) {
254
+ const requestedFingerprint = normalizeOptionalFingerprint(benchmarkFingerprint);
255
+ const projectSource = await readLocalProjectSource(workspace);
256
+ const currentFingerprint = await readCurrentBenchmarkFingerprint(workspace).catch(() => null);
257
+ if (requestedFingerprint &&
258
+ currentFingerprint &&
259
+ requestedFingerprint !== currentFingerprint) {
260
+ const snapshot = await loadLocalArchive(workspace);
261
+ return localHistoricalBenchmarkFiles(snapshot, requestedFingerprint);
262
+ }
263
+ return inspectableEngineCaseFiles(projectSource.engineCases);
264
+ }
265
+ function localHistoricalBenchmarkDocument(snapshot, benchmarkFingerprint) {
266
+ const subject = snapshot.subjects.find((entry) => entry.benchmarkFingerprint === benchmarkFingerprint);
267
+ const source = subject ? readBenchmarkSourceMetadata(subject) : null;
268
+ if (!source?.sourceYaml) {
269
+ return null;
270
+ }
271
+ return loadAuthoredWorkbenchSourceDocument({
272
+ sourceYaml: source.sourceYaml,
273
+ path: WORKBENCH_BENCHMARK_FILE,
274
+ sourceFiles: source.files,
275
+ cases: localHistoricalBenchmarkFiles(snapshot, benchmarkFingerprint),
276
+ });
277
+ }
278
+ function localHistoricalBenchmarkFiles(_snapshot, _benchmarkFingerprint) {
279
+ return [];
280
+ }
281
+ function inspectableEngineCaseFiles(engineCases) {
282
+ return engineCases.flatMap((bundle) => engineCaseFiles(bundle).map((file) => ({
283
+ ...file,
284
+ path: `${bundle.id}/${file.path}`,
285
+ }))).sort((left, right) => left.path.localeCompare(right.path));
286
+ }
287
+ function engineCaseFiles(bundle) {
288
+ const buckets = bundle.files;
289
+ return buckets.source?.length
290
+ ? buckets.source
291
+ : [...(buckets.subjectVisible ?? []), ...(buckets.enginePrivate ?? [])];
292
+ }
293
+ function subjectSummary(subject) {
294
+ const { eval: _eval, prompt: _prompt, meta: _meta, ...summary } = subject;
295
+ return summary;
296
+ }
297
+ function resultSummary(result) {
298
+ const { evaluation: _evaluation, ...summary } = result;
299
+ return summary;
300
+ }
301
+ function readSubjectForApi(snapshot, subjectId) {
302
+ return readArchiveRecord("Subject", subjectId, () => readLocalSubject(snapshot, subjectId));
303
+ }
304
+ function readResultForApi(snapshot, resultId) {
305
+ return readArchiveRecord("Evaluation result", resultId, () => {
306
+ const result = snapshot.evaluations.find((entry) => entry.id === resultId);
307
+ if (!result) {
308
+ throw new Error(`Evaluation result not found: ${resultId}`);
309
+ }
310
+ return result;
311
+ });
312
+ }
313
+ function readSubjectFilesForApi(snapshot, subjectId) {
314
+ return readArchiveRecord("Subject", subjectId, () => readLocalSubjectFiles(snapshot, subjectId));
315
+ }
316
+ function readBenchmarkSourceMetadata(subject) {
317
+ const benchmark = asRecord(asRecord(subject.meta)?.benchmark);
318
+ const files = Array.isArray(benchmark?.files)
319
+ ? benchmark.files
320
+ .map(readSurfaceSnapshotFile)
321
+ .filter((file) => file !== null)
322
+ : [];
323
+ const sourceYaml = files.find((file) => file.path === WORKBENCH_BENCHMARK_FILE)?.content ?? null;
324
+ if (!sourceYaml) {
325
+ return null;
326
+ }
327
+ return { sourceYaml, files };
328
+ }
329
+ function readSurfaceSnapshotFile(value) {
330
+ const record = asRecord(value);
331
+ if (!record) {
332
+ return null;
333
+ }
334
+ const filePath = typeof record?.path === "string" ? record.path : "";
335
+ const content = typeof record?.content === "string" ? record.content : null;
336
+ if (!filePath || content === null) {
337
+ return null;
338
+ }
339
+ return {
340
+ path: filePath,
341
+ kind: record.kind === "binary" ? "binary" : "text",
342
+ encoding: record.encoding === "base64" ? "base64" : "utf8",
343
+ content,
344
+ executable: record.executable === true,
345
+ };
346
+ }
347
+ function asRecord(value) {
348
+ return value && typeof value === "object" && !Array.isArray(value)
349
+ ? value
350
+ : null;
351
+ }
352
+ function normalizeOptionalFingerprint(value) {
353
+ const normalized = value?.trim();
354
+ return normalized ? normalized : null;
355
+ }
356
+ function readArchiveRecord(kind, id, read) {
357
+ try {
358
+ return read();
359
+ }
360
+ catch (error) {
361
+ if (error instanceof Error && error.message === `${kind} not found: ${id}`) {
362
+ throw new LocalApiError(error.message, 404);
363
+ }
364
+ throw error;
365
+ }
366
+ }
367
+ async function localRunDetail(workspace, runId) {
368
+ const snapshot = await loadLocalArchive(workspace);
369
+ const run = snapshot.runs.find((entry) => entry.id === runId);
370
+ if (!run) {
371
+ throw new LocalApiError(`Run not found: ${runId}`, 404);
372
+ }
373
+ const allJobs = await loadLocalJobs(workspace);
374
+ const runJobs = allJobs.filter((job) => job.runId === runId);
375
+ return { run, jobs: runJobs };
376
+ }
377
+ async function loadExecutionFiles(workspace, runId, jobId) {
378
+ const files = await readExecutionFilesForRun(workspace, runId, jobId);
379
+ return summarizeSubjectFiles(files);
380
+ }
381
+ async function readExecutionFilesForRun(workspace, runId, jobId) {
382
+ await assertExecutionJobInRun(workspace, runId, jobId);
383
+ return await readLocalExecutionFiles(workspace, jobId);
384
+ }
385
+ async function assertExecutionJobInRun(workspace, runId, jobId) {
386
+ const job = (await loadLocalJobs(workspace)).find((entry) => entry.id === jobId);
387
+ if (!job || job.runId !== runId) {
388
+ throw new LocalApiError(`Execution job not found: ${jobId}`, 404);
389
+ }
390
+ }
391
+ async function loadLocalJobs(workspace) {
392
+ const jobsDir = path.join(localRuntimeDir(workspace), "jobs");
393
+ const entries = await fs.readdir(jobsDir, { withFileTypes: true }).catch(() => []);
394
+ const jobs = [];
395
+ for (const entry of entries) {
396
+ if (entry.isFile() && entry.name.endsWith(".json")) {
397
+ const content = await fs.readFile(path.join(jobsDir, entry.name), "utf8");
398
+ jobs.push(JSON.parse(content));
399
+ }
400
+ }
401
+ return jobs;
402
+ }
403
+ function readLocalTrace(job) {
404
+ const trace = job.trace;
405
+ if (!trace || typeof trace !== "object" || Array.isArray(trace)) {
406
+ return { trace_id: job.id, spans: [], events: [], summaries: [] };
407
+ }
408
+ const record = trace;
409
+ return {
410
+ trace_id: typeof record.trace_id === "string" ? record.trace_id : job.id,
411
+ spans: Array.isArray(record.spans) ? record.spans : [],
412
+ events: Array.isArray(record.events) ? record.events : [],
413
+ summaries: Array.isArray(record.summaries) ? record.summaries : [],
414
+ };
415
+ }
416
+ function readSearchString(params, key) {
417
+ const value = params.get(key);
418
+ if (!value) {
419
+ throw new LocalApiError(`${key} is required.`);
420
+ }
421
+ return value;
422
+ }
423
+ function readOptionalSearchString(params, key) {
424
+ const value = params.get(key)?.trim();
425
+ return value ? value : null;
426
+ }
427
+ function readPreviewMode(params) {
428
+ const view = params.get("view") ?? "rendered";
429
+ if (view === "diff" || view === "raw" || view === "rendered") {
430
+ return view;
431
+ }
432
+ throw new LocalApiError("view must be diff, raw, or rendered.");
433
+ }
434
+ async function sendFile(response, filePath, contentType, method = "GET") {
435
+ const body = await fs.readFile(filePath);
436
+ response.writeHead(200, {
437
+ "content-type": contentType,
438
+ "content-length": body.byteLength,
439
+ "cache-control": "no-store",
440
+ });
441
+ response.end(method === "HEAD" ? undefined : body);
442
+ }
443
+ async function sendFontFile(response, assetsRoot, url, method = "GET") {
444
+ let fileName;
445
+ try {
446
+ fileName = decodeURIComponent(url.pathname.slice("/assets/fonts/".length));
447
+ }
448
+ catch {
449
+ throw new LocalApiError("Invalid font asset path.", 404);
450
+ }
451
+ if (!fileName || fileName.includes("/") || fileName.includes("\\")) {
452
+ throw new LocalApiError("Invalid font asset path.", 404);
453
+ }
454
+ await sendFile(response, path.join(assetsRoot, "fonts", fileName), "font/woff2", method);
455
+ }
456
+ async function sendHtml(response, method = "GET") {
457
+ const body = `<!doctype html>
458
+ <html lang="en">
459
+ <head>
460
+ <meta charset="utf-8" />
461
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
462
+ <title>Workbench Local</title>
463
+ <link rel="stylesheet" href="/assets/client.css" />
464
+ </head>
465
+ <body>
466
+ <div id="root"></div>
467
+ <script type="module" src="/assets/client.js"></script>
468
+ </body>
469
+ </html>`;
470
+ response.writeHead(200, {
471
+ "content-type": "text/html; charset=utf-8",
472
+ "content-length": Buffer.byteLength(body),
473
+ "cache-control": "no-store",
474
+ });
475
+ response.end(method === "HEAD" ? undefined : body);
476
+ }
477
+ function sendJson(response, value, status = 200, method = "GET") {
478
+ const body = `${JSON.stringify(value, null, 2)}\n`;
479
+ response.writeHead(status, {
480
+ "content-type": "application/json; charset=utf-8",
481
+ "content-length": Buffer.byteLength(body),
482
+ "cache-control": "no-store",
483
+ });
484
+ response.end(method === "HEAD" ? undefined : body);
485
+ }
486
+ function sendError(response, error, method = "GET") {
487
+ const message = error instanceof Error ? error.message : String(error);
488
+ const status = error instanceof LocalApiError ? error.status : 500;
489
+ sendJson(response, { message }, status, method);
490
+ }
491
+ function displayHost(host) {
492
+ if (host === "0.0.0.0" || host === "::") {
493
+ return "127.0.0.1";
494
+ }
495
+ return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
496
+ }
@@ -0,0 +1,10 @@
1
+ interface CliIo {
2
+ stdin: NodeJS.ReadableStream;
3
+ stdout: NodeJS.WritableStream;
4
+ stderr: NodeJS.WritableStream;
5
+ }
6
+ interface CliRuntimeOptions {
7
+ }
8
+ export declare function runCli(argv: readonly string[], io?: CliIo, runtimeOptions?: CliRuntimeOptions): Promise<number>;
9
+ export {};
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA0GA,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC,cAAc,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B;AAoCD,UAAU,iBAAiB;CAAG;AA+I9B,wBAAsB,MAAM,CAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,GAAE,KAIH,EACD,cAAc,GAAE,iBAAsB,GACrC,OAAO,CAAC,MAAM,CAAC,CA6GjB"}