omnius 1.0.867 → 1.0.868

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 (27) hide show
  1. package/dist/api/py-embed.js +13 -0
  2. package/dist/index.js +2 -2
  3. package/dist/library.js +220 -194
  4. package/dist/tui/native/worker.js +857 -143
  5. package/dist/update-worker.js +128 -77
  6. package/docs/DISCOVERY.json +1069 -139
  7. package/docs/DISCOVERY.md +26 -7
  8. package/docs/design/media-unification-TODO.md +26 -6
  9. package/docs/design/media-unification-design.md +89 -0
  10. package/docs/design/update-operation-cancellation.md +40 -0
  11. package/docs/design/update-session-restoration-TODO.md +30 -0
  12. package/docs/work-orders/evidence/instrument-media-live-20261016/media-audio-playing.png +0 -0
  13. package/docs/work-orders/evidence/instrument-media-live-20261016/media-image-100x30.png +0 -0
  14. package/docs/work-orders/evidence/instrument-media-live-20261016/media-image-120x36.png +0 -0
  15. package/docs/work-orders/evidence/instrument-media-live-20261016/media-image-160x48.png +0 -0
  16. package/docs/work-orders/evidence/instrument-media-live-20261016/media-image-60x20.png +0 -0
  17. package/docs/work-orders/evidence/instrument-media-live-20261016/media-image-80x24.png +0 -0
  18. package/docs/work-orders/evidence/instrument-media-live-20261016/media-progress-collapsed.png +0 -0
  19. package/docs/work-orders/evidence/instrument-media-live-20261016/media-video-frame-a.png +0 -0
  20. package/docs/work-orders/evidence/instrument-media-live-20261016/media-video-frame-b.png +0 -0
  21. package/docs/work-orders/evidence/instrument-media-live-20261016/media-voice-autocomplete-junction.png +0 -0
  22. package/docs/work-orders/evidence/instrument-media-live-20261016/report.json +813 -0
  23. package/docs/work-orders/evidence/instrument-media-verification-20261016.json +59 -0
  24. package/docs/work-orders/evidence/native-interface-state-verification-20261016.json +59 -0
  25. package/docs/work-orders/evidence/project-media-unification-20261016.json +108 -0
  26. package/npm-shrinkwrap.json +2 -2
  27. package/package.json +1 -1
@@ -6,29 +6,70 @@ import { CliRenderEvents as CliRenderEvents7, NativeSession, RGBA as RGBA3, crea
6
6
 
7
7
  // packages/cli/src/tui/native/components/frontend.ts
8
8
  import {
9
- BoxRenderable as BoxRenderable7,
9
+ BoxRenderable as BoxRenderable8,
10
10
  CliRenderEvents as CliRenderEvents5,
11
11
  EmbeddedTerminalRenderable as EmbeddedTerminalRenderable2,
12
12
  ScrollBoxRenderable as ScrollBoxRenderable5,
13
13
  SyntaxStyle as SyntaxStyle2,
14
14
  TextareaRenderable as TextareaRenderable2,
15
- TextRenderable as TextRenderable6,
15
+ TextRenderable as TextRenderable7,
16
16
  TextAttributes as TextAttributes8,
17
17
  createClipboard,
18
18
  createHostClipboard,
19
19
  createRendererClipboardAdapter
20
20
  } from "@opentui/core";
21
21
 
22
+ // packages/cli/src/tui/native/interface-state.ts
23
+ var record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
24
+ var offset = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(1e7, Math.floor(value))) : 0;
25
+ function sanitizeNativeInterfaceState(value) {
26
+ if (!record(value) || value.version !== 1 || typeof value.activeViewId !== "string" || !record(value.input)) return void 0;
27
+ if (typeof value.input.text !== "string") return void 0;
28
+ const text = value.input.text.slice(0, 1e6), views = {};
29
+ if (record(value.views)) for (const [key, entry] of Object.entries(value.views).slice(0, 256)) {
30
+ if (key.length > 1024 || !record(entry)) continue;
31
+ const disclosures = {};
32
+ if (record(entry.disclosures)) for (const [id, collapsed] of Object.entries(entry.disclosures).slice(0, 1e4)) {
33
+ if (id.length <= 1024 && typeof collapsed === "boolean") Object.defineProperty(disclosures, id, { value: collapsed, enumerable: true, configurable: true, writable: true });
34
+ }
35
+ Object.defineProperty(views, key, { value: { top: offset(entry.top), live: entry.live === true, disclosures }, enumerable: true, configurable: true, writable: true });
36
+ }
37
+ const layout = record(value.layout) ? value.layout : {}, instruments = record(value.instruments) ? value.instruments : {}, tabs = record(value.tabs) ? value.tabs : {};
38
+ return {
39
+ version: 1,
40
+ ...typeof value.sessionId === "string" ? { sessionId: value.sessionId.slice(0, 1024) } : {},
41
+ activeViewId: value.activeViewId.slice(0, 1024),
42
+ presentation: value.presentation === "tree" ? "tree" : "stack",
43
+ input: { text, cursor: Math.min(text.length, offset(value.input.cursor)) },
44
+ views,
45
+ layout: {
46
+ ...typeof layout.sidebarWidth === "number" && Number.isFinite(layout.sidebarWidth) ? { sidebarWidth: Math.max(24, Math.min(1e4, Math.floor(layout.sidebarWidth))) } : {},
47
+ ...typeof layout.detailsOpen === "boolean" ? { detailsOpen: layout.detailsOpen } : {},
48
+ narrowDetailsOpen: layout.narrowDetailsOpen === true,
49
+ headerPanelCollapsed: layout.headerPanelCollapsed === true
50
+ },
51
+ instruments: {
52
+ inference: instruments.inference !== false,
53
+ session: instruments.session === true,
54
+ context: instruments.context === true,
55
+ devices: instruments.devices === true,
56
+ scrollTop: offset(instruments.scrollTop),
57
+ deviceScrollTop: offset(instruments.deviceScrollTop)
58
+ },
59
+ tabs: { sessions: offset(tabs.sessions), agents: offset(tabs.agents) }
60
+ };
61
+ }
62
+
22
63
  // packages/cli/src/tui/native/components/blocks.ts
23
64
  import {
24
- BoxRenderable,
65
+ BoxRenderable as BoxRenderable2,
25
66
  CodeRenderable,
26
67
  EmbeddedTerminalRenderable,
27
- ImageRenderable,
68
+ ImageRenderable as ImageRenderable2,
28
69
  MarkdownRenderable,
29
70
  RenderableEvents,
30
71
  ScrollBoxRenderable,
31
- TextRenderable
72
+ TextRenderable as TextRenderable2
32
73
  } from "@opentui/core";
33
74
 
34
75
  // packages/cli/src/tui/native/components/text.ts
@@ -170,6 +211,505 @@ function installDefaultSelectionContrast(renderer2) {
170
211
  return dispose;
171
212
  }
172
213
 
214
+ // packages/cli/src/tui/native/components/media.ts
215
+ import { execFile as execFile2 } from "node:child_process";
216
+ import { BoxRenderable, ImageRenderable, NativeImagePool, TextRenderable } from "@opentui/core";
217
+
218
+ // packages/cli/src/tui/native/components/media-playback.ts
219
+ import { spawn, execFile } from "node:child_process";
220
+ import { realpath, lstat } from "node:fs/promises";
221
+ import { extname, relative, resolve } from "node:path";
222
+ import { Audio } from "@opentui/core";
223
+ async function validateMediaFile(path, root) {
224
+ const info = await lstat(path);
225
+ if (!info.isFile() || info.isSymbolicLink()) throw new Error("Media must be a regular local file");
226
+ const canonical = await realpath(path), project = await realpath(root);
227
+ const local = relative(project, canonical);
228
+ if (local === ".." || local.startsWith("../") || resolve(project, local) !== canonical) throw new Error("Media is outside its project");
229
+ if (!/\.(png|jpe?g|webp|gif|mp4|webm|mov|mkv|wav|mp3|flac|ogg|m4a)$/i.test(extname(canonical))) throw new Error("Unsupported media file");
230
+ return canonical;
231
+ }
232
+ async function probeMedia(spec, signal) {
233
+ const path = await validateMediaFile(spec.path, spec.projectRoot);
234
+ const output = await new Promise((done, fail2) => execFile("ffprobe", ["-v", "error", "-show_entries", "stream=codec_type,width,height:format=duration", "-of", "json", path], { signal, timeout: 5e3, maxBuffer: 128e3 }, (error, stdout) => error ? fail2(error) : done(stdout)));
235
+ const data = JSON.parse(output), video = data.streams?.find((stream) => stream.codec_type === "video");
236
+ const durationSec = Number(data.format?.duration);
237
+ return { ...video?.width > 0 ? { width: video.width, height: video.height } : {}, ...Number.isFinite(durationSec) && durationSec > 0 ? { durationSec } : {}, hasAudio: data.streams?.some((stream) => stream.codec_type === "audio") };
238
+ }
239
+ var NativeMediaPlayback = class {
240
+ constructor(changed, silent = process.env["OMNIUS_MEDIA_TEST_SILENT"] === "1") {
241
+ this.changed = changed;
242
+ this.silent = silent;
243
+ }
244
+ changed;
245
+ silent;
246
+ positionSec = 0;
247
+ playing = false;
248
+ process;
249
+ controller;
250
+ audio;
251
+ stream;
252
+ ticker;
253
+ epoch = 0;
254
+ audioOffset = 0;
255
+ cleanup = Promise.resolve();
256
+ get closed() {
257
+ return this.cleanup;
258
+ }
259
+ async play(spec, size, frame) {
260
+ this.pause();
261
+ const epoch = ++this.epoch;
262
+ const controller = this.controller = new AbortController();
263
+ try {
264
+ await this.cleanup;
265
+ if (epoch !== this.epoch) return;
266
+ const path = await validateMediaFile(spec.path, spec.projectRoot);
267
+ if (epoch !== this.epoch) return;
268
+ const width = Math.max(2, Math.min(640, Math.floor(size.width / 2) * 2)), height = Math.max(2, Math.min(360, Math.floor(size.height / 2) * 2));
269
+ const info = spec.kind === "video" ? await probeMedia(spec, controller.signal) : {};
270
+ if (epoch !== this.epoch) return;
271
+ const input = ["-nostdin", "-loglevel", "error", "-hwaccel", "none", "-ss", String(this.positionSec), ...spec.kind === "video" ? ["-re"] : [], "-i", path];
272
+ const output = spec.kind === "video" ? ["-an", "-vf", `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2,fps=8`, "-pix_fmt", "rgba", "-f", "rawvideo", "pipe:1"] : ["-vn", "-f", "f32le", "-ac", "2", "-ar", "48000", "pipe:1"];
273
+ const withAudio = spec.kind === "video" && info.hasAudio;
274
+ if (withAudio) output.push("-map", "0:a:0", "-vn", "-f", "f32le", "-ac", "2", "-ar", "48000", "pipe:3");
275
+ const child = this.process = spawn("ffmpeg", [...input, ...output], { stdio: withAudio ? ["pipe", "pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe"], windowsHide: true });
276
+ child.stdin.end();
277
+ let diagnostic = "";
278
+ child.stderr.on("data", (chunk) => {
279
+ diagnostic = (diagnostic + chunk.toString()).slice(-2e3);
280
+ });
281
+ const closed = new Promise((done, fail2) => {
282
+ child.once("error", fail2);
283
+ child.once("close", done);
284
+ });
285
+ this.cleanup = closed.catch(() => {
286
+ });
287
+ this.playing = true;
288
+ this.changed(spec.kind === "video" && !withAudio ? "Playing · no audio track" : "Playing");
289
+ const audioWork = withAudio ? this.consumeAudio(child.stdio[3], controller, epoch) : void 0;
290
+ if (audioWork) void audioWork.catch((error) => {
291
+ if (epoch === this.epoch) {
292
+ this.pause();
293
+ this.changed(error.message);
294
+ }
295
+ });
296
+ if (spec.kind === "video") {
297
+ const bytes = width * height * 4;
298
+ let pending = Buffer.alloc(0), frames = 0;
299
+ const offset2 = this.positionSec;
300
+ for await (const chunk of child.stdout) {
301
+ if (epoch !== this.epoch) break;
302
+ pending = Buffer.concat([pending, chunk]);
303
+ while (pending.length >= bytes && epoch === this.epoch) {
304
+ frame(pending.subarray(0, bytes), width, height);
305
+ pending = pending.subarray(bytes);
306
+ frames++;
307
+ if (!withAudio) this.positionSec = offset2 + frames / 8;
308
+ this.changed();
309
+ }
310
+ }
311
+ } else await this.consumeAudio(child.stdout, controller, epoch);
312
+ if (audioWork) await audioWork;
313
+ const code = await closed;
314
+ if (epoch !== this.epoch) return;
315
+ if (code !== 0) throw new Error(diagnostic.trim() || "Media decoder failed");
316
+ this.pause();
317
+ this.positionSec = 0;
318
+ this.changed("Playback complete");
319
+ } catch (error) {
320
+ if (epoch !== this.epoch) return;
321
+ this.pause();
322
+ this.changed(error instanceof Error ? error.message : String(error));
323
+ }
324
+ }
325
+ async consumeAudio(source, controller, epoch) {
326
+ const audio = this.audio = Audio.create({ autoStart: false, sampleRate: 48e3 });
327
+ audio.on("error", (error) => {
328
+ if (epoch === this.epoch) {
329
+ this.pause();
330
+ this.changed(error.message);
331
+ }
332
+ });
333
+ if (!(this.silent ? audio.startMixer() : audio.start())) throw new Error("Audio output is unavailable");
334
+ this.audioOffset = this.positionSec;
335
+ const stream = this.stream = await audio.playStream(source, { format: "pcm", sampleFormat: "f32le", channels: 2, sampleRate: 48e3, buffer: { capacityMs: 500, startupMs: 50, resumeMs: 50 }, signal: controller.signal });
336
+ if (epoch !== this.epoch) {
337
+ stream.dispose();
338
+ return;
339
+ }
340
+ stream.on("error", (error) => {
341
+ if (epoch === this.epoch) {
342
+ this.pause();
343
+ this.changed(error.message);
344
+ }
345
+ });
346
+ this.ticker = setInterval(() => {
347
+ if (this.silent) audio.mixFrames(480, 2);
348
+ const stats = stream.getStats();
349
+ this.positionSec = this.audioOffset + Number(stats.framesPlayed) / stats.sampleRate;
350
+ this.changed();
351
+ }, this.silent ? 10 : 100);
352
+ await stream.closed;
353
+ }
354
+ pause() {
355
+ ++this.epoch;
356
+ if (this.stream) {
357
+ try {
358
+ const stats = this.stream.getStats();
359
+ this.positionSec = this.audioOffset + Number(stats.framesPlayed) / stats.sampleRate;
360
+ } catch {
361
+ }
362
+ }
363
+ this.controller?.abort();
364
+ this.controller = void 0;
365
+ this.stream?.dispose();
366
+ this.stream = void 0;
367
+ this.audio?.dispose();
368
+ this.audio = void 0;
369
+ if (this.ticker) clearInterval(this.ticker);
370
+ this.ticker = void 0;
371
+ const child = this.process;
372
+ this.process = void 0;
373
+ if (child && child.exitCode === null) {
374
+ child.kill("SIGTERM");
375
+ const timer = setTimeout(() => {
376
+ if (child.exitCode === null) child.kill("SIGKILL");
377
+ }, 250);
378
+ timer.unref?.();
379
+ child.once("close", () => clearTimeout(timer));
380
+ }
381
+ const wasPlaying = this.playing;
382
+ this.playing = false;
383
+ if (wasPlaying) this.changed("Paused");
384
+ }
385
+ dispose() {
386
+ this.pause();
387
+ }
388
+ };
389
+
390
+ // packages/cli/src/tui/native/components/media.ts
391
+ function mediaPreviewHeight(width, imageWidth, imageHeight, cellAspectRatio = 2) {
392
+ if (![width, imageWidth, imageHeight, cellAspectRatio].every((value) => Number.isFinite(value) && value > 0)) return 8;
393
+ return Math.max(1, Math.min(80, Math.round(width * imageHeight / imageWidth / cellAspectRatio)));
394
+ }
395
+ function audioWaveform(samples, width) {
396
+ const cells = Math.max(1, Math.min(240, Math.floor(width)));
397
+ const glyphs = "▁▂▃▄▅▆▇█";
398
+ return Array.from({ length: cells }, (_, column) => {
399
+ const begin = Math.floor(column * samples.length / cells), end = Math.max(begin + 1, Math.floor((column + 1) * samples.length / cells));
400
+ let peak = 0;
401
+ for (let index = begin; index < end && index < samples.length; index++) if (Number.isFinite(samples[index])) peak = Math.max(peak, Math.abs(samples[index]));
402
+ return glyphs[Math.min(7, Math.floor(Math.min(1, peak) * 7))];
403
+ }).join("");
404
+ }
405
+ var time = (seconds) => `${Math.floor(Math.max(0, seconds) / 60)}:${String(Math.floor(Math.max(0, seconds) % 60)).padStart(2, "0")}`;
406
+ var NativeMediaView = class extends BoxRenderable {
407
+ constructor(renderer2, mediaId, spec, colors, sendEvent) {
408
+ super(renderer2, { id: `media:${mediaId}`, width: "100%", flexDirection: "column", flexShrink: 0 });
409
+ this.renderer = renderer2;
410
+ this.mediaId = mediaId;
411
+ this.colors = colors;
412
+ this.sendEvent = sendEvent;
413
+ this.spec = spec;
414
+ this.location = new TextRenderable(renderer2, { id: `media-location:${mediaId}`, content: spec.path, width: "100%", wrapMode: "word", selectable: true, fg: colors.dim });
415
+ this.status = new TextRenderable(renderer2, { id: `media-status:${mediaId}`, content: "", width: "100%", wrapMode: "word", selectable: true, fg: colors.dim });
416
+ this.wave = new TextRenderable(renderer2, { id: `media-waveform:${mediaId}`, content: "", width: "100%", height: 1, wrapMode: "none", fg: colors.foreground, visible: false });
417
+ this.playheadLabel = new TextRenderable(renderer2, { id: `media-position:${mediaId}`, content: "", width: "100%", height: 1, wrapMode: "none", fg: colors.dim, visible: false });
418
+ this.controls = new BoxRenderable(renderer2, { id: `media-controls:${mediaId}`, flexDirection: "row", gap: 2, flexShrink: 0 });
419
+ this.playControl = this.control("play", "Play", () => this.togglePlayback());
420
+ this.controls.add(this.playControl);
421
+ this.controls.add(this.control("open", "Open ↗", () => {
422
+ void this.open();
423
+ }));
424
+ this.playback = new NativeMediaPlayback((message) => {
425
+ if (this.isDestroyed) return;
426
+ if (message) this.currentStatus = message;
427
+ this.refreshStatus();
428
+ });
429
+ this.add(this.location);
430
+ this.add(this.wave);
431
+ this.add(this.playheadLabel);
432
+ this.add(this.status);
433
+ this.add(this.controls);
434
+ this.update(spec);
435
+ }
436
+ renderer;
437
+ mediaId;
438
+ colors;
439
+ sendEvent;
440
+ spec;
441
+ location;
442
+ status;
443
+ wave;
444
+ playheadLabel;
445
+ controls;
446
+ playControl;
447
+ image;
448
+ pool;
449
+ playback;
450
+ probeController;
451
+ samples;
452
+ waveformText = "";
453
+ sourceWidth = 16;
454
+ sourceHeight = 9;
455
+ duration = 0;
456
+ hovered = false;
457
+ lastWidth = 0;
458
+ currentStatus = "";
459
+ sourceEpoch = 0;
460
+ needsProbe = true;
461
+ get closed() {
462
+ return this.playback.closed;
463
+ }
464
+ pausePlayback() {
465
+ this.playback.pause();
466
+ }
467
+ control(action, label, operation) {
468
+ const control = new TextRenderable(this.renderer, {
469
+ id: `media-control:${this.mediaId}:${action}`,
470
+ content: label,
471
+ fg: this.colors.foreground,
472
+ selectable: false,
473
+ onMouseDown: (event) => {
474
+ if (event.button === 0) {
475
+ operation();
476
+ event.stopPropagation();
477
+ }
478
+ },
479
+ onKeyDown: (event) => {
480
+ if (event.eventType !== "release" && (event.name === "return" || event.sequence === " ")) {
481
+ operation();
482
+ event.preventDefault();
483
+ event.stopPropagation();
484
+ }
485
+ }
486
+ });
487
+ control.focusable = true;
488
+ control.on("focused", () => {
489
+ control.attributes = 32;
490
+ });
491
+ control.on("blurred", () => {
492
+ control.attributes = 0;
493
+ });
494
+ return control;
495
+ }
496
+ update(spec) {
497
+ const changed = spec.path !== this.spec.path || spec.kind !== this.spec.kind;
498
+ this.spec = spec;
499
+ this.location.content = spec.path;
500
+ this.playControl.visible = spec.kind !== "image";
501
+ if (changed) {
502
+ this.playback.pause();
503
+ this.playback.positionSec = 0;
504
+ this.probeController?.abort();
505
+ this.probeController = void 0;
506
+ this.needsProbe = true;
507
+ this.pool?.dispose();
508
+ this.pool = void 0;
509
+ this.samples = void 0;
510
+ this.sourceEpoch++;
511
+ }
512
+ if ((spec.kind === "image" || spec.kind === "video") && !this.image) {
513
+ this.image = new ImageRenderable(this.renderer, {
514
+ id: `media-preview:${this.mediaId}`,
515
+ width: "100%",
516
+ height: 8,
517
+ fit: "fit",
518
+ protocol: "auto",
519
+ onLoad: (image) => {
520
+ if (this.spec.kind === "image") {
521
+ this.sourceWidth = image.width;
522
+ this.sourceHeight = image.height;
523
+ }
524
+ this.refreshGeometry();
525
+ },
526
+ onError: () => {
527
+ this.currentStatus = "Preview unavailable · use Open";
528
+ this.refreshStatus();
529
+ },
530
+ onMouseOver: () => {
531
+ this.hovered = true;
532
+ this.refreshStatus();
533
+ },
534
+ onMouseOut: () => {
535
+ this.hovered = false;
536
+ this.refreshStatus();
537
+ },
538
+ onMouseUp: (event) => {
539
+ if (event.button === 0 && !event.isDragging) {
540
+ void this.open();
541
+ event.stopPropagation();
542
+ }
543
+ },
544
+ onKeyDown: (event) => {
545
+ if (event.eventType !== "release" && event.name === "return") {
546
+ void this.open();
547
+ event.preventDefault();
548
+ event.stopPropagation();
549
+ }
550
+ }
551
+ });
552
+ this.image.focusable = true;
553
+ this.add(this.image, 1);
554
+ }
555
+ if (this.image) {
556
+ this.image.visible = spec.kind === "image" || spec.kind === "video";
557
+ const source = spec.kind === "image" ? spec.path : spec.previewPath;
558
+ if (source && this.image.source !== source && !this.playback.playing) {
559
+ const epoch = this.sourceEpoch;
560
+ void validateMediaFile(source, spec.projectRoot).then((path) => {
561
+ if (!this.isDestroyed && epoch === this.sourceEpoch && !this.playback.playing && this.image && this.image.source !== path) this.image.source = path;
562
+ }).catch((error) => {
563
+ if (!this.isDestroyed) {
564
+ this.currentStatus = `Preview unavailable · ${error.message}`;
565
+ this.refreshStatus();
566
+ }
567
+ });
568
+ }
569
+ }
570
+ this.wave.visible = spec.kind === "audio" || spec.kind === "music";
571
+ this.playheadLabel.visible = spec.kind !== "image";
572
+ this.refreshStatus();
573
+ }
574
+ startProbe() {
575
+ if (this.needsProbe && !this.probeController) {
576
+ this.needsProbe = false;
577
+ const spec = this.spec;
578
+ const controller = this.probeController = new AbortController(), epoch = this.sourceEpoch;
579
+ if (spec.kind !== "image") void probeMedia(spec, controller.signal).then((info) => {
580
+ if (controller.signal.aborted || epoch !== this.sourceEpoch || this.isDestroyed) return;
581
+ this.sourceWidth = info.width || 16;
582
+ this.sourceHeight = info.height || 9;
583
+ this.duration = info.durationSec || 0;
584
+ this.refreshGeometry();
585
+ if (spec.kind === "audio" || spec.kind === "music") this.loadWaveform(controller, epoch);
586
+ }).catch((error) => {
587
+ if (!controller.signal.aborted && !this.isDestroyed) {
588
+ this.currentStatus = `Media metadata unavailable · ${error.message}`;
589
+ this.refreshStatus();
590
+ }
591
+ });
592
+ }
593
+ this.refreshStatus();
594
+ }
595
+ loadWaveform(controller, epoch) {
596
+ execFile2("ffmpeg", ["-nostdin", "-loglevel", "error", "-hwaccel", "none", "-i", this.spec.path, "-t", "600", "-vn", "-ac", "1", "-ar", "1000", "-f", "f32le", "pipe:1"], { encoding: "buffer", signal: controller.signal, timeout: 1e4, maxBuffer: 25e5 }, (error, buffer) => {
597
+ if (controller.signal.aborted || epoch !== this.sourceEpoch || this.isDestroyed) return;
598
+ if (error) {
599
+ this.currentStatus = "Waveform unavailable · playback and Open remain available";
600
+ this.refreshStatus();
601
+ return;
602
+ }
603
+ const samples = new Float32Array(Math.floor(buffer.length / 4));
604
+ for (let index = 0; index < samples.length; index++) samples[index] = buffer.readFloatLE(index * 4);
605
+ this.samples = samples;
606
+ this.lastWidth = 0;
607
+ this.refreshGeometry();
608
+ });
609
+ }
610
+ reconcile(viewport, active = true) {
611
+ if (this.isDestroyed) return;
612
+ let ancestor = this;
613
+ while (ancestor && active) {
614
+ active = ancestor.visible;
615
+ ancestor = ancestor.parent;
616
+ }
617
+ const visible = active && this.y + this.height > viewport.y && this.y < viewport.y + viewport.height;
618
+ if (!visible && this.playback.playing) this.playback.pause();
619
+ if (!visible && this.probeController && !this.samples) {
620
+ this.probeController.abort();
621
+ this.probeController = void 0;
622
+ this.needsProbe = true;
623
+ }
624
+ if (visible) this.startProbe();
625
+ this.refreshGeometry();
626
+ }
627
+ refreshGeometry() {
628
+ if (this.isDestroyed) return;
629
+ const width = Math.max(1, this.width);
630
+ if (this.image) {
631
+ const height = mediaPreviewHeight(width, this.sourceWidth, this.sourceHeight, this.image.cellAspectRatio);
632
+ if (this.image.height !== height) this.image.height = height;
633
+ }
634
+ if (width !== this.lastWidth) {
635
+ this.lastWidth = width;
636
+ if (this.samples) this.waveformText = audioWaveform(this.samples, width);
637
+ }
638
+ this.refreshStatus();
639
+ }
640
+ refreshStatus() {
641
+ if (this.isDestroyed) return;
642
+ const text = `${this.hovered ? "Open ↗ · " : ""}${this.currentStatus}`;
643
+ if (this.status.plainText !== text) this.status.content = text;
644
+ const label = this.playback?.playing ? "Pause" : this.playback?.positionSec ? "Resume" : "Play";
645
+ if (this.playControl.plainText !== label) this.playControl.content = label;
646
+ if (this.waveformText) {
647
+ const cursor = this.duration > 0 ? Math.min(this.waveformText.length - 1, Math.floor(this.playback.positionSec / this.duration * this.waveformText.length)) : 0;
648
+ const waveform = this.playback.positionSec > 0 ? this.waveformText.slice(0, cursor) + "│" + this.waveformText.slice(cursor + 1) : this.waveformText;
649
+ if (this.wave.plainText !== waveform) this.wave.content = waveform;
650
+ }
651
+ const progress = `${time(this.playback?.positionSec ?? 0)} / ${this.duration ? time(this.duration) : "--:--"}${this.duration > 600 && this.samples ? " · waveform shows first 10 min" : ""}`;
652
+ if (this.playheadLabel.plainText !== progress) this.playheadLabel.content = progress;
653
+ }
654
+ togglePlayback() {
655
+ if (this.playback.playing) {
656
+ this.playback.pause();
657
+ return;
658
+ }
659
+ if (this.spec.kind === "image") return;
660
+ void this.playback.play(this.spec, { width: Math.max(2, this.width * 4), height: Math.max(2, (this.image?.height ?? 24) * 8) }, (rgba, width, height) => {
661
+ if (!this.image || this.isDestroyed) return;
662
+ if (!this.pool || this.pool.width !== width || this.pool.height !== height) {
663
+ this.pool?.dispose();
664
+ this.pool = new NativeImagePool({ width, height, capacity: 3, owner: this.renderer.nativeScene.resourceContext });
665
+ }
666
+ const frame = this.pool.publishRgba(rgba);
667
+ if (frame) {
668
+ this.image.source = frame;
669
+ frame.dispose();
670
+ }
671
+ });
672
+ }
673
+ async open() {
674
+ try {
675
+ const path = await validateMediaFile(this.spec.path, this.spec.projectRoot);
676
+ if (this.isDestroyed) return;
677
+ this.sendEvent({ type: "action", action: "media-open", id: this.mediaId, text: path });
678
+ if (process.env["OMNIUS_MEDIA_TEST_NO_OPEN"] === "1") {
679
+ this.currentStatus = "Open requested (test fixture)";
680
+ this.refreshStatus();
681
+ return;
682
+ }
683
+ const command2 = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer.exe" : "xdg-open";
684
+ execFile2(command2, [path], { timeout: 5e3 }, (error) => {
685
+ if (!this.isDestroyed) {
686
+ this.currentStatus = error ? `Open failed · ${error.message}` : "Opened in the system viewer";
687
+ this.refreshStatus();
688
+ }
689
+ });
690
+ } catch (error) {
691
+ if (!this.isDestroyed) {
692
+ this.currentStatus = `Open unavailable · ${error instanceof Error ? error.message : String(error)}`;
693
+ this.refreshStatus();
694
+ }
695
+ }
696
+ }
697
+ applyTheme(colors) {
698
+ this.colors = colors;
699
+ for (const view of [this.location, this.status, this.wave, this.playheadLabel, ...this.controls.getChildren()]) if (view instanceof TextRenderable) {
700
+ view.fg = view === this.wave ? colors.foreground : colors.dim;
701
+ view.bg = colors.background;
702
+ }
703
+ }
704
+ destroy() {
705
+ this.playback.dispose();
706
+ this.probeController?.abort();
707
+ this.pool?.dispose();
708
+ this.pool = void 0;
709
+ super.destroy();
710
+ }
711
+ };
712
+
173
713
  // packages/cli/src/tui/native/components/code.ts
174
714
  var aliases = {
175
715
  js: "javascript",
@@ -297,10 +837,10 @@ function nativeMarkdownSections(owner, source, streaming = false) {
297
837
  }
298
838
  ];
299
839
  const rows = source.split("\n"), offsets = [];
300
- let offset = 0;
840
+ let offset2 = 0;
301
841
  for (const row of rows) {
302
- offsets.push(offset);
303
- offset += row.length + 1;
842
+ offsets.push(offset2);
843
+ offset2 += row.length + 1;
304
844
  }
305
845
  const output = [], stack = [
306
846
  { level: 0, children: output }
@@ -400,7 +940,7 @@ var NativeBlockView = class {
400
940
  this.inline = inline;
401
941
  this.spec = spec;
402
942
  this.pixel = inline && spec.kind === "text" && Boolean(spec.actions?.length) && spec.actions.every((action) => action.id.startsWith("pixel:"));
403
- this.root = new BoxRenderable(context.renderer, {
943
+ this.root = new BoxRenderable2(context.renderer, {
404
944
  id: `block:${spec.id}`,
405
945
  flexDirection: "column",
406
946
  flexShrink: inline && !this.pixel ? 1 : 0,
@@ -424,6 +964,7 @@ var NativeBlockView = class {
424
964
  actions;
425
965
  progress;
426
966
  image;
967
+ media;
427
968
  terminal;
428
969
  collapsed = false;
429
970
  previousModelCollapsed;
@@ -443,7 +984,7 @@ var NativeBlockView = class {
443
984
  this.root.marginBottom = 0;
444
985
  this.root.flexDirection = spec.kind === "row" ? "row" : "column";
445
986
  if (spec.title) {
446
- this.title = new TextRenderable(renderer2, {
987
+ this.title = new TextRenderable2(renderer2, {
447
988
  id: `block-title:${spec.id}`,
448
989
  content: "",
449
990
  fg: colors.foreground,
@@ -467,7 +1008,7 @@ var NativeBlockView = class {
467
1008
  nativeControlFocus(this.title, 1);
468
1009
  this.root.add(this.title);
469
1010
  }
470
- this.body = new BoxRenderable(renderer2, {
1011
+ this.body = new BoxRenderable2(renderer2, {
471
1012
  id: `block-body:${spec.id}`,
472
1013
  flexDirection: spec.kind === "row" ? "row" : "column",
473
1014
  width: this.pixel ? "auto" : "100%",
@@ -487,8 +1028,12 @@ var NativeBlockView = class {
487
1028
  });
488
1029
  this.context.terminals.set(spec.id, this.terminal);
489
1030
  this.body.add(this.terminal);
1031
+ } else if (spec.kind === "media" && spec.media) {
1032
+ this.media = new NativeMediaView(renderer2, spec.id, spec.media, colors, this.context.emit);
1033
+ this.context.mediaViews?.add(this.media);
1034
+ this.body.add(this.media);
490
1035
  } else if (spec.kind === "image" && spec.imagePath) {
491
- this.image = new ImageRenderable(renderer2, {
1036
+ this.image = new ImageRenderable2(renderer2, {
492
1037
  id: `image:${spec.id}`,
493
1038
  source: spec.imagePath,
494
1039
  width: "100%",
@@ -499,7 +1044,7 @@ var NativeBlockView = class {
499
1044
  if (this.image) this.image.visible = false;
500
1045
  if (this.text) {
501
1046
  this.text.visible = true;
502
- if (this.text instanceof TextRenderable) this.text.content = styledText(this.spec.text ?? "Image unavailable");
1047
+ if (this.text instanceof TextRenderable2) this.text.content = styledText(this.spec.text ?? "Image unavailable");
503
1048
  }
504
1049
  this.context.emit({ type: "action", action: "image-error", id: spec.id, text: String(error) });
505
1050
  },
@@ -509,7 +1054,7 @@ var NativeBlockView = class {
509
1054
  }
510
1055
  });
511
1056
  this.body.add(this.image);
512
- this.text = new TextRenderable(renderer2, { id: `image-fallback:${spec.id}`, content: "", visible: false, width: "100%", wrapMode: "word", fg: colors.dim, selectable: true });
1057
+ this.text = new TextRenderable2(renderer2, { id: `image-fallback:${spec.id}`, content: "", visible: false, width: "100%", wrapMode: "word", fg: colors.dim, selectable: true });
513
1058
  this.body.add(this.text);
514
1059
  } else if (spec.kind === "markdown") {
515
1060
  this.text = new MarkdownRenderable(renderer2, {
@@ -561,7 +1106,7 @@ var NativeBlockView = class {
561
1106
  });
562
1107
  this.body.add(this.text);
563
1108
  } else if (spec.text !== void 0) {
564
- this.text = new TextRenderable(renderer2, {
1109
+ this.text = new TextRenderable2(renderer2, {
565
1110
  id: `text:${spec.id}`,
566
1111
  content: "",
567
1112
  width: this.pixel ? "auto" : "100%",
@@ -574,7 +1119,7 @@ var NativeBlockView = class {
574
1119
  this.body.add(this.text);
575
1120
  }
576
1121
  if (spec.kind === "progress") {
577
- this.progress = new TextRenderable(renderer2, {
1122
+ this.progress = new TextRenderable2(renderer2, {
578
1123
  id: `progress-track:${spec.id}`,
579
1124
  width: "100%",
580
1125
  height: 1,
@@ -588,7 +1133,7 @@ var NativeBlockView = class {
588
1133
  this.body.add(this.progress);
589
1134
  }
590
1135
  this.children = new NativeBlockList(this.context, this.body);
591
- this.actions = new BoxRenderable(renderer2, {
1136
+ this.actions = new BoxRenderable2(renderer2, {
592
1137
  id: `block-actions:${spec.id}`,
593
1138
  flexDirection: "row",
594
1139
  gap: 1,
@@ -597,6 +1142,11 @@ var NativeBlockView = class {
597
1142
  this.root.add(this.actions);
598
1143
  }
599
1144
  reset() {
1145
+ if (this.media) {
1146
+ this.context.mediaViews?.delete(this.media);
1147
+ this.media.destroy();
1148
+ this.media = void 0;
1149
+ }
600
1150
  this.children?.destroy();
601
1151
  this.markdownSections?.destroy();
602
1152
  this.markdownSections = void 0;
@@ -632,13 +1182,14 @@ var NativeBlockView = class {
632
1182
  this.markdownSections.destroy();
633
1183
  this.markdownSections = void 0;
634
1184
  }
635
- if (this.text instanceof TextRenderable) this.text.content = styledText(spec.kind === "image" ? spec.text ?? "Image unavailable" : spec.text);
1185
+ if (this.text instanceof TextRenderable2) this.text.content = styledText(spec.kind === "image" ? spec.text ?? "Image unavailable" : spec.text);
636
1186
  else if (this.text instanceof MarkdownRenderable || this.text instanceof CodeRenderable) {
637
1187
  const code = this.text instanceof CodeRenderable ? nativeCodeContent(plainText(spec.text), spec.language) : void 0;
638
1188
  this.text.content = sections ? "" : code?.content ?? plainText(spec.text);
639
1189
  this.text.streaming = spec.streaming ?? false;
640
1190
  if (this.text instanceof CodeRenderable) this.text.filetype = code?.language ?? "text";
641
1191
  }
1192
+ if (this.media && spec.media) this.media.update(spec.media);
642
1193
  if (this.image && spec.imagePath && this.image.source !== spec.imagePath) this.image.source = spec.imagePath;
643
1194
  if (this.terminal) this.terminal.height = spec.height ?? 14;
644
1195
  if (this.image) this.image.height = spec.height ?? 12;
@@ -649,7 +1200,7 @@ var NativeBlockView = class {
649
1200
  this.actionsSignature = actionsSignature;
650
1201
  for (const child of [...this.actions.getChildren()]) child.destroy();
651
1202
  for (const action of spec.actions ?? []) {
652
- const button = new TextRenderable(this.context.renderer, {
1203
+ const button = new TextRenderable2(this.context.renderer, {
653
1204
  id: `block-action:${spec.id}:${action.id}`,
654
1205
  content: plainText(action.label),
655
1206
  fg: this.context.colors.foreground,
@@ -686,6 +1237,20 @@ var NativeBlockView = class {
686
1237
  if (this.title) this.title.content = `${this.collapsed ? "▸ " : "· "}${plainText(this.spec.title)}`;
687
1238
  this.context.emit({ type: "action", action: "toggle-block", id: this.spec.id });
688
1239
  }
1240
+ captureDisclosures(result) {
1241
+ if (this.spec.collapsible) Object.defineProperty(result, this.spec.id, { value: this.collapsed, enumerable: true, configurable: true, writable: true });
1242
+ this.children?.captureDisclosures(result);
1243
+ this.markdownSections?.captureDisclosures(result);
1244
+ }
1245
+ restoreDisclosures(state) {
1246
+ if (this.spec.collapsible && Object.hasOwn(state, this.spec.id)) {
1247
+ this.collapsed = state[this.spec.id];
1248
+ if (this.body) this.body.visible = !this.collapsed;
1249
+ if (this.title) this.title.content = `${this.collapsed ? "▸ " : "· "}${plainText(this.spec.title)}`;
1250
+ }
1251
+ this.children?.restoreDisclosures(state);
1252
+ this.markdownSections?.restoreDisclosures(state);
1253
+ }
689
1254
  applyTheme() {
690
1255
  const colors = this.context.colors;
691
1256
  this.root.backgroundColor = colors.background;
@@ -704,14 +1269,20 @@ var NativeBlockView = class {
704
1269
  this.progress.fg = colors.foreground;
705
1270
  this.progress.bg = colors.background;
706
1271
  }
707
- for (const action of this.actions?.getChildren() ?? []) if (action instanceof TextRenderable) {
1272
+ for (const action of this.actions?.getChildren() ?? []) if (action instanceof TextRenderable2) {
708
1273
  action.fg = colors.foreground;
709
1274
  action.bg = colors.background;
710
1275
  }
1276
+ this.media?.applyTheme(colors);
711
1277
  this.children?.applyTheme();
712
1278
  this.markdownSections?.applyTheme();
713
1279
  }
714
1280
  destroy() {
1281
+ if (this.media) {
1282
+ this.context.mediaViews?.delete(this.media);
1283
+ this.media.destroy();
1284
+ this.media = void 0;
1285
+ }
715
1286
  this.children?.destroy();
716
1287
  this.markdownSections?.destroy();
717
1288
  if (this.terminal) this.context.terminals.delete(this.spec.id);
@@ -750,7 +1321,10 @@ var NativeBlockList = class {
750
1321
  reorder(order) {
751
1322
  const next = order.filter((id) => this.views.has(id));
752
1323
  if (next.length === this.order.length && next.every((id, index) => this.order[index] === id)) return;
753
- next.forEach((id, index) => this.parent.add(this.views.get(id).root, index));
1324
+ next.forEach((id, index) => {
1325
+ const view = this.views.get(id).root;
1326
+ if (this.parent.getChildren()[index] !== view) this.parent.add(view, index);
1327
+ });
754
1328
  this.order = next;
755
1329
  }
756
1330
  remove(id) {
@@ -761,6 +1335,13 @@ var NativeBlockList = class {
761
1335
  applyTheme() {
762
1336
  for (const view of this.views.values()) view.applyTheme();
763
1337
  }
1338
+ captureDisclosures(result = {}) {
1339
+ for (const view of this.views.values()) view.captureDisclosures(result);
1340
+ return result;
1341
+ }
1342
+ restoreDisclosures(state) {
1343
+ for (const view of this.views.values()) view.restoreDisclosures(state);
1344
+ }
764
1345
  clear() {
765
1346
  for (const view of this.views.values()) view.destroy();
766
1347
  this.views.clear();
@@ -773,12 +1354,12 @@ var NativeBlockList = class {
773
1354
 
774
1355
  // packages/cli/src/tui/native/components/modals.ts
775
1356
  import {
776
- BoxRenderable as BoxRenderable3,
1357
+ BoxRenderable as BoxRenderable4,
777
1358
  InputRenderable,
778
1359
  ScrollBoxRenderable as ScrollBoxRenderable2,
779
1360
  TextareaRenderable,
780
1361
  TextAttributes as TextAttributes4,
781
- TextRenderable as TextRenderable2
1362
+ TextRenderable as TextRenderable3
782
1363
  } from "@opentui/core";
783
1364
 
784
1365
  // packages/cli/src/tui/native/components/select.ts
@@ -820,8 +1401,8 @@ function styleNativeSlider(slider, colors) {
820
1401
  const seek = (event) => {
821
1402
  const horizontal = slider.orientation === "horizontal";
822
1403
  const length = horizontal ? slider.width : slider.height;
823
- const offset = horizontal ? event.x - slider.x : event.y - slider.y;
824
- const progress = length > 1 ? Math.max(0, Math.min(1, offset / (length - 1))) : 0;
1404
+ const offset2 = horizontal ? event.x - slider.x : event.y - slider.y;
1405
+ const progress = length > 1 ? Math.max(0, Math.min(1, offset2 / (length - 1))) : 0;
825
1406
  slider.value = slider.min + progress * (slider.max - slider.min);
826
1407
  event.preventDefault();
827
1408
  event.stopPropagation();
@@ -854,8 +1435,8 @@ var NativeSelectRenderable = class extends SelectRenderable {
854
1435
  this.renderAfter = (buffer) => {
855
1436
  const count = this.visibleCount();
856
1437
  if (!this.showScrollIndicator || this.options.length <= count) return;
857
- const offset = Math.max(0, Math.min(this.getSelectedIndex() - Math.floor(count / 2), this.options.length - count));
858
- drawDotRail(buffer, this.width - 1, 0, this.height, false, offset / (this.options.length - count), colors);
1438
+ const offset2 = Math.max(0, Math.min(this.getSelectedIndex() - Math.floor(count / 2), this.options.length - count));
1439
+ drawDotRail(buffer, this.width - 1, 0, this.height, false, offset2 / (this.options.length - count), colors);
859
1440
  };
860
1441
  }
861
1442
  visibleCount() {
@@ -931,7 +1512,7 @@ var NativeSelectRenderable = class extends SelectRenderable {
931
1512
  };
932
1513
 
933
1514
  // packages/cli/src/tui/native/components/corner-frame.ts
934
- import { BoxRenderable as BoxRenderable2, CliRenderEvents as CliRenderEvents2, RenderableEvents as RenderableEvents2, TextAttributes as TextAttributes3 } from "@opentui/core";
1515
+ import { BoxRenderable as BoxRenderable3, CliRenderEvents as CliRenderEvents2, RenderableEvents as RenderableEvents2, TextAttributes as TextAttributes3 } from "@opentui/core";
935
1516
  function drawCornerFrame(buffer, panel, colors, progress = 1) {
936
1517
  const { x, y, width, height } = panel;
937
1518
  if (width <= 0 || height <= 0) return;
@@ -970,7 +1551,7 @@ var NativeCornerFrame = class {
970
1551
  this.colors = colors;
971
1552
  this.renderer = renderer2;
972
1553
  this.container = container;
973
- this.chrome = new BoxRenderable2(renderer2, {
1554
+ this.chrome = new BoxRenderable3(renderer2, {
974
1555
  id: `${panel.id}:corners`,
975
1556
  position: "absolute",
976
1557
  left: 0,
@@ -1100,7 +1681,7 @@ var NativeModalView = class {
1100
1681
  this.close = close2;
1101
1682
  this.spec = spec;
1102
1683
  const { renderer: renderer2, colors } = context;
1103
- this.root = new BoxRenderable3(renderer2, {
1684
+ this.root = new BoxRenderable4(renderer2, {
1104
1685
  id: `modal:${spec.id}`,
1105
1686
  position: "absolute",
1106
1687
  top: 0,
@@ -1122,7 +1703,7 @@ var NativeModalView = class {
1122
1703
  }
1123
1704
  }
1124
1705
  });
1125
- this.panel = new BoxRenderable3(renderer2, {
1706
+ this.panel = new BoxRenderable4(renderer2, {
1126
1707
  id: `modal-panel:${spec.id}`,
1127
1708
  flexDirection: "column",
1128
1709
  width: "94%",
@@ -1138,7 +1719,12 @@ var NativeModalView = class {
1138
1719
  this.root.add(this.panel);
1139
1720
  this.frame = new NativeCornerFrame(this.panel, colors, renderer2, this.root);
1140
1721
  this.breadcrumb = this.addText(`modal-breadcrumb:${spec.id}`);
1141
- this.heading = this.addText(`modal-title:${spec.id}`);
1722
+ const header = new BoxRenderable4(renderer2, { id: `modal-heading:${spec.id}`, width: "100%", flexDirection: "row", gap: 1, flexShrink: 0 });
1723
+ this.heading = new TextRenderable3(renderer2, { id: `modal-title:${spec.id}`, content: "", width: 0, flexGrow: 1, flexShrink: 1, wrapMode: "word", fg: colors.foreground });
1724
+ this.headerActions = new BoxRenderable4(renderer2, { id: `modal-header-actions:${spec.id}`, flexDirection: "row", gap: 1, flexShrink: 0 });
1725
+ header.add(this.heading);
1726
+ header.add(this.headerActions);
1727
+ this.panel.add(header);
1142
1728
  this.description = this.addText(`modal-description:${spec.id}`);
1143
1729
  if (spec.kind === "select") this.buildSelect();
1144
1730
  else if (spec.kind === "input") this.buildInput();
@@ -1156,7 +1742,7 @@ var NativeModalView = class {
1156
1742
  this.blocks = new NativeBlockList(this.modalContext(), this.document);
1157
1743
  this.panel.add(this.document);
1158
1744
  } else {
1159
- this.progress = new TextRenderable2(renderer2, {
1745
+ this.progress = new TextRenderable3(renderer2, {
1160
1746
  id: `modal-progress:${spec.id}`,
1161
1747
  width: "100%",
1162
1748
  height: 1,
@@ -1168,13 +1754,13 @@ var NativeModalView = class {
1168
1754
  selectable: false
1169
1755
  });
1170
1756
  this.panel.add(this.progress);
1171
- const progressBlocks = new BoxRenderable3(renderer2, { id: `modal-progress-blocks:${spec.id}`, flexDirection: "column", width: "100%" });
1757
+ const progressBlocks = new BoxRenderable4(renderer2, { id: `modal-progress-blocks:${spec.id}`, flexDirection: "column", width: "100%" });
1172
1758
  this.panel.add(progressBlocks);
1173
1759
  this.blocks = new NativeBlockList(this.modalContext(), progressBlocks);
1174
1760
  }
1175
1761
  this.error = this.addText(`modal-error:${spec.id}`);
1176
1762
  this.hints = this.addText(`modal-hints:${spec.id}`);
1177
- this.actions = new BoxRenderable3(renderer2, { id: `modal-actions:${spec.id}`, flexDirection: "row", gap: 1, width: "100%", marginTop: 0 });
1763
+ this.actions = new BoxRenderable4(renderer2, { id: `modal-actions:${spec.id}`, flexDirection: "row", gap: 1, width: "100%", marginTop: 0 });
1178
1764
  this.panel.add(this.actions);
1179
1765
  this.update(spec);
1180
1766
  }
@@ -1190,6 +1776,7 @@ var NativeModalView = class {
1190
1776
  error;
1191
1777
  hints;
1192
1778
  actions;
1779
+ headerActions;
1193
1780
  filterInput;
1194
1781
  editor;
1195
1782
  secretMask;
@@ -1201,11 +1788,39 @@ var NativeModalView = class {
1201
1788
  filter = "";
1202
1789
  changing = false;
1203
1790
  actionsSignature = "";
1791
+ headerActionsSignature = "";
1204
1792
  addText(id) {
1205
- const text = new TextRenderable2(this.context.renderer, { id, content: "", width: "100%", wrapMode: "word", flexShrink: 0, fg: this.context.colors.foreground, selectable: true });
1793
+ const text = new TextRenderable3(this.context.renderer, { id, content: "", width: "100%", wrapMode: "word", flexShrink: 0, fg: this.context.colors.foreground, selectable: true });
1206
1794
  this.panel.add(text);
1207
1795
  return text;
1208
1796
  }
1797
+ replaceActions(container, actions, prefix = "") {
1798
+ for (const child of [...container.getChildren()]) child.destroy();
1799
+ for (const action of actions) {
1800
+ const button = new TextRenderable3(this.context.renderer, {
1801
+ id: `modal-action:${this.spec.id}:${prefix}${action.id}`,
1802
+ content: plainText(action.label),
1803
+ fg: this.context.colors.foreground,
1804
+ bg: this.context.colors.background,
1805
+ attributes: action.disabled ? 2 : action.active ? 1 : 0,
1806
+ selectable: false,
1807
+ onMouseDown: (event) => {
1808
+ if (event.button !== 0 || action.disabled) return;
1809
+ this.emitAction(action.command ?? action.id, this.editor?.plainText);
1810
+ event.stopPropagation();
1811
+ },
1812
+ onKeyDown: (key) => {
1813
+ if (key.eventType === "release" || key.name !== "return" && key.sequence !== " " || action.disabled) return;
1814
+ this.emitAction(action.command ?? action.id, this.editor?.plainText);
1815
+ key.preventDefault();
1816
+ }
1817
+ });
1818
+ button.focusable = !action.disabled;
1819
+ nativeControlFocus(button, action.disabled ? 2 : action.active ? 1 : 0);
1820
+ container.add(button);
1821
+ }
1822
+ container.visible = actions.length > 0;
1823
+ }
1209
1824
  buildSelect() {
1210
1825
  const { renderer: renderer2, colors } = this.context;
1211
1826
  this.filterInput = new InputRenderable(renderer2, {
@@ -1308,34 +1923,15 @@ var NativeModalView = class {
1308
1923
  this.refreshOptions(patch.selectedIndex);
1309
1924
  this.blocks?.replace(spec.blocks ?? []);
1310
1925
  if (this.progress) this.progress.content = nativeProgressText(spec.progress, true);
1926
+ const headerActionsSignature = JSON.stringify(spec.headerActions ?? []);
1927
+ if (headerActionsSignature !== this.headerActionsSignature) {
1928
+ this.headerActionsSignature = headerActionsSignature;
1929
+ this.replaceActions(this.headerActions, spec.headerActions ?? [], "header:");
1930
+ }
1311
1931
  const actionsSignature = JSON.stringify(spec.actions ?? []);
1312
1932
  if (actionsSignature !== this.actionsSignature) {
1313
1933
  this.actionsSignature = actionsSignature;
1314
- for (const child of [...this.actions.getChildren()]) child.destroy();
1315
- for (const action of spec.actions ?? []) {
1316
- const button = new TextRenderable2(this.context.renderer, {
1317
- id: `modal-action:${spec.id}:${action.id}`,
1318
- content: plainText(action.label),
1319
- fg: this.context.colors.foreground,
1320
- bg: this.context.colors.background,
1321
- attributes: action.disabled ? 2 : action.active ? 1 : 0,
1322
- selectable: false,
1323
- onMouseDown: (event) => {
1324
- if (event.button !== 0 || action.disabled) return;
1325
- this.emitAction(action.command ?? action.id, this.editor?.plainText);
1326
- event.stopPropagation();
1327
- },
1328
- onKeyDown: (key) => {
1329
- if (key.eventType === "release" || key.name !== "return" && key.sequence !== " " || action.disabled) return;
1330
- this.emitAction(action.command ?? action.id, this.editor?.plainText);
1331
- key.preventDefault();
1332
- }
1333
- });
1334
- button.focusable = !action.disabled;
1335
- nativeControlFocus(button, action.disabled ? 2 : action.active ? 1 : 0);
1336
- this.actions.add(button);
1337
- }
1338
- this.actions.visible = (spec.actions?.length ?? 0) > 0;
1934
+ this.replaceActions(this.actions, spec.actions ?? []);
1339
1935
  }
1340
1936
  } finally {
1341
1937
  this.changing = false;
@@ -1390,7 +1986,7 @@ var NativeModalView = class {
1390
1986
  this.close(this.spec.id, { confirmed: true, key: current?.item.key ?? null, index: current?.index ?? -1, ...value === void 0 ? {} : { value } });
1391
1987
  }
1392
1988
  cancel(action = "cancel") {
1393
- if (this.frame.closing) return;
1989
+ if (this.frame.closing || this.spec.nonDismissible) return;
1394
1990
  if (this.spec.deferSelection) {
1395
1991
  this.emitAction(action);
1396
1992
  return;
@@ -1559,7 +2155,7 @@ var NativeModalView = class {
1559
2155
  editor.placeholderColor = colors.foreground;
1560
2156
  }
1561
2157
  if (this.secretMask) this.secretMask.fg = colors.foreground;
1562
- for (const action of this.actions.getChildren()) if (action instanceof TextRenderable2) {
2158
+ for (const action of [...this.actions.getChildren(), ...this.headerActions.getChildren()]) if (action instanceof TextRenderable3) {
1563
2159
  action.fg = colors.foreground;
1564
2160
  action.bg = colors.background;
1565
2161
  }
@@ -1643,12 +2239,12 @@ var NativeModalStack = class {
1643
2239
 
1644
2240
  // packages/cli/src/tui/native/components/instruments.ts
1645
2241
  import {
1646
- BoxRenderable as BoxRenderable4,
2242
+ BoxRenderable as BoxRenderable5,
1647
2243
  RGBA as RGBA2,
1648
2244
  RenderableEvents as RenderableEvents3,
1649
2245
  ScrollBoxRenderable as ScrollBoxRenderable3,
1650
2246
  TextAttributes as TextAttributes5,
1651
- TextRenderable as TextRenderable3
2247
+ TextRenderable as TextRenderable4
1652
2248
  } from "@opentui/core";
1653
2249
 
1654
2250
  // packages/cli/src/tui/native/components/device-history.ts
@@ -1848,7 +2444,7 @@ function instrumentStatus(value) {
1848
2444
  // packages/cli/src/tui/native/components/instruments.ts
1849
2445
  var foreground = RGBA2.defaultForeground();
1850
2446
  var background = RGBA2.defaultBackground();
1851
- var SectionBanner = class extends TextRenderable3 {
2447
+ var SectionBanner = class extends TextRenderable4 {
1852
2448
  constructor(renderer2, title, id, activate) {
1853
2449
  super(renderer2, {
1854
2450
  id,
@@ -1903,7 +2499,7 @@ var SectionBanner = class extends TextRenderable3 {
1903
2499
  if (this.plainText !== next) this.content = next;
1904
2500
  }
1905
2501
  };
1906
- var TelemetryRow = class extends TextRenderable3 {
2502
+ var TelemetryRow = class extends TextRenderable4 {
1907
2503
  constructor(renderer2, label, id, reserved = 12) {
1908
2504
  super(renderer2, {
1909
2505
  id,
@@ -1949,7 +2545,7 @@ var SignalMeter = class extends TelemetryRow {
1949
2545
  this.update(formatSignalMeter(value, maximum));
1950
2546
  }
1951
2547
  };
1952
- var InstrumentDivider = class extends TextRenderable3 {
2548
+ var InstrumentDivider = class extends TextRenderable4 {
1953
2549
  constructor(renderer2, actions) {
1954
2550
  super(renderer2, {
1955
2551
  id: "omnius-instrument-divider",
@@ -2046,17 +2642,17 @@ var InstrumentDivider = class extends TextRenderable3 {
2046
2642
  this.attributes = this.focused ? TextAttributes5.INVERSE : TextAttributes5.DIM;
2047
2643
  }
2048
2644
  };
2049
- var DeviceMonitor = class extends BoxRenderable4 {
2645
+ var DeviceMonitor = class extends BoxRenderable5 {
2050
2646
  constructor(renderer2, action) {
2051
2647
  super(renderer2, { id: "omnius-devices", width: "100%", height: 5, minHeight: 5, maxHeight: "60%", flexShrink: 1, flexDirection: "column", visible: false });
2052
2648
  this.action = action;
2053
2649
  this.heading = new SectionBanner(renderer2, "DEVICES", "omnius-devices:heading", () => this.toggle());
2054
2650
  this.heading.update("DEVICES", false);
2055
2651
  this.add(this.heading);
2056
- this.summary = new TextRenderable3(renderer2, { id: "omnius-devices:summary", content: "--", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2652
+ this.summary = new TextRenderable4(renderer2, { id: "omnius-devices:summary", content: "--", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2057
2653
  this.add(this.summary);
2058
- this.compactLoad = new TextRenderable3(renderer2, { id: "omnius-devices:load-history", content: "", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2059
- this.compactMemory = new TextRenderable3(renderer2, { id: "omnius-devices:memory-history", content: "", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2654
+ this.compactLoad = new TextRenderable4(renderer2, { id: "omnius-devices:load-history", content: "", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2655
+ this.compactMemory = new TextRenderable4(renderer2, { id: "omnius-devices:memory-history", content: "", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2060
2656
  this.add(this.compactLoad);
2061
2657
  this.add(this.compactMemory);
2062
2658
  this.scroll = new ScrollBoxRenderable3(renderer2, {
@@ -2074,7 +2670,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2074
2670
  contentOptions: { flexDirection: "column" }
2075
2671
  });
2076
2672
  this.add(this.scroll);
2077
- this.notes = new TextRenderable3(renderer2, {
2673
+ this.notes = new TextRenderable4(renderer2, {
2078
2674
  id: "omnius-devices:notes",
2079
2675
  content: "",
2080
2676
  width: "100%",
@@ -2086,7 +2682,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2086
2682
  truncate: true
2087
2683
  });
2088
2684
  this.scroll.add(this.notes);
2089
- const controls = new BoxRenderable4(renderer2, { id: "omnius-devices:controls", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2 });
2685
+ const controls = new BoxRenderable5(renderer2, { id: "omnius-devices:controls", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2 });
2090
2686
  this.add(controls);
2091
2687
  this.policy = this.button(renderer2, "omnius-devices:policy", "GPU POLICY", () => {
2092
2688
  if (this.canSelect()) this.action?.("/gpu");
@@ -2110,7 +2706,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2110
2706
  historySource = "";
2111
2707
  rows = /* @__PURE__ */ new Map();
2112
2708
  button(renderer2, id, label, activate) {
2113
- const button = new TextRenderable3(renderer2, {
2709
+ const button = new TextRenderable4(renderer2, {
2114
2710
  id,
2115
2711
  content: label,
2116
2712
  height: 1,
@@ -2173,7 +2769,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2173
2769
  for (const device of snapshot?.devices ?? []) {
2174
2770
  let row = this.rows.get(device.uuid);
2175
2771
  if (!row) {
2176
- const root = new BoxRenderable4(this._ctx, {
2772
+ const root = new BoxRenderable5(this._ctx, {
2177
2773
  id: `omnius-device:${device.uuid}`,
2178
2774
  width: "100%",
2179
2775
  flexDirection: "column",
@@ -2192,9 +2788,9 @@ var DeviceMonitor = class extends BoxRenderable4 {
2192
2788
  title.on(RenderableEvents3.FOCUSED, () => this.scroll.scrollChildIntoView(root.id));
2193
2789
  const load = new TelemetryRow(this._ctx, "LOAD", `${root.id}:load`, 16);
2194
2790
  const memory = new TelemetryRow(this._ctx, "VRAM", `${root.id}:memory`, 16);
2195
- const plot = (name) => new TextRenderable3(this._ctx, { id: `${root.id}:${name}-history`, content: "", width: "100%", height: 3, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true, selectable: false });
2791
+ const plot = (name) => new TextRenderable4(this._ctx, { id: `${root.id}:${name}-history`, content: "", width: "100%", height: 3, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true, selectable: false });
2196
2792
  const loadGraph = plot("load"), memoryGraph = plot("memory"), history = new DeviceHistory();
2197
- const reason = new TextRenderable3(this._ctx, {
2793
+ const reason = new TextRenderable4(this._ctx, {
2198
2794
  id: `${root.id}:reason`,
2199
2795
  content: "",
2200
2796
  width: "100%",
@@ -2241,6 +2837,12 @@ var DeviceMonitor = class extends BoxRenderable4 {
2241
2837
  this.fitHeight(this.availableRows + 2);
2242
2838
  this.refresh();
2243
2839
  }
2840
+ get isExpanded() {
2841
+ return this.expanded;
2842
+ }
2843
+ setExpanded(expanded) {
2844
+ if (this.expanded !== expanded) this.toggle();
2845
+ }
2244
2846
  fitHeight(railRows) {
2245
2847
  this.availableRows = Math.max(0, railRows - 2);
2246
2848
  this.visible = Boolean(this.snapshot) && this.availableRows >= 3;
@@ -2299,7 +2901,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2299
2901
  ];
2300
2902
  }
2301
2903
  };
2302
- var ChannelMap = class extends BoxRenderable4 {
2904
+ var ChannelMap = class extends BoxRenderable5 {
2303
2905
  constructor(renderer2, id, inspect, reveal) {
2304
2906
  super(renderer2, {
2305
2907
  id,
@@ -2331,7 +2933,7 @@ var ChannelMap = class extends BoxRenderable4 {
2331
2933
  if (!this.channels.has(agent.id))
2332
2934
  this.channels.set(agent.id, this.channels.size + 1);
2333
2935
  if (!this.rows.has(agent.id)) {
2334
- const row = new TextRenderable3(this._ctx, {
2936
+ const row = new TextRenderable4(this._ctx, {
2335
2937
  id: `${this.id}:channel:${agent.id}`,
2336
2938
  content: "",
2337
2939
  width: "100%",
@@ -2397,7 +2999,7 @@ var ChannelMap = class extends BoxRenderable4 {
2397
2999
  }
2398
3000
  }
2399
3001
  };
2400
- var InstrumentRail = class extends BoxRenderable4 {
3002
+ var InstrumentRail = class extends BoxRenderable5 {
2401
3003
  scroll;
2402
3004
  inferenceHeader;
2403
3005
  sessionHeader;
@@ -2451,7 +3053,7 @@ var InstrumentRail = class extends BoxRenderable4 {
2451
3053
  () => this.reveal(this.inferenceHeader)
2452
3054
  );
2453
3055
  this.scroll.add(this.inferenceHeader);
2454
- this.inference = new BoxRenderable4(renderer2, {
3056
+ this.inference = new BoxRenderable5(renderer2, {
2455
3057
  id: `${id}:inference-body`,
2456
3058
  width: "100%",
2457
3059
  flexDirection: "column",
@@ -2466,7 +3068,7 @@ var InstrumentRail = class extends BoxRenderable4 {
2466
3068
  };
2467
3069
  addRow(this.inference, "route", "ROUTE");
2468
3070
  addRow(this.inference, "backend", "BACKEND");
2469
- this.model = new TextRenderable3(renderer2, {
3071
+ this.model = new TextRenderable4(renderer2, {
2470
3072
  id: `${id}:model`,
2471
3073
  content: "MODEL --",
2472
3074
  width: "100%",
@@ -2495,7 +3097,7 @@ var InstrumentRail = class extends BoxRenderable4 {
2495
3097
  });
2496
3098
  this.contextHeader.on(RenderableEvents3.FOCUSED, () => this.reveal(this.contextHeader));
2497
3099
  this.inference.add(this.contextHeader);
2498
- this.contextDetails = new BoxRenderable4(renderer2, { id: `${id}:context-detail-body`, width: "100%", flexDirection: "column", flexShrink: 0, visible: false });
3100
+ this.contextDetails = new BoxRenderable5(renderer2, { id: `${id}:context-detail-body`, width: "100%", flexDirection: "column", flexShrink: 0, visible: false });
2499
3101
  this.inference.add(this.contextDetails);
2500
3102
  this.contextHeader.update("CONTEXT DETAILS", false);
2501
3103
  addRow(this.contextDetails, "messages", "MESSAGES");
@@ -2514,7 +3116,7 @@ var InstrumentRail = class extends BoxRenderable4 {
2514
3116
  () => this.reveal(this.sessionHeader)
2515
3117
  );
2516
3118
  this.scroll.add(this.sessionHeader);
2517
- this.session = new BoxRenderable4(renderer2, {
3119
+ this.session = new BoxRenderable5(renderer2, {
2518
3120
  id: `${id}:session-body`,
2519
3121
  width: "100%",
2520
3122
  flexDirection: "column",
@@ -2597,7 +3199,7 @@ var InstrumentRail = class extends BoxRenderable4 {
2597
3199
  applyTheme(colors) {
2598
3200
  this.backgroundColor = colors.background;
2599
3201
  const visit = (node) => {
2600
- if (node instanceof TextRenderable3) {
3202
+ if (node instanceof TextRenderable4) {
2601
3203
  node.fg = colors.foreground;
2602
3204
  node.bg = colors.background;
2603
3205
  }
@@ -2616,6 +3218,25 @@ var InstrumentRail = class extends BoxRenderable4 {
2616
3218
  body.visible
2617
3219
  );
2618
3220
  }
3221
+ capturePresentation() {
3222
+ return {
3223
+ inference: this.inference.visible,
3224
+ session: this.session.visible,
3225
+ context: this.contextHeader.expanded,
3226
+ devices: this.devices.isExpanded,
3227
+ scrollTop: this.scroll.scrollTop,
3228
+ deviceScrollTop: this.devices.scroll.scrollTop
3229
+ };
3230
+ }
3231
+ restorePresentation(state) {
3232
+ if (this.inference.visible !== state.inference) this.toggleSection("inference");
3233
+ if (this.session.visible !== state.session) this.toggleSection("session");
3234
+ this.contextHeader.update("CONTEXT DETAILS", state.context);
3235
+ this.contextDetails.visible = this.contextHeader.visible && state.context;
3236
+ this.devices.setExpanded(state.devices);
3237
+ this.scroll.scrollTo(state.scrollTop);
3238
+ this.devices.scroll.scrollTo(state.deviceScrollTop);
3239
+ }
2619
3240
  focusTargets() {
2620
3241
  if (!this.visible) return [];
2621
3242
  return [
@@ -3440,6 +4061,7 @@ var ALIASES = {
3440
4061
  update: ["upgrade"],
3441
4062
  indicator: ["tray"],
3442
4063
  listen: ["mic"],
4064
+ sound: ["audio"],
3443
4065
  pointcloud: ["points", "pcl", "point"],
3444
4066
  neovim: ["nvim"],
3445
4067
  apikey: ["key"],
@@ -4074,7 +4696,7 @@ var VimComposer = class {
4074
4696
  };
4075
4697
 
4076
4698
  // packages/cli/src/tui/native/components/selection-menu.ts
4077
- import { BoxRenderable as BoxRenderable5, CliRenderEvents as CliRenderEvents3, TextAttributes as TextAttributes6, TextRenderable as TextRenderable4 } from "@opentui/core";
4699
+ import { BoxRenderable as BoxRenderable6, CliRenderEvents as CliRenderEvents3, TextAttributes as TextAttributes6, TextRenderable as TextRenderable5 } from "@opentui/core";
4078
4700
  var NativeSelectionMenu = class {
4079
4701
  constructor(renderer2, colors, copy, restoreFocus) {
4080
4702
  this.renderer = renderer2;
@@ -4102,7 +4724,7 @@ var NativeSelectionMenu = class {
4102
4724
  this.selectedText = text;
4103
4725
  this.anchor = { x, y };
4104
4726
  this.previousFocus = this.renderer.currentFocusedRenderable;
4105
- this.overlay = new BoxRenderable5(this.renderer, {
4727
+ this.overlay = new BoxRenderable6(this.renderer, {
4106
4728
  id: "omnius-selection-menu",
4107
4729
  position: "absolute",
4108
4730
  left: 0,
@@ -4118,7 +4740,7 @@ var NativeSelectionMenu = class {
4118
4740
  event.stopPropagation();
4119
4741
  }
4120
4742
  });
4121
- this.panel = new BoxRenderable5(this.renderer, {
4743
+ this.panel = new BoxRenderable6(this.renderer, {
4122
4744
  id: "omnius-selection-menu-panel",
4123
4745
  position: "absolute",
4124
4746
  width: 14,
@@ -4132,7 +4754,7 @@ var NativeSelectionMenu = class {
4132
4754
  event.stopPropagation();
4133
4755
  }
4134
4756
  });
4135
- const action = new TextRenderable4(this.renderer, {
4757
+ const action = new TextRenderable5(this.renderer, {
4136
4758
  id: "omnius-selection-copy",
4137
4759
  content: "COPY",
4138
4760
  height: 1,
@@ -4181,7 +4803,7 @@ var NativeSelectionMenu = class {
4181
4803
  if (!this.panel) return;
4182
4804
  this.panel.backgroundColor = colors.background;
4183
4805
  this.frame?.setColors(colors);
4184
- for (const child of this.panel.getChildren()) if (child instanceof TextRenderable4) {
4806
+ for (const child of this.panel.getChildren()) if (child instanceof TextRenderable5) {
4185
4807
  child.fg = colors.foreground;
4186
4808
  child.bg = colors.background;
4187
4809
  }
@@ -4207,8 +4829,8 @@ var NativeSelectionMenu = class {
4207
4829
  };
4208
4830
 
4209
4831
  // packages/cli/src/tui/native/components/tab-strip.ts
4210
- import { BoxRenderable as BoxRenderable6, CliRenderEvents as CliRenderEvents4, ScrollBoxRenderable as ScrollBoxRenderable4, SliderRenderable, TextAttributes as TextAttributes7, TextRenderable as TextRenderable5 } from "@opentui/core";
4211
- var NativeTabStrip = class extends BoxRenderable6 {
4832
+ import { BoxRenderable as BoxRenderable7, CliRenderEvents as CliRenderEvents4, ScrollBoxRenderable as ScrollBoxRenderable4, SliderRenderable, TextAttributes as TextAttributes7, TextRenderable as TextRenderable6 } from "@opentui/core";
4833
+ var NativeTabStrip = class extends BoxRenderable7 {
4212
4834
  constructor(renderer2, options) {
4213
4835
  super(renderer2, {
4214
4836
  id: options.id,
@@ -4287,7 +4909,7 @@ var NativeTabStrip = class extends BoxRenderable6 {
4287
4909
  for (const [index, action] of items.entries()) {
4288
4910
  let entry = this.buttons.get(action.id);
4289
4911
  if (!entry) {
4290
- const button = new TextRenderable5(this.renderer, {
4912
+ const button = new TextRenderable6(this.renderer, {
4291
4913
  id: `${this.options.prefix}:${action.id}`,
4292
4914
  content: "",
4293
4915
  height: 1,
@@ -4374,6 +4996,10 @@ var NativeTabStrip = class extends BoxRenderable6 {
4374
4996
  styleNativeScrollbars(this.scroll, colors);
4375
4997
  styleNativeSlider(this.rail, colors);
4376
4998
  }
4999
+ restoreOffset(offset2) {
5000
+ this.reveal = void 0;
5001
+ this.scroll.scrollLeft = offset2;
5002
+ }
4377
5003
  destroySelf() {
4378
5004
  this.renderer.off(CliRenderEvents4.FRAME, this.syncScroll);
4379
5005
  super.destroySelf();
@@ -4387,13 +5013,13 @@ var NativeFrontend = class {
4387
5013
  this.renderer = renderer2;
4388
5014
  this.emit = emit;
4389
5015
  const colors = palette(initialTheme);
4390
- this.context = { renderer: renderer2, colors, syntax: SyntaxStyle2.fromStyles({}, renderer2.nativeScene), emit, terminals: /* @__PURE__ */ new Map() };
5016
+ this.context = { renderer: renderer2, colors, syntax: SyntaxStyle2.fromStyles({}, renderer2.nativeScene), emit, terminals: /* @__PURE__ */ new Map(), mediaViews: /* @__PURE__ */ new Set() };
4391
5017
  this.disposeSelectionContrast = installDefaultSelectionContrast(renderer2);
4392
5018
  this.clipboard = options.clipboard ?? createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
4393
5019
  this.selectionMenu = new NativeSelectionMenu(renderer2, colors, (text) => {
4394
5020
  void this.copy(text);
4395
5021
  }, () => this.restoreFocus());
4396
- this.root = new BoxRenderable7(renderer2, {
5022
+ this.root = new BoxRenderable8(renderer2, {
4397
5023
  id: "omnius-native",
4398
5024
  width: "100%",
4399
5025
  height: "100%",
@@ -4401,7 +5027,7 @@ var NativeFrontend = class {
4401
5027
  backgroundColor: colors.background
4402
5028
  });
4403
5029
  renderer2.root.add(this.root);
4404
- this.header = new BoxRenderable7(renderer2, {
5030
+ this.header = new BoxRenderable8(renderer2, {
4405
5031
  id: "omnius-header",
4406
5032
  width: "100%",
4407
5033
  minWidth: 0,
@@ -4425,9 +5051,9 @@ var NativeFrontend = class {
4425
5051
  TextAttributes8.DIM
4426
5052
  );
4427
5053
  };
4428
- const headerRow = new BoxRenderable7(renderer2, { id: "omnius-header-row", width: "100%", minWidth: 0, minHeight: 1, flexDirection: "row", gap: 1 });
5054
+ const headerRow = new BoxRenderable8(renderer2, { id: "omnius-header-row", width: "100%", minWidth: 0, minHeight: 1, flexDirection: "row", gap: 1 });
4429
5055
  this.header.add(headerRow);
4430
- this.identity = new TextRenderable6(renderer2, {
5056
+ this.identity = new TextRenderable7(renderer2, {
4431
5057
  id: "omnius-identity",
4432
5058
  content: "Omnius",
4433
5059
  fg: colors.foreground,
@@ -4465,9 +5091,9 @@ var NativeFrontend = class {
4465
5091
  verticalScrollbarOptions: { visible: false }
4466
5092
  });
4467
5093
  headerRow.add(this.headerActions);
4468
- this.headerPanel = new TextRenderable6(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
5094
+ this.headerPanel = new TextRenderable7(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
4469
5095
  this.header.add(this.headerPanel);
4470
- this.detailButton = new TextRenderable6(renderer2, {
5096
+ this.detailButton = new TextRenderable7(renderer2, {
4471
5097
  id: "omnius-details-toggle",
4472
5098
  content: "+ DETAILS",
4473
5099
  height: 1,
@@ -4500,7 +5126,7 @@ var NativeFrontend = class {
4500
5126
  control.attributes = attributes;
4501
5127
  });
4502
5128
  }
4503
- this.sessionRow = new BoxRenderable7(renderer2, {
5129
+ this.sessionRow = new BoxRenderable8(renderer2, {
4504
5130
  id: "omnius-sessions-row",
4505
5131
  width: "100%",
4506
5132
  height: 1,
@@ -4518,7 +5144,7 @@ var NativeFrontend = class {
4518
5144
  activate: (id) => this.emitAction("session", id)
4519
5145
  });
4520
5146
  this.sessionRow.add(this.sessions);
4521
- this.newSession = new TextRenderable6(renderer2, {
5147
+ this.newSession = new TextRenderable7(renderer2, {
4522
5148
  id: "omnius-session-new",
4523
5149
  content: "+ NEW",
4524
5150
  width: 5,
@@ -4547,13 +5173,15 @@ var NativeFrontend = class {
4547
5173
  this.newSession.on("focused", styleNew);
4548
5174
  this.newSession.on("blurred", styleNew);
4549
5175
  this.sessionRow.add(this.newSession);
4550
- this.routeWarning = new TextRenderable6(renderer2, { id: "omnius-route-warning", content: "", width: "100%", wrapMode: "word", fg: colors.warning, attributes: TextAttributes8.BOLD | TextAttributes8.UNDERLINE, visible: false, flexShrink: 0 });
5176
+ this.routeWarning = new TextRenderable7(renderer2, { id: "omnius-route-warning", content: "", width: "100%", wrapMode: "word", fg: colors.warning, attributes: TextAttributes8.BOLD | TextAttributes8.UNDERLINE, visible: false, flexShrink: 0 });
4551
5177
  this.root.add(this.routeWarning);
4552
- this.taskPane = new BoxRenderable7(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
5178
+ this.taskPane = new BoxRenderable8(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
4553
5179
  this.tasks = new NativeBlockList(this.context, this.taskPane);
4554
5180
  this.root.add(this.taskPane);
4555
- this.body = new BoxRenderable7(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
5181
+ this.body = new BoxRenderable8(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
4556
5182
  this.root.add(this.body);
5183
+ this.transcriptPane = new BoxRenderable8(renderer2, { id: "omnius-transcript-pane", flexDirection: "column", flexGrow: 1, flexShrink: 1, minWidth: 0, minHeight: 1 });
5184
+ this.body.add(this.transcriptPane);
4557
5185
  this.scroll = new ScrollBoxRenderable5(renderer2, {
4558
5186
  id: "omnius-conversation",
4559
5187
  flexGrow: 1,
@@ -4609,7 +5237,7 @@ var NativeFrontend = class {
4609
5237
  }
4610
5238
  } }
4611
5239
  });
4612
- this.body.add(this.scroll);
5240
+ this.transcriptPane.add(this.scroll);
4613
5241
  this.content = new NativeBlockList(this.context, this.scroll);
4614
5242
  this.instrumentDivider = new InstrumentDivider(renderer2, {
4615
5243
  resize: (column) => this.resizeInstruments(column),
@@ -4627,7 +5255,7 @@ var NativeFrontend = class {
4627
5255
  this.root.onMouseUp = (event) => this.instrumentDivider.handleRelease(event);
4628
5256
  this.instruments = new InstrumentRail(renderer2, { colors, onInspectAgent: (id) => this.emitAction("view", id), onAction: (command2) => this.emitAction(command2) });
4629
5257
  this.body.add(this.instruments);
4630
- this.embeddedPane = new BoxRenderable7(renderer2, {
5258
+ this.embeddedPane = new BoxRenderable8(renderer2, {
4631
5259
  id: "omnius-editor-pane",
4632
5260
  width: "50%",
4633
5261
  flexShrink: 0,
@@ -4638,7 +5266,7 @@ var NativeFrontend = class {
4638
5266
  visible: false
4639
5267
  });
4640
5268
  this.body.add(this.embeddedPane);
4641
- this.liveButton = new TextRenderable6(renderer2, {
5269
+ this.liveButton = new TextRenderable7(renderer2, {
4642
5270
  id: "omnius-live-button",
4643
5271
  content: "↓ Live",
4644
5272
  width: "100%",
@@ -4660,9 +5288,9 @@ var NativeFrontend = class {
4660
5288
  }
4661
5289
  });
4662
5290
  this.liveButton.focusable = true;
4663
- this.root.add(this.liveButton);
4664
- this.queue = new TextRenderable6(renderer2, { id: "omnius-steering-queue", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, visible: false, flexShrink: 0 });
4665
- this.root.add(this.queue);
5291
+ this.transcriptPane.add(this.liveButton);
5292
+ this.queue = new TextRenderable7(renderer2, { id: "omnius-steering-queue", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, visible: false, flexShrink: 0 });
5293
+ this.transcriptPane.add(this.queue);
4666
5294
  this.suggestions = new NativeSelectRenderable(renderer2, {
4667
5295
  id: "omnius-suggestions",
4668
5296
  width: "100%",
@@ -4679,10 +5307,10 @@ var NativeFrontend = class {
4679
5307
  });
4680
5308
  this.suggestions.onActivate = () => this.acceptSuggestion();
4681
5309
  this.suggestions.on("itemSelected", () => this.acceptSuggestion());
4682
- this.root.add(this.suggestions);
4683
- this.status = new BoxRenderable7(renderer2, { id: "omnius-live-status", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2, visible: false });
4684
- this.root.add(this.status);
4685
- this.footer = new BoxRenderable7(renderer2, {
5310
+ this.transcriptPane.add(this.suggestions);
5311
+ this.status = new BoxRenderable8(renderer2, { id: "omnius-live-status", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2, visible: false });
5312
+ this.transcriptPane.add(this.status);
5313
+ this.footer = new BoxRenderable8(renderer2, {
4686
5314
  id: "omnius-input-box",
4687
5315
  width: "100%",
4688
5316
  flexShrink: 0,
@@ -4691,7 +5319,7 @@ var NativeFrontend = class {
4691
5319
  flexDirection: "column"
4692
5320
  });
4693
5321
  this.root.add(this.footer);
4694
- this.footerRule = new BoxRenderable7(renderer2, {
5322
+ this.footerRule = new BoxRenderable8(renderer2, {
4695
5323
  id: "omnius-input-rule",
4696
5324
  width: "100%",
4697
5325
  height: 1,
@@ -4719,9 +5347,9 @@ var NativeFrontend = class {
4719
5347
  sidebarColumn: this.instrumentDivider.visible ? this.instrumentDivider.x - this.footerRule.x : void 0
4720
5348
  }), this.footerRule.x, this.footerRule.y, this.context.colors.foreground, this.context.colors.background, TextAttributes8.DIM);
4721
5349
  };
4722
- const inputRow = new BoxRenderable7(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
5350
+ const inputRow = new BoxRenderable8(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
4723
5351
  this.footer.add(inputRow);
4724
- this.prompt = new TextRenderable6(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
5352
+ this.prompt = new TextRenderable7(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
4725
5353
  inputRow.add(this.prompt);
4726
5354
  this.editor = new TextareaRenderable2(renderer2, {
4727
5355
  id: "omnius-input",
@@ -4741,7 +5369,7 @@ var NativeFrontend = class {
4741
5369
  onCursorChange: () => this.editorChanged(),
4742
5370
  onSubmit: () => this.submit()
4743
5371
  });
4744
- this.vimMode = new TextRenderable6(renderer2, {
5372
+ this.vimMode = new TextRenderable7(renderer2, {
4745
5373
  id: "omnius-vim-mode",
4746
5374
  content: "INSERT",
4747
5375
  height: 1,
@@ -4762,17 +5390,17 @@ var NativeFrontend = class {
4762
5390
  this.renderer.requestRender();
4763
5391
  });
4764
5392
  inputRow.add(this.editor);
4765
- this.enhance = new BoxRenderable7(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
5393
+ this.enhance = new BoxRenderable8(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
4766
5394
  inputRow.add(this.enhance);
4767
- this.activity = new TextRenderable6(renderer2, { id: "omnius-activity", content: "", flexGrow: 1, minWidth: 0, height: 1, wrapMode: "none", truncate: true, visible: false, fg: colors.accent });
5395
+ this.activity = new TextRenderable7(renderer2, { id: "omnius-activity", content: "", flexGrow: 1, minWidth: 0, height: 1, wrapMode: "none", truncate: true, visible: false, fg: colors.accent });
4768
5396
  this.status.add(this.activity);
4769
- this.media = new BoxRenderable7(renderer2, { id: "omnius-media", height: 1, minWidth: 0, flexShrink: 1, flexDirection: "row", gap: 1, visible: false });
4770
- this.mediaText = new TextRenderable6(renderer2, { id: "omnius-media-text", content: "", flexGrow: 1, minWidth: 0, wrapMode: "none", truncate: true, fg: colors.accent });
5397
+ this.media = new BoxRenderable8(renderer2, { id: "omnius-media", height: 1, minWidth: 0, flexShrink: 1, flexDirection: "row", gap: 1, visible: false });
5398
+ this.mediaText = new TextRenderable7(renderer2, { id: "omnius-media-text", content: "", flexGrow: 1, minWidth: 0, wrapMode: "none", truncate: true, fg: colors.accent });
4771
5399
  this.media.add(this.mediaText);
4772
- this.micTrack = new TextRenderable6(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, fg: colors.foreground, visible: false });
5400
+ this.micTrack = new TextRenderable7(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, fg: colors.foreground, visible: false });
4773
5401
  this.media.add(this.micTrack);
4774
5402
  this.status.add(this.media);
4775
- this.copyFeedback = new TextRenderable6(renderer2, {
5403
+ this.copyFeedback = new TextRenderable7(renderer2, {
4776
5404
  id: "omnius-copy-feedback",
4777
5405
  content: "",
4778
5406
  height: 1,
@@ -4783,14 +5411,14 @@ var NativeFrontend = class {
4783
5411
  visible: false
4784
5412
  });
4785
5413
  this.status.add(this.copyFeedback);
4786
- this.metricsRow = new BoxRenderable7(renderer2, { id: "omnius-metrics-row", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 1 });
4787
- this.keyHints = new TextRenderable6(renderer2, { id: "omnius-key-hints", content: "F1 HELP TAB FOCUS", height: 1, flexShrink: 0, attributes: TextAttributes8.DIM, fg: colors.foreground, selectable: false });
5414
+ this.metricsRow = new BoxRenderable8(renderer2, { id: "omnius-metrics-row", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 1 });
5415
+ this.keyHints = new TextRenderable7(renderer2, { id: "omnius-key-hints", content: "F1 HELP TAB FOCUS", height: 1, flexShrink: 0, attributes: TextAttributes8.DIM, fg: colors.foreground, selectable: false });
4788
5416
  this.metricsRow.add(this.keyHints);
4789
- this.metricsStartActions = new BoxRenderable7(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
5417
+ this.metricsStartActions = new BoxRenderable8(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4790
5418
  this.metricsRow.add(this.metricsStartActions);
4791
- this.metrics = new TextRenderable6(renderer2, { id: "omnius-metrics", content: "", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, flexShrink: 1, flexGrow: 1, minWidth: 0 });
5419
+ this.metrics = new TextRenderable7(renderer2, { id: "omnius-metrics", content: "", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, flexShrink: 1, flexGrow: 1, minWidth: 0 });
4792
5420
  this.metricsRow.add(this.metrics);
4793
- this.metricsEndActions = new BoxRenderable7(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
5421
+ this.metricsEndActions = new BoxRenderable8(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4794
5422
  this.metricsRow.add(this.metricsEndActions);
4795
5423
  this.root.add(this.metricsRow);
4796
5424
  this.modals = new NativeModalStack(this.context, () => this.restoreFocus());
@@ -4823,6 +5451,7 @@ var NativeFrontend = class {
4823
5451
  taskPane;
4824
5452
  tasks;
4825
5453
  body;
5454
+ transcriptPane;
4826
5455
  instruments;
4827
5456
  instrumentDivider;
4828
5457
  detailButton;
@@ -4874,6 +5503,9 @@ var NativeFrontend = class {
4874
5503
  destroyed = false;
4875
5504
  pendingScrollRestore;
4876
5505
  viewScroll = /* @__PURE__ */ new Map();
5506
+ viewDisclosures = /* @__PURE__ */ new Map();
5507
+ pendingDisclosures;
5508
+ pendingInterface;
4877
5509
  embeddedId;
4878
5510
  selectionArmed = false;
4879
5511
  update(state, patch) {
@@ -4883,7 +5515,9 @@ var NativeFrontend = class {
4883
5515
  const viewChanged = previousView !== void 0 && previousView !== currentView;
4884
5516
  if (viewChanged) {
4885
5517
  this.viewScroll.set(previousView, { top: this.scroll.scrollTop, live: this.offset === 0 });
5518
+ this.viewDisclosures.set(previousView, this.content.captureDisclosures(this.tasks.captureDisclosures()));
4886
5519
  this.pendingScrollRestore = this.viewScroll.get(currentView) ?? { top: 0, live: true };
5520
+ this.pendingDisclosures = this.viewDisclosures.get(currentView);
4887
5521
  this.newEntries = 0;
4888
5522
  }
4889
5523
  this.state = state;
@@ -4918,6 +5552,11 @@ var NativeFrontend = class {
4918
5552
  if (this.offset > 0 && !viewChanged && !patch.reset) this.newEntries += patch.upsert.filter((block) => !this.content.views.has(block.id)).length;
4919
5553
  this.content.patch(patch);
4920
5554
  }
5555
+ if (this.pendingDisclosures) {
5556
+ this.content.restoreDisclosures(this.pendingDisclosures);
5557
+ this.tasks.restoreDisclosures(this.pendingDisclosures);
5558
+ this.pendingDisclosures = void 0;
5559
+ }
4921
5560
  this.queue.content = state.queued.length ? `Queued (${state.queued.length}) · ${state.queued.join(" · ")}` : "";
4922
5561
  this.queue.visible = state.queued.length > 0;
4923
5562
  const suggestionSignature = JSON.stringify(state.suggestions);
@@ -4966,7 +5605,7 @@ var NativeFrontend = class {
4966
5605
  for (const action of actions) {
4967
5606
  const colors = this.context.colors;
4968
5607
  const attributes = action.disabled ? TextAttributes8.DIM : action.active ? TextAttributes8.BOLD | TextAttributes8.UNDERLINE : 0;
4969
- const button = new TextRenderable6(this.renderer, {
5608
+ const button = new TextRenderable7(this.renderer, {
4970
5609
  id: `${prefix}:${action.id}`,
4971
5610
  content: styledText(action.label),
4972
5611
  flexShrink: 0,
@@ -5112,14 +5751,17 @@ var NativeFrontend = class {
5112
5751
  const shown = !this.embeddedPane.visible && (wide ? this.detailsOpen ?? true : this.narrowDetailsOpen);
5113
5752
  const horizontal = wide || this.embeddedPane.visible && width >= 80;
5114
5753
  this.body.flexDirection = horizontal ? "row" : "column";
5115
- this.scroll.height = horizontal ? "100%" : "auto";
5116
- this.scroll.flexBasis = horizontal ? "auto" : 0;
5754
+ this.transcriptPane.height = horizontal ? "100%" : "auto";
5755
+ this.transcriptPane.flexBasis = horizontal ? "auto" : 0;
5756
+ this.scroll.height = "auto";
5757
+ this.scroll.flexBasis = 0;
5117
5758
  this.instruments.visible = shown;
5118
5759
  this.instrumentDivider.visible = wide && !this.embeddedPane.visible;
5119
5760
  this.instrumentDivider.setCollapsed(!shown);
5120
5761
  const railWidth = Math.max(24, Math.min(width - 41, this.sidebarWidth ?? Math.floor(width * 0.38)));
5121
5762
  this.instruments.width = wide ? railWidth : "100%";
5122
- this.scroll.width = wide && this.instrumentDivider.visible ? width - (shown ? railWidth : 0) - 1 : this.embeddedPane.visible && width >= 80 ? "50%" : "100%";
5763
+ this.transcriptPane.width = wide && this.instrumentDivider.visible ? width - (shown ? railWidth : 0) - 1 : this.embeddedPane.visible && width >= 80 ? "50%" : "100%";
5764
+ this.scroll.width = "100%";
5123
5765
  const inspectorHeight = Math.max(4, Math.min(9, Math.floor(this.renderer.terminalHeight / 3)));
5124
5766
  this.instruments.height = wide ? "100%" : inspectorHeight;
5125
5767
  this.instruments.maxHeight = wide ? "100%" : inspectorHeight;
@@ -5167,6 +5809,13 @@ var NativeFrontend = class {
5167
5809
  }
5168
5810
  onFrame = () => {
5169
5811
  if (this.destroyed) return;
5812
+ if (this.pendingInterface) {
5813
+ const state = this.pendingInterface;
5814
+ this.pendingInterface = void 0;
5815
+ this.instruments.restorePresentation(state.instruments);
5816
+ this.sessions.restoreOffset(state.tabs.sessions);
5817
+ this.tabs.restoreOffset(state.tabs.agents);
5818
+ }
5170
5819
  if (this.pendingScrollRestore) {
5171
5820
  const restore = this.pendingScrollRestore;
5172
5821
  this.pendingScrollRestore = void 0;
@@ -5176,10 +5825,11 @@ var NativeFrontend = class {
5176
5825
  const height = Math.max(1, Math.min(8, this.editor.editorView.getTotalVirtualLineCount(), this.renderer.terminalHeight - 8));
5177
5826
  if (height !== this.editor.height) this.editor.height = height;
5178
5827
  this.layoutTabStrips();
5179
- const offset = this.offset;
5180
- if (offset === 0) this.newEntries = 0;
5181
- this.liveButton.visible = offset > 0;
5182
- const liveLabel = offset > 0 ? `↓ Live${this.newEntries ? ` · ${this.newEntries} new` : ""}` : "↓ Live";
5828
+ for (const media of this.context.mediaViews ?? []) media.reconcile({ x: this.scroll.viewport.x, y: this.scroll.viewport.y, width: this.scroll.viewport.width, height: this.scroll.viewport.height }, !this.paused);
5829
+ const offset2 = this.offset;
5830
+ if (offset2 === 0) this.newEntries = 0;
5831
+ this.liveButton.visible = offset2 > 0;
5832
+ const liveLabel = offset2 > 0 ? `↓ Live${this.newEntries ? ` · ${this.newEntries} new` : ""}` : "↓ Live";
5183
5833
  if (this.liveButton.plainText !== liveLabel) this.liveButton.content = liveLabel;
5184
5834
  const event = { type: "viewport", offset: this.scroll.scrollTop, height: this.scroll.viewport.height, width: this.scroll.viewport.width, total: this.scroll.scrollHeight };
5185
5835
  const signature = JSON.stringify(event);
@@ -5446,7 +6096,7 @@ var NativeFrontend = class {
5446
6096
  const target = targets[(current + direction + targets.length) % targets.length];
5447
6097
  target.focus();
5448
6098
  if (target.id.startsWith("header:")) this.headerActions.scrollChildIntoView(target.id);
5449
- else if (target.id.startsWith("block-title:") || target.id.startsWith("block-action:")) {
6099
+ else if (target.id.startsWith("block-title:") || target.id.startsWith("block-action:") || target.id.startsWith("media-control:") || target.id.startsWith("media-preview:")) {
5450
6100
  let block = target;
5451
6101
  while (block && !block.id.startsWith("block:")) block = block.parent;
5452
6102
  if (block) this.scroll.scrollChildIntoView(block.id);
@@ -5618,6 +6268,52 @@ var NativeFrontend = class {
5618
6268
  this.restoreFocus();
5619
6269
  }
5620
6270
  }
6271
+ pauseMedia() {
6272
+ for (const media of this.context.mediaViews ?? []) media.pausePlayback();
6273
+ }
6274
+ captureInterfaceState() {
6275
+ this.flushLatestInput();
6276
+ const key = `${this.state?.sessions?.activeId ?? ""}:${this.state?.activeViewId ?? "main"}:${this.state?.presentation ?? "stack"}`;
6277
+ this.viewScroll.set(key, { top: this.scroll.scrollTop, live: this.offset === 0 });
6278
+ this.viewDisclosures.set(key, this.content.captureDisclosures(this.tasks.captureDisclosures()));
6279
+ return {
6280
+ version: 1,
6281
+ sessionId: this.state?.sessions?.activeId,
6282
+ activeViewId: this.state?.activeViewId ?? "main",
6283
+ presentation: this.state?.presentation ?? "stack",
6284
+ input: { text: this.editor.plainText, cursor: this.inputCursor() },
6285
+ views: Object.fromEntries([...this.viewScroll].slice(-256).map(([id, view]) => [id, { ...view, disclosures: this.viewDisclosures.get(id) ?? {} }])),
6286
+ layout: { sidebarWidth: this.sidebarWidth, detailsOpen: this.detailsOpen, narrowDetailsOpen: this.narrowDetailsOpen, headerPanelCollapsed: this.headerPanelCollapsed },
6287
+ instruments: this.instruments.capturePresentation(),
6288
+ tabs: { sessions: this.sessions.scroll.scrollLeft, agents: this.tabs.scroll.scrollLeft }
6289
+ };
6290
+ }
6291
+ restoreInterfaceState(value) {
6292
+ const state = sanitizeNativeInterfaceState(value);
6293
+ if (!state) return;
6294
+ for (const [key2, view2] of Object.entries(state.views)) {
6295
+ this.viewScroll.set(key2, { top: view2.top, live: view2.live });
6296
+ this.viewDisclosures.set(key2, view2.disclosures);
6297
+ }
6298
+ this.sidebarWidth = state.layout.sidebarWidth;
6299
+ this.detailsOpen = state.layout.detailsOpen;
6300
+ this.narrowDetailsOpen = state.layout.narrowDetailsOpen;
6301
+ this.headerPanelCollapsed = state.layout.headerPanelCollapsed;
6302
+ this.headerSignature = "";
6303
+ this.setInput(state.input.text, state.input.cursor);
6304
+ const key = `${this.state?.sessions?.activeId ?? state.sessionId ?? ""}:${this.state?.activeViewId ?? state.activeViewId}:${this.state?.presentation ?? state.presentation}`;
6305
+ const view = this.viewScroll.get(key);
6306
+ if (view) {
6307
+ this.pendingScrollRestore = view;
6308
+ const disclosures = this.viewDisclosures.get(key) ?? {};
6309
+ this.content.restoreDisclosures(disclosures);
6310
+ this.tasks.restoreDisclosures(disclosures);
6311
+ }
6312
+ this.pendingInterface = state;
6313
+ if (this.state) this.update(this.state);
6314
+ this.layoutInstruments();
6315
+ this.renderer.requestRender();
6316
+ }
5621
6317
  async dispose() {
5622
6318
  if (this.destroyed) return;
5623
6319
  this.destroyed = true;
@@ -5628,9 +6324,14 @@ var NativeFrontend = class {
5628
6324
  this.renderer.keyInput.off("paste", this.onPaste);
5629
6325
  this.renderer.off(CliRenderEvents5.FRAME, this.onFrame);
5630
6326
  this.renderer.off(CliRenderEvents5.RESIZE, this.onResize);
6327
+ const mediaClosed = [...this.context.mediaViews ?? []].map((media) => {
6328
+ media.pausePlayback();
6329
+ return media.closed;
6330
+ });
5631
6331
  this.modals.destroy();
5632
6332
  this.content.destroy();
5633
6333
  this.tasks.destroy();
6334
+ await Promise.allSettled(mediaClosed);
5634
6335
  this.root.destroyRecursively();
5635
6336
  this.context.syntax.destroy();
5636
6337
  await this.clipboard.dispose();
@@ -5650,10 +6351,10 @@ function installTerminalCursorDefaults(renderer2, session) {
5650
6351
  if (disposed || session.disposed) return Promise.resolve();
5651
6352
  requested++;
5652
6353
  if (pending) return pending;
5653
- let resolve;
6354
+ let resolve2;
5654
6355
  let reject;
5655
6356
  const completion = new Promise((done, fail2) => {
5656
- resolve = done;
6357
+ resolve2 = done;
5657
6358
  reject = fail2;
5658
6359
  });
5659
6360
  pending = completion;
@@ -5666,7 +6367,7 @@ function installTerminalCursorDefaults(renderer2, session) {
5666
6367
  restored = generation;
5667
6368
  }
5668
6369
  pending = void 0;
5669
- resolve();
6370
+ resolve2();
5670
6371
  } catch (error) {
5671
6372
  pending = void 0;
5672
6373
  reject(error);
@@ -5732,11 +6433,24 @@ async function command(message) {
5732
6433
  frontend.command(message.command);
5733
6434
  return;
5734
6435
  }
6436
+ if (message.type === "interface-capture") {
6437
+ await renderer.idle();
6438
+ reply({ type: "interface-state", id: message.id, state: frontend.captureInterfaceState() });
6439
+ return;
6440
+ }
6441
+ if (message.type === "interface-restore") {
6442
+ frontend.restoreInterfaceState(message.state);
6443
+ renderer.requestRender();
6444
+ await renderer.idle();
6445
+ reply({ type: "ack", id: message.id });
6446
+ return;
6447
+ }
5735
6448
  if (message.type === "state") {
5736
6449
  frontend.update(message.snapshot, message.content);
5737
6450
  renderer.requestRender();
5738
6451
  await renderer.idle();
5739
6452
  } else if (message.type === "suspend") {
6453
+ frontend.pauseMedia();
5740
6454
  await cursorDefaults.restore();
5741
6455
  await renderer.suspend();
5742
6456
  } else if (message.type === "resume") {