@kheleshikhi/studio-agent 0.8.0 → 0.9.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/README.md CHANGED
@@ -221,6 +221,43 @@ query, returns ranked summaries with a small default limit, and leaves full
221
221
  source to `example inspect <id>`; normally inspect no more than three relevant
222
222
  examples.
223
223
 
224
+ ### Authoring projects (library scope)
225
+
226
+ A Maker's authoring project fixes which asset libraries the work may draw
227
+ from. Start the Attempt in one and every later command in that workspace is
228
+ scoped by the server:
229
+
230
+ ```sh
231
+ nerdbro project list # your projects
232
+ nerdbro project libraries # every library; home/permitted marked for the active project
233
+ nerdbro project create --title "Siege of the fort" --output-target video \
234
+ --home-library history-youtube [--purpose <text>] [--permitted shared-general,...]
235
+ nerdbro attempt start --label siege --project <project-id>
236
+ nerdbro project current | project show [<project-id>]
237
+ nerdbro component search cannon # compact, in-scope page; outside-scope ids on stderr
238
+ nerdbro component search cannon --all-libraries [--limit n] [--offset n]
239
+ nerdbro component search cannon --full # the old full-contract list
240
+ nerdbro component inspect physics.cannon.v1 # libraries, inScope, scopeBasis; never filtered
241
+ nerdbro project import physics.cannon.v1 --reason "siege cannon" [--kind component|behavior|audio|experience]
242
+ nerdbro studio validate <asset-id> # scope_warnings: LIBRARY_SCOPE_NOT_PERMITTED
243
+ ```
244
+
245
+ The project's public id is kept in `.nerdbro-attempt.json`; `project show`
246
+ and `project import` default to it (pass `--project` or an id to choose
247
+ another). In a project Attempt, `component|behavior search` returns
248
+ `view: "compact"`, `scope`, and `outside_scope` `{count, examples, hint}`; the
249
+ human output prints the scope and hint on stderr (stdout stays JSON).
250
+ `--compact` asks for the compact view without a project; `--full` and
251
+ `--compact` are exclusive. Studio assets created in a project Attempt join
252
+ the project. `studio validate|compile|preview` copy any
253
+ `LIBRARY_SCOPE_NOT_PERMITTED` diagnostics to `scope_warnings`; approval in the
254
+ Studio refuses an unimported out-of-scope dependency. Without `--project`
255
+ nothing changes. Routes and scopes: `GET /v1/agent/libraries`
256
+ (`CATALOGUE_READ`), `GET /v1/agent/projects[/{id}]` (`STUDIO_READ`),
257
+ `POST /v1/agent/projects` (`STUDIO_ASSET_CREATE`),
258
+ `POST /v1/agent/projects/{id}/imports` (`STUDIO_DRAFT_WRITE`); no library or
259
+ membership writes.
260
+
224
261
  `nerdbro experience inspect-browser <id>` opens the canonical Experience
225
262
  workspace. `nerdbro experience drive <id> --scenario scenario.json` runs a
226
263
  bounded scenario against the mounted Experience Preview of **your local
@@ -354,6 +391,23 @@ For a gesture with an interaction handle, assert
354
391
  `gestures.<id>.affordance_visible` before `drag`; the driver also refuses a
355
392
  handle that the mounted preview has not presented.
356
393
 
394
+ ## Presentation video and episodes
395
+
396
+ `nerdbro studio render-video <asset> --recipe recipe.json` and `nerdbro
397
+ experience render-video <stable-id> --recipe recipe.json` render one
398
+ presentation MP4 (H.264/AAC, exact frame times, sounds mixed from canonical
399
+ bytes; needs local FFmpeg) as a daemon render job the CLI polls; Ctrl-C
400
+ cancels it. `render.json` records the source hash, the dependency record
401
+ digest (Scene) or per-Scene provenance (Experience), the recipe and every
402
+ frame's SHA-256, plus a per-phase capture timing breakdown.
403
+
404
+ `nerdbro episode validate|plan|render|status <episode.json>` assembles an
405
+ episode from such shots with a content-addressed shot cache, one ducked and
406
+ loudness-normalised mix, SRT subtitles and a QC report. `NERDBRO_RENDER_WORKERS`
407
+ (default 1, at most 4) sets how many render browsers the daemon may run, and
408
+ `--workers` how many shots render at once. See
409
+ [episode video production](../docs/guides/episode-video.md).
410
+
357
411
  ## Library authoring
358
412
 
359
413
  The Studio Library's semantic kinds — `experience`, `audio`, `component`
package/dist/attempt.d.ts CHANGED
@@ -7,6 +7,8 @@ export interface AttemptPointer {
7
7
  attempt_root: string;
8
8
  server_origin: string;
9
9
  started_at: string;
10
+ /** Authoring project public id when the Attempt was started in one; the server owns its scope. */
11
+ project?: string | null;
10
12
  }
11
13
  export declare function attemptHome(): string;
12
14
  export declare function attemptDirectory(attemptId: string): string;
@@ -15,6 +17,8 @@ export declare function readAttemptPointer(start?: string): Promise<{
15
17
  workspace: string;
16
18
  } | null>;
17
19
  export declare function createAttemptPointer(server: Record<string, unknown>, serverOrigin: string, workspace?: string): Promise<AttemptPointer>;
20
+ /** The project public id the server scoped this Attempt to, if any. */
21
+ export declare function attemptProject(server: Record<string, unknown>): string | null;
18
22
  export declare function saveCommandEvidence(pointer: AttemptPointer, commandId: string, command: string, result: unknown): Promise<void>;
19
23
  export declare function finalizeClient(pointer: AttemptPointer, workspace: string, server: Record<string, unknown>): Promise<{
20
24
  path: string;
package/dist/attempt.js CHANGED
@@ -54,12 +54,22 @@ export async function createAttemptPointer(server, serverOrigin, workspace = pro
54
54
  label: String(server.label), mode: String(server.mode),
55
55
  attempt_root: root, server_origin: serverOrigin,
56
56
  started_at: String(server.started_at),
57
+ // Only a project Attempt records one, so unscoped pointers stay as before.
58
+ ...(attemptProject(server) ? { project: attemptProject(server) } : {}),
57
59
  };
58
60
  await writeFile(path.join(workspace, POINTER_NAME), `${JSON.stringify(pointer, null, 2)}\n`, { flag: "wx", mode: 0o600 });
59
- await writeFile(path.join(root, "README.md"), `# NerdBro Attempt ${attemptId}\n\nLabel: ${pointer.label}\nMode: ${pointer.mode}\n\nRun \`nerdbro attempt finish --outcome completed|blocked|incomplete|abandoned\` in the original workspace with the accurate outcome, then run \`just studio-agent-attempt-export ${attemptId}\` from the NerdBro repository.\n`);
61
+ await writeFile(path.join(root, "README.md"), `# NerdBro Attempt ${attemptId}\n\nLabel: ${pointer.label}\nMode: ${pointer.mode}\n${pointer.project ? `Project: ${pointer.project}\n` : ""}\nRun \`nerdbro attempt finish --outcome completed|blocked|incomplete|abandoned\` in the original workspace with the accurate outcome, then run \`just studio-agent-attempt-export ${attemptId}\` from the NerdBro repository.\n`);
60
62
  await writeFile(path.join(root, "RUN_MANIFEST.json"), `${JSON.stringify({ schema: "kheleshikhi.studio-agent-attempt-run.v1", ...server }, null, 2)}\n`);
61
63
  return pointer;
62
64
  }
65
+ /** The project public id the server scoped this Attempt to, if any. */
66
+ export function attemptProject(server) {
67
+ const scope = server.project_scope;
68
+ if (!scope || typeof scope !== "object")
69
+ return null;
70
+ const project = scope.project;
71
+ return typeof project === "string" && project ? project : null;
72
+ }
63
73
  export async function saveCommandEvidence(pointer, commandId, command, result) {
64
74
  const root = attemptDirectory(pointer.attempt_id);
65
75
  const destination = path.join(root, "client", "evidence", command.startsWith("studio.drive") ? "drives" : command.startsWith("studio.inspect") ? "captures" : command.startsWith("studio.certify") ? "certification" : "other");
@@ -1 +1 @@
1
- {"version":3,"file":"attempt.js","sourceRoot":"","sources":["../src/attempt.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"attempt.js","sourceRoot":"","sources":["../src/attempt.ts"],"names":[],"mappings":"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"}
package/dist/browser.d.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  import { type RenderRecipe } from "./video.js";
2
2
  import { type Page } from "playwright";
3
3
  import { AgentApi } from "./protocol.js";
4
+ import { type RenderProgress } from "./render-jobs.js";
4
5
  import { type DriveScenario, type ExperienceScenario } from "./scenario.js";
6
+ /** A render's cancellation signal and progress reports (a daemon render job). */
7
+ export type RenderControl = {
8
+ signal?: AbortSignal;
9
+ onProgress?: (value: RenderProgress) => void;
10
+ };
5
11
  /** One semantic step as it happened, for Agent Presence and the activity feed. */
6
12
  export type DriveStepReport = {
7
13
  index: number;
@@ -90,13 +96,15 @@ export interface PreviewObservationRequest extends BrowserCaptureRequest {
90
96
  }
91
97
  export declare class StudioBrowser {
92
98
  private api;
99
+ private readonly profileName;
93
100
  private context?;
94
101
  private page?;
95
102
  private headless;
96
103
  private browserEngine;
97
104
  private apiOrigin;
98
105
  private credentials;
99
- constructor(api: AgentApi);
106
+ /** `profileName` keeps a render worker's persistent profile apart from the main browser's. */
107
+ constructor(api: AgentApi, profileName?: string);
100
108
  start(headless: boolean): Promise<void>;
101
109
  stop(): Promise<void>;
102
110
  clearAuthentication(): void;
@@ -139,7 +147,7 @@ export declare class StudioBrowser {
139
147
  audioDirectory: string;
140
148
  /** Optional: afterwards, play in real time and sample frame intervals. */
141
149
  measurePlaybackSeconds?: number;
142
- }): Promise<Record<string, unknown>>;
150
+ } & RenderControl): Promise<Record<string, unknown>>;
143
151
  /**
144
152
  * Presentation frames of an Experience: the canonical Experience player
145
153
  * in the presentation host, driven by video time (`renderBegin`, then
@@ -154,11 +162,17 @@ export declare class StudioBrowser {
154
162
  recipe: RenderRecipe;
155
163
  framesDirectory: string;
156
164
  audioDirectory: string;
157
- }): Promise<Record<string, unknown>>;
165
+ } & RenderControl): Promise<Record<string, unknown>>;
158
166
  private requirePage;
159
167
  /** The page's runtime observation and its one-at-a-time drive channel. */
160
168
  private renderChannel;
161
- /** The newest observation once [ready] holds (the page publishes every 150 ms). */
169
+ /**
170
+ * The newest observation once [ready] holds. The page publishes one every
171
+ * 150 ms and after every command; while not ready, each read also asks the
172
+ * host for a fresh one (the drive-command wake event, with no command
173
+ * waiting, only republishes), so a paint that lands between polls is seen
174
+ * within milliseconds.
175
+ */
162
176
  private waitForRender;
163
177
  /**
164
178
  * The frame loop both subjects share: actions at their seconds, a step to
package/dist/browser.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { mkdir, rm, writeFile } from "node:fs/promises";
2
- import { actionsBetween, audioKey, frameCount, frameTime, pngSize, sha256 } from "./video.js";
2
+ import { actionsBetween, audioKey, frameCount, frameTime, PhaseTimes, pngSize, sha256 } from "./video.js";
3
3
  import path from "node:path";
4
4
  import { chromium } from "playwright";
5
5
  import { cacheDirectory } from "./state.js";
6
+ import { throwIfCancelled } from "./render-jobs.js";
6
7
  import { CliError } from "./types.js";
7
8
  import { assertCurrentObservation, ObservationSampler, observationSettings } from "./observation.js";
8
9
  import { evidenceMode, isDriveCommand, matchesObservation, isExperienceCommand, matchesExperienceObservation, observedValue, stepSummary } from "./scenario.js";
@@ -49,6 +50,25 @@ export async function waitForCapturePaint(page) {
49
50
  throw new CliError("Studio did not paint after runtime readiness.", "STUDIO_CAPTURE_PAINT_TIMEOUT");
50
51
  }
51
52
  }
53
+ /**
54
+ * One PNG of the viewport `clip` at device scale 1, through the page's
55
+ * existing CDP session with Chrome's fast PNG encoder (`optimizeForSpeed`:
56
+ * the same pixels, lighter compression). Playwright's screenshot is the
57
+ * fallback.
58
+ */
59
+ async function captureViewportPng(page, clip) {
60
+ const session = viewportSessions.get(page);
61
+ if (session) {
62
+ try {
63
+ const { data } = await session.send("Page.captureScreenshot", {
64
+ format: "png", optimizeForSpeed: true, captureBeyondViewport: false, clip: { ...clip, scale: 1 },
65
+ });
66
+ return Buffer.from(data, "base64");
67
+ }
68
+ catch { /* fall back to Playwright */ }
69
+ }
70
+ return page.screenshot({ type: "png", scale: "css", clip });
71
+ }
52
72
  async function waitForVisualReady(page, timeoutMs, stage) {
53
73
  try {
54
74
  await page.waitForFunction(() => {
@@ -269,14 +289,17 @@ function driveOutcome(kind, steps, results, summaries, final) {
269
289
  const LIBRARY_WORKSPACES = new Set(["experiences", "audio", "components", "behaviors"]);
270
290
  export class StudioBrowser {
271
291
  api;
292
+ profileName;
272
293
  context;
273
294
  page;
274
295
  headless = false;
275
296
  browserEngine = "not-started";
276
297
  apiOrigin;
277
298
  credentials;
278
- constructor(api) {
299
+ /** `profileName` keeps a render worker's persistent profile apart from the main browser's. */
300
+ constructor(api, profileName = "browser-profile") {
279
301
  this.api = api;
302
+ this.profileName = profileName;
280
303
  this.apiOrigin = new URL(api.origin).origin;
281
304
  this.credentials = new BrowserCredentialManager(api);
282
305
  }
@@ -284,7 +307,7 @@ export class StudioBrowser {
284
307
  if (this.context)
285
308
  return;
286
309
  this.headless = headless;
287
- const profile = path.join(cacheDirectory(), "browser-profile");
310
+ const profile = path.join(cacheDirectory(), this.profileName);
288
311
  await mkdir(profile, { recursive: true, mode: 0o700 });
289
312
  const attempts = [
290
313
  { channel: "chrome", name: "Google Chrome" },
@@ -877,10 +900,12 @@ export class StudioBrowser {
877
900
  */
878
901
  async renderFrames(request) {
879
902
  const { recipe } = request;
903
+ request.onProgress?.({ stage: "mount" });
880
904
  const mounted = await this.drive({ ...request, viewport: `${recipe.width}x${recipe.height}`, present: true,
881
905
  deviceScaleFactor: 1, scenario: { version: 1, steps: [{ op: "pause" }] } });
882
906
  const page = this.requirePage();
883
- const { observation, command } = this.renderChannel(page);
907
+ const phases = new PhaseTimes();
908
+ const { observation, command } = this.renderChannel(page, phases);
884
909
  const first = await observation();
885
910
  if (first.scene_clock_seconds === undefined || first.scene_clock_seconds === null) {
886
911
  throw new CliError("This Scene has no SceneClock; a video needs logical time.", "VIDEO_NO_CLOCK", 2);
@@ -892,7 +917,7 @@ export class StudioBrowser {
892
917
  // plays, so an authored pause holds the picture. Record each stretch.
893
918
  const clock = [];
894
919
  let lastPlaying;
895
- const frames = await this.captureFrames(page, request, async (time, index) => {
920
+ const frames = await this.captureFrames(page, { ...request, phases }, async (time, index) => {
896
921
  const reached = await this.waitForRender(page, (value) => typeof value.render_video_seconds === "number" &&
897
922
  Math.abs(value.render_video_seconds - time) <= 1e-6 && value.visual_ready === true, "the Scene to stand at the frame and paint");
898
923
  if (typeof reached.scene_clock_seconds !== "number") {
@@ -904,8 +929,9 @@ export class StudioBrowser {
904
929
  lastPlaying = reached.scene_clock_playing;
905
930
  return reached;
906
931
  }, command);
932
+ request.onProgress?.({ stage: "audio_collect" });
907
933
  const log = await command({ op: "renderLog" });
908
- const audio = await this.collectAudio(log.audio ?? [], request.audioDirectory, recipe, async (event) => (await command({ op: "audioBytes", target: event.id }, 30_000)).bytes);
934
+ const audio = await this.collectAudio(log.audio ?? [], request.audioDirectory, recipe, request.signal, async (event) => (await command({ op: "audioBytes", target: event.id }, 30_000)).bytes);
909
935
  const final = await observation();
910
936
  // Real-time playback timing on the same mounted learner preview: the
911
937
  // clock plays from the recipe's start and every animation frame interval
@@ -913,6 +939,8 @@ export class StudioBrowser {
913
939
  let playback;
914
940
  const seconds = Math.min(30, Math.max(0, request.measurePlaybackSeconds ?? 0));
915
941
  if (seconds > 0) {
942
+ throwIfCancelled(request.signal);
943
+ request.onProgress?.({ stage: "playback_measure" });
916
944
  // Video time only moves forward; real-time playback seeks instead.
917
945
  await command({ op: "reset" });
918
946
  await command({ op: "seek", value: recipe.start });
@@ -965,17 +993,19 @@ export class StudioBrowser {
965
993
  */
966
994
  async renderExperienceFrames(request) {
967
995
  const { recipe } = request;
996
+ request.onProgress?.({ stage: "mount" });
968
997
  await this.driveExperience({ ...request, viewport: `${recipe.width}x${recipe.height}`, present: true,
969
998
  deviceScaleFactor: 1, scenario: { version: 1, steps: [] }, out: path.join(request.audioDirectory, "..", "mounted.png") });
970
999
  const page = this.requirePage();
971
- const { observation, command } = this.renderChannel(page);
1000
+ const phases = new PhaseTimes();
1001
+ const { observation, command } = this.renderChannel(page, phases);
972
1002
  const first = await observation();
973
1003
  await command({ op: "renderBegin" });
974
1004
  await this.waitForRender(page, (value) => value.mounted === true && value.stage_ready === true, "the Experience stage to paint");
975
1005
  await command({ op: "step", value: recipe.start });
976
1006
  const scenes = [];
977
1007
  let lastScene;
978
- const frames = await this.captureFrames(page, request, async (time, index) => {
1008
+ const frames = await this.captureFrames(page, { ...request, phases }, async (time, index) => {
979
1009
  const reached = await this.waitForRender(page, (value) => value.stage_ready === true, "the Experience stage to paint");
980
1010
  if (typeof reached.render_video_ms !== "number" || Math.abs(reached.render_video_ms - time * 1000) > 1) {
981
1011
  throw new CliError("The Experience did not stand at the requested video time.", "VIDEO_CLOCK_DRIFT", 1, { expected_ms: time * 1000, observed_ms: reached.render_video_ms });
@@ -985,8 +1015,9 @@ export class StudioBrowser {
985
1015
  lastScene = reached.active_scene;
986
1016
  return reached;
987
1017
  }, command);
1018
+ request.onProgress?.({ stage: "audio_collect" });
988
1019
  const log = await command({ op: "renderLog" });
989
- const audio = await this.collectAudio(log.audio ?? [], request.audioDirectory, recipe, async (event) => (await command({ op: "audioBytes", target: event.id, ...(event.scene ? { scene: event.scene } : {}) }, 30_000)).bytes);
1020
+ const audio = await this.collectAudio(log.audio ?? [], request.audioDirectory, recipe, request.signal, async (event) => (await command({ op: "audioBytes", target: event.id, ...(event.scene ? { scene: event.scene } : {}) }, 30_000)).bytes);
990
1021
  const final = await observation();
991
1022
  return {
992
1023
  ...frames,
@@ -1006,7 +1037,7 @@ export class StudioBrowser {
1006
1037
  return this.page;
1007
1038
  }
1008
1039
  /** The page's runtime observation and its one-at-a-time drive channel. */
1009
- renderChannel(page) {
1040
+ renderChannel(page, phases) {
1010
1041
  const observation = async () => page.evaluate(() => {
1011
1042
  const raw = document.getElementById("kheleshikhi-studio-runtime-observation")?.textContent;
1012
1043
  return raw ? JSON.parse(raw) : {};
@@ -1014,6 +1045,7 @@ export class StudioBrowser {
1014
1045
  let sequence = 0;
1015
1046
  const command = async (payload, timeoutMs = 10_000) => {
1016
1047
  const id = `render-${sequence += 1}`;
1048
+ const sent = performance.now();
1017
1049
  await page.evaluate(({ id, payload }) => {
1018
1050
  let node = document.getElementById("kheleshikhi-studio-drive-command");
1019
1051
  if (!node) {
@@ -1023,7 +1055,12 @@ export class StudioBrowser {
1023
1055
  document.body.append(node);
1024
1056
  }
1025
1057
  node.textContent = JSON.stringify({ id, ...payload });
1058
+ // Wake the host now; a host without the listener still finds the
1059
+ // command on its next 150 ms poll.
1060
+ document.dispatchEvent(new Event("kheleshikhi-studio-drive-command"));
1026
1061
  }, { id, payload });
1062
+ const delivered = performance.now();
1063
+ phases?.add("command_send", delivered - sent);
1027
1064
  const reply = await page.waitForFunction((expected) => {
1028
1065
  const raw = document.getElementById("kheleshikhi-studio-drive-result")?.textContent;
1029
1066
  if (!raw)
@@ -1033,25 +1070,34 @@ export class StudioBrowser {
1033
1070
  }, id, { timeout: timeoutMs }).then((handle) => handle.jsonValue()).catch(() => {
1034
1071
  throw new CliError("The presentation host did not acknowledge a render step.", "VIDEO_STEP_TIMEOUT", 1, { payload: { ...payload, bytes: undefined } });
1035
1072
  });
1073
+ phases?.add("command_ack_wait", performance.now() - delivered);
1036
1074
  if (reply.ok !== true)
1037
1075
  throw new CliError(String(reply.error || "Render step failed."), "VIDEO_STEP_FAILED", 1, { payload, reply });
1038
1076
  return reply;
1039
1077
  };
1040
1078
  return { observation, command };
1041
1079
  }
1042
- /** The newest observation once [ready] holds (the page publishes every 150 ms). */
1080
+ /**
1081
+ * The newest observation once [ready] holds. The page publishes one every
1082
+ * 150 ms and after every command; while not ready, each read also asks the
1083
+ * host for a fresh one (the drive-command wake event, with no command
1084
+ * waiting, only republishes), so a paint that lands between polls is seen
1085
+ * within milliseconds.
1086
+ */
1043
1087
  async waitForRender(page, ready, what, timeoutMs = 20_000) {
1044
1088
  const deadline = Date.now() + timeoutMs;
1045
- for (;;) {
1046
- const value = await page.evaluate(() => {
1089
+ for (let attempt = 0;; attempt += 1) {
1090
+ const value = await page.evaluate((wake) => {
1047
1091
  const raw = document.getElementById("kheleshikhi-studio-runtime-observation")?.textContent;
1092
+ if (wake)
1093
+ document.dispatchEvent(new Event("kheleshikhi-studio-drive-command"));
1048
1094
  return raw ? JSON.parse(raw) : {};
1049
- });
1095
+ }, attempt > 0);
1050
1096
  if (ready(value))
1051
1097
  return value;
1052
1098
  if (Date.now() > deadline)
1053
1099
  throw new CliError(`Timed out waiting for ${what}.`, "VIDEO_STAGE_NOT_READY", 1, { observed: value });
1054
- await page.waitForTimeout(40);
1100
+ await page.waitForTimeout(attempt === 0 ? 0 : 10);
1055
1101
  }
1056
1102
  }
1057
1103
  /**
@@ -1061,6 +1107,8 @@ export class StudioBrowser {
1061
1107
  */
1062
1108
  async captureFrames(page, request, arrived, command) {
1063
1109
  const { recipe } = request;
1110
+ const phases = request.phases ?? new PhaseTimes();
1111
+ throwIfCancelled(request.signal);
1064
1112
  await mkdir(request.framesDirectory, { recursive: true });
1065
1113
  const viewport = await page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight, dpr: window.devicePixelRatio }));
1066
1114
  if (viewport.width !== recipe.width || viewport.height !== recipe.height || viewport.dpr !== 1) {
@@ -1074,29 +1122,41 @@ export class StudioBrowser {
1074
1122
  let previous = recipe.start - 1;
1075
1123
  let lastShot;
1076
1124
  const started = Date.now();
1125
+ phases.active = true;
1077
1126
  for (let index = 0; index < total; index += 1) {
1127
+ const frameStarted = performance.now();
1128
+ // A cancelled job (client gone, Ctrl-C, deadline) stops here, between frames.
1129
+ throwIfCancelled(request.signal);
1130
+ request.onProgress?.({ stage: "capture", frame: index, frames: total });
1078
1131
  const time = frameTime(recipe, index);
1079
1132
  for (const action of actionsBetween(recipe, previous, time)) {
1080
- await command({ op: "step", value: action.at });
1081
- await command({ op: action.op, ...(action.target !== undefined ? { target: action.target } : {}), ...(action.value !== undefined ? { value: action.value } : {}) });
1133
+ await phases.time("actions", async () => {
1134
+ await command({ op: "step", value: action.at });
1135
+ await command({ op: action.op, ...(action.target !== undefined ? { target: action.target } : {}), ...(action.value !== undefined ? { value: action.value } : {}) });
1136
+ });
1082
1137
  performed.push({ at: action.at, op: action.op, ...(action.target !== undefined ? { target: action.target } : {}), frame: index });
1083
1138
  }
1084
- await command({ op: "step", value: time });
1085
- const reached = await arrived(time, index);
1086
- await waitForCapturePaint(page);
1087
- const png = await page.screenshot({ type: "png", scale: "css", clip });
1139
+ await phases.time("step", () => command({ op: "step", value: time }));
1140
+ const reached = await phases.time("arrival", () => arrived(time, index));
1141
+ await phases.time("paint", () => waitForCapturePaint(page));
1142
+ const png = await phases.time("screenshot", () => captureViewportPng(page, clip));
1143
+ const writeStarted = performance.now();
1088
1144
  const size = pngSize(png);
1089
1145
  if (size.width !== recipe.width || size.height !== recipe.height) {
1090
1146
  throw new CliError(`Frame ${index} is ${size.width}x${size.height}, not ${recipe.width}x${recipe.height}.`, "VIDEO_FRAME_SIZE_MISMATCH", 1, { frame: index, size });
1091
1147
  }
1092
1148
  hashes.push(sha256(png));
1093
1149
  await writeFile(path.join(request.framesDirectory, `${String(index).padStart(6, "0")}.png`), png);
1150
+ phases.add("hash_and_write", performance.now() - writeStarted);
1151
+ phases.add("frame_total", performance.now() - frameStarted);
1094
1152
  const shot = reached.learner_camera?.shot ?? null;
1095
1153
  if (index === 0 || shot !== lastShot)
1096
1154
  shots.push({ frame: index, at: time, shot });
1097
1155
  lastShot = shot;
1098
1156
  previous = time;
1099
1157
  }
1158
+ phases.active = false;
1159
+ request.onProgress?.({ stage: "capture", frame: total, frames: total });
1100
1160
  return {
1101
1161
  frames: total,
1102
1162
  frame_size: { width: recipe.width, height: recipe.height, device_scale_factor: 1, verified_frames: total },
@@ -1106,10 +1166,11 @@ export class StudioBrowser {
1106
1166
  camera_shots: shots,
1107
1167
  actions: performed,
1108
1168
  capture_seconds: (Date.now() - started) / 1000,
1169
+ capture_phases: phases.summary(total),
1109
1170
  };
1110
1171
  }
1111
1172
  /** Every logged sound's canonical bytes, written once by key; checked against the logged SHA-256. */
1112
- async collectAudio(events, directory, recipe, bytesOf) {
1173
+ async collectAudio(events, directory, recipe, signal, bytesOf) {
1113
1174
  const sources = {};
1114
1175
  if (recipe.audio === "scene") {
1115
1176
  await mkdir(directory, { recursive: true });
@@ -1117,6 +1178,7 @@ export class StudioBrowser {
1117
1178
  const key = audioKey(event);
1118
1179
  if (event.action !== "play" || sources[key])
1119
1180
  continue;
1181
+ throwIfCancelled(signal);
1120
1182
  if (Object.keys(sources).length >= 32)
1121
1183
  throw new CliError("A render mixes at most 32 distinct sounds.", "VIDEO_AUDIO_TOO_MANY", 2);
1122
1184
  const data = Buffer.from(await bytesOf(event), "base64");