omnius 1.0.866 → 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 (36) hide show
  1. package/assets/fonts/DepartureMono-LICENSE.txt +93 -0
  2. package/assets/licenses/Prism-1.29.0-LICENSE.txt +21 -0
  3. package/dist/api/py-embed.js +32 -4
  4. package/dist/index.js +2 -2
  5. package/dist/library.js +1212 -1084
  6. package/dist/tui/native/worker.js +863 -159
  7. package/dist/update-worker.js +147 -81
  8. package/docs/DISCOVERY.json +10535 -8877
  9. package/docs/DISCOVERY.md +37 -7
  10. package/docs/design/dashboard-TODO.md +86 -0
  11. package/docs/design/dashboard-reconstruction-plan.md +58 -0
  12. package/docs/design/dashboard-styleguide.md +86 -0
  13. package/docs/design/media-unification-TODO.md +78 -0
  14. package/docs/design/media-unification-design.md +89 -0
  15. package/docs/design/update-operation-cancellation.md +40 -0
  16. package/docs/design/update-session-restoration-TODO.md +30 -0
  17. package/docs/work-orders/evidence/dashboard-browser-reconstruction-20261016.json +75 -0
  18. package/docs/work-orders/evidence/dashboard-media-management-20261016.json +145 -0
  19. package/docs/work-orders/evidence/instrument-media-live-20261016/media-audio-playing.png +0 -0
  20. package/docs/work-orders/evidence/instrument-media-live-20261016/media-image-100x30.png +0 -0
  21. package/docs/work-orders/evidence/instrument-media-live-20261016/media-image-120x36.png +0 -0
  22. package/docs/work-orders/evidence/instrument-media-live-20261016/media-image-160x48.png +0 -0
  23. package/docs/work-orders/evidence/instrument-media-live-20261016/media-image-60x20.png +0 -0
  24. package/docs/work-orders/evidence/instrument-media-live-20261016/media-image-80x24.png +0 -0
  25. package/docs/work-orders/evidence/instrument-media-live-20261016/media-progress-collapsed.png +0 -0
  26. package/docs/work-orders/evidence/instrument-media-live-20261016/media-video-frame-a.png +0 -0
  27. package/docs/work-orders/evidence/instrument-media-live-20261016/media-video-frame-b.png +0 -0
  28. package/docs/work-orders/evidence/instrument-media-live-20261016/media-voice-autocomplete-junction.png +0 -0
  29. package/docs/work-orders/evidence/instrument-media-live-20261016/report.json +813 -0
  30. package/docs/work-orders/evidence/instrument-media-verification-20261016.json +59 -0
  31. package/docs/work-orders/evidence/native-interface-state-verification-20261016.json +59 -0
  32. package/docs/work-orders/evidence/native-straight-boundaries-verification-20261016.json +36 -0
  33. package/docs/work-orders/evidence/project-media-unification-20261016.json +108 -0
  34. package/docs/work-orders/instrument-tui.md +17 -7
  35. package/npm-shrinkwrap.json +2 -2
  36. 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
@@ -1784,17 +2380,9 @@ function terminalCellWidth(value) {
1784
2380
  var graphemes2 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1785
2381
  function formatSignalDivider(width, geometry = { direction: "top" }) {
1786
2382
  const cells = Math.max(0, Math.floor(width));
1787
- if (cells < 8) return "─".repeat(cells);
1788
- const buffer = Math.max(1, Math.floor(geometry.buffer ?? 2));
1789
- const requestedRight = geometry.direction === "bottom" && geometry.sidebarColumn !== void 0 ? geometry.sidebarColumn : (geometry.rightTextStart ?? cells) - buffer - 1;
1790
- const right = Math.max(4, Math.min(cells - 2, Math.floor(requestedRight)));
1791
- const requestedLeft = Math.floor(geometry.leftTextEnd ?? 0) + buffer;
1792
- if (geometry.direction === "top" && requestedLeft > right - 2) return "─".repeat(cells);
1793
- const left = Math.max(1, Math.min(right - 2, requestedLeft));
1794
- const outside = geometry.direction === "top" ? "▁" : "▔";
1795
- const inside = geometry.direction === "top" ? "▔" : "▁";
1796
- const fall = geometry.direction === "top" ? "╱" : "╲", rise = geometry.direction === "top" ? "╲" : "╱";
1797
- return outside.repeat(left) + fall + inside.repeat(right - left - 1) + rise + outside.repeat(cells - right - 1);
2383
+ const column = Math.floor(geometry.sidebarColumn ?? -1);
2384
+ if (!Number.isFinite(column) || column < 0 || column >= cells) return "─".repeat(cells);
2385
+ return "─".repeat(column) + (geometry.direction === "top" ? "┬" : "┴") + "─".repeat(cells - column - 1);
1798
2386
  }
1799
2387
  function fitInstrumentText(value, cells, marker = "~") {
1800
2388
  const text = stripTerminalSequences(normalizeTerminalLine(value));
@@ -1856,7 +2444,7 @@ function instrumentStatus(value) {
1856
2444
  // packages/cli/src/tui/native/components/instruments.ts
1857
2445
  var foreground = RGBA2.defaultForeground();
1858
2446
  var background = RGBA2.defaultBackground();
1859
- var SectionBanner = class extends TextRenderable3 {
2447
+ var SectionBanner = class extends TextRenderable4 {
1860
2448
  constructor(renderer2, title, id, activate) {
1861
2449
  super(renderer2, {
1862
2450
  id,
@@ -1911,7 +2499,7 @@ var SectionBanner = class extends TextRenderable3 {
1911
2499
  if (this.plainText !== next) this.content = next;
1912
2500
  }
1913
2501
  };
1914
- var TelemetryRow = class extends TextRenderable3 {
2502
+ var TelemetryRow = class extends TextRenderable4 {
1915
2503
  constructor(renderer2, label, id, reserved = 12) {
1916
2504
  super(renderer2, {
1917
2505
  id,
@@ -1957,7 +2545,7 @@ var SignalMeter = class extends TelemetryRow {
1957
2545
  this.update(formatSignalMeter(value, maximum));
1958
2546
  }
1959
2547
  };
1960
- var InstrumentDivider = class extends TextRenderable3 {
2548
+ var InstrumentDivider = class extends TextRenderable4 {
1961
2549
  constructor(renderer2, actions) {
1962
2550
  super(renderer2, {
1963
2551
  id: "omnius-instrument-divider",
@@ -2054,17 +2642,17 @@ var InstrumentDivider = class extends TextRenderable3 {
2054
2642
  this.attributes = this.focused ? TextAttributes5.INVERSE : TextAttributes5.DIM;
2055
2643
  }
2056
2644
  };
2057
- var DeviceMonitor = class extends BoxRenderable4 {
2645
+ var DeviceMonitor = class extends BoxRenderable5 {
2058
2646
  constructor(renderer2, action) {
2059
2647
  super(renderer2, { id: "omnius-devices", width: "100%", height: 5, minHeight: 5, maxHeight: "60%", flexShrink: 1, flexDirection: "column", visible: false });
2060
2648
  this.action = action;
2061
2649
  this.heading = new SectionBanner(renderer2, "DEVICES", "omnius-devices:heading", () => this.toggle());
2062
2650
  this.heading.update("DEVICES", false);
2063
2651
  this.add(this.heading);
2064
- 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 });
2065
2653
  this.add(this.summary);
2066
- 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 });
2067
- 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 });
2068
2656
  this.add(this.compactLoad);
2069
2657
  this.add(this.compactMemory);
2070
2658
  this.scroll = new ScrollBoxRenderable3(renderer2, {
@@ -2082,7 +2670,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2082
2670
  contentOptions: { flexDirection: "column" }
2083
2671
  });
2084
2672
  this.add(this.scroll);
2085
- this.notes = new TextRenderable3(renderer2, {
2673
+ this.notes = new TextRenderable4(renderer2, {
2086
2674
  id: "omnius-devices:notes",
2087
2675
  content: "",
2088
2676
  width: "100%",
@@ -2094,7 +2682,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2094
2682
  truncate: true
2095
2683
  });
2096
2684
  this.scroll.add(this.notes);
2097
- 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 });
2098
2686
  this.add(controls);
2099
2687
  this.policy = this.button(renderer2, "omnius-devices:policy", "GPU POLICY", () => {
2100
2688
  if (this.canSelect()) this.action?.("/gpu");
@@ -2118,7 +2706,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2118
2706
  historySource = "";
2119
2707
  rows = /* @__PURE__ */ new Map();
2120
2708
  button(renderer2, id, label, activate) {
2121
- const button = new TextRenderable3(renderer2, {
2709
+ const button = new TextRenderable4(renderer2, {
2122
2710
  id,
2123
2711
  content: label,
2124
2712
  height: 1,
@@ -2181,7 +2769,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2181
2769
  for (const device of snapshot?.devices ?? []) {
2182
2770
  let row = this.rows.get(device.uuid);
2183
2771
  if (!row) {
2184
- const root = new BoxRenderable4(this._ctx, {
2772
+ const root = new BoxRenderable5(this._ctx, {
2185
2773
  id: `omnius-device:${device.uuid}`,
2186
2774
  width: "100%",
2187
2775
  flexDirection: "column",
@@ -2200,9 +2788,9 @@ var DeviceMonitor = class extends BoxRenderable4 {
2200
2788
  title.on(RenderableEvents3.FOCUSED, () => this.scroll.scrollChildIntoView(root.id));
2201
2789
  const load = new TelemetryRow(this._ctx, "LOAD", `${root.id}:load`, 16);
2202
2790
  const memory = new TelemetryRow(this._ctx, "VRAM", `${root.id}:memory`, 16);
2203
- 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 });
2204
2792
  const loadGraph = plot("load"), memoryGraph = plot("memory"), history = new DeviceHistory();
2205
- const reason = new TextRenderable3(this._ctx, {
2793
+ const reason = new TextRenderable4(this._ctx, {
2206
2794
  id: `${root.id}:reason`,
2207
2795
  content: "",
2208
2796
  width: "100%",
@@ -2249,6 +2837,12 @@ var DeviceMonitor = class extends BoxRenderable4 {
2249
2837
  this.fitHeight(this.availableRows + 2);
2250
2838
  this.refresh();
2251
2839
  }
2840
+ get isExpanded() {
2841
+ return this.expanded;
2842
+ }
2843
+ setExpanded(expanded) {
2844
+ if (this.expanded !== expanded) this.toggle();
2845
+ }
2252
2846
  fitHeight(railRows) {
2253
2847
  this.availableRows = Math.max(0, railRows - 2);
2254
2848
  this.visible = Boolean(this.snapshot) && this.availableRows >= 3;
@@ -2307,7 +2901,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2307
2901
  ];
2308
2902
  }
2309
2903
  };
2310
- var ChannelMap = class extends BoxRenderable4 {
2904
+ var ChannelMap = class extends BoxRenderable5 {
2311
2905
  constructor(renderer2, id, inspect, reveal) {
2312
2906
  super(renderer2, {
2313
2907
  id,
@@ -2339,7 +2933,7 @@ var ChannelMap = class extends BoxRenderable4 {
2339
2933
  if (!this.channels.has(agent.id))
2340
2934
  this.channels.set(agent.id, this.channels.size + 1);
2341
2935
  if (!this.rows.has(agent.id)) {
2342
- const row = new TextRenderable3(this._ctx, {
2936
+ const row = new TextRenderable4(this._ctx, {
2343
2937
  id: `${this.id}:channel:${agent.id}`,
2344
2938
  content: "",
2345
2939
  width: "100%",
@@ -2405,7 +2999,7 @@ var ChannelMap = class extends BoxRenderable4 {
2405
2999
  }
2406
3000
  }
2407
3001
  };
2408
- var InstrumentRail = class extends BoxRenderable4 {
3002
+ var InstrumentRail = class extends BoxRenderable5 {
2409
3003
  scroll;
2410
3004
  inferenceHeader;
2411
3005
  sessionHeader;
@@ -2459,7 +3053,7 @@ var InstrumentRail = class extends BoxRenderable4 {
2459
3053
  () => this.reveal(this.inferenceHeader)
2460
3054
  );
2461
3055
  this.scroll.add(this.inferenceHeader);
2462
- this.inference = new BoxRenderable4(renderer2, {
3056
+ this.inference = new BoxRenderable5(renderer2, {
2463
3057
  id: `${id}:inference-body`,
2464
3058
  width: "100%",
2465
3059
  flexDirection: "column",
@@ -2474,7 +3068,7 @@ var InstrumentRail = class extends BoxRenderable4 {
2474
3068
  };
2475
3069
  addRow(this.inference, "route", "ROUTE");
2476
3070
  addRow(this.inference, "backend", "BACKEND");
2477
- this.model = new TextRenderable3(renderer2, {
3071
+ this.model = new TextRenderable4(renderer2, {
2478
3072
  id: `${id}:model`,
2479
3073
  content: "MODEL --",
2480
3074
  width: "100%",
@@ -2503,7 +3097,7 @@ var InstrumentRail = class extends BoxRenderable4 {
2503
3097
  });
2504
3098
  this.contextHeader.on(RenderableEvents3.FOCUSED, () => this.reveal(this.contextHeader));
2505
3099
  this.inference.add(this.contextHeader);
2506
- 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 });
2507
3101
  this.inference.add(this.contextDetails);
2508
3102
  this.contextHeader.update("CONTEXT DETAILS", false);
2509
3103
  addRow(this.contextDetails, "messages", "MESSAGES");
@@ -2522,7 +3116,7 @@ var InstrumentRail = class extends BoxRenderable4 {
2522
3116
  () => this.reveal(this.sessionHeader)
2523
3117
  );
2524
3118
  this.scroll.add(this.sessionHeader);
2525
- this.session = new BoxRenderable4(renderer2, {
3119
+ this.session = new BoxRenderable5(renderer2, {
2526
3120
  id: `${id}:session-body`,
2527
3121
  width: "100%",
2528
3122
  flexDirection: "column",
@@ -2605,7 +3199,7 @@ var InstrumentRail = class extends BoxRenderable4 {
2605
3199
  applyTheme(colors) {
2606
3200
  this.backgroundColor = colors.background;
2607
3201
  const visit = (node) => {
2608
- if (node instanceof TextRenderable3) {
3202
+ if (node instanceof TextRenderable4) {
2609
3203
  node.fg = colors.foreground;
2610
3204
  node.bg = colors.background;
2611
3205
  }
@@ -2624,6 +3218,25 @@ var InstrumentRail = class extends BoxRenderable4 {
2624
3218
  body.visible
2625
3219
  );
2626
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
+ }
2627
3240
  focusTargets() {
2628
3241
  if (!this.visible) return [];
2629
3242
  return [
@@ -3448,6 +4061,7 @@ var ALIASES = {
3448
4061
  update: ["upgrade"],
3449
4062
  indicator: ["tray"],
3450
4063
  listen: ["mic"],
4064
+ sound: ["audio"],
3451
4065
  pointcloud: ["points", "pcl", "point"],
3452
4066
  neovim: ["nvim"],
3453
4067
  apikey: ["key"],
@@ -4082,7 +4696,7 @@ var VimComposer = class {
4082
4696
  };
4083
4697
 
4084
4698
  // packages/cli/src/tui/native/components/selection-menu.ts
4085
- 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";
4086
4700
  var NativeSelectionMenu = class {
4087
4701
  constructor(renderer2, colors, copy, restoreFocus) {
4088
4702
  this.renderer = renderer2;
@@ -4110,7 +4724,7 @@ var NativeSelectionMenu = class {
4110
4724
  this.selectedText = text;
4111
4725
  this.anchor = { x, y };
4112
4726
  this.previousFocus = this.renderer.currentFocusedRenderable;
4113
- this.overlay = new BoxRenderable5(this.renderer, {
4727
+ this.overlay = new BoxRenderable6(this.renderer, {
4114
4728
  id: "omnius-selection-menu",
4115
4729
  position: "absolute",
4116
4730
  left: 0,
@@ -4126,7 +4740,7 @@ var NativeSelectionMenu = class {
4126
4740
  event.stopPropagation();
4127
4741
  }
4128
4742
  });
4129
- this.panel = new BoxRenderable5(this.renderer, {
4743
+ this.panel = new BoxRenderable6(this.renderer, {
4130
4744
  id: "omnius-selection-menu-panel",
4131
4745
  position: "absolute",
4132
4746
  width: 14,
@@ -4140,7 +4754,7 @@ var NativeSelectionMenu = class {
4140
4754
  event.stopPropagation();
4141
4755
  }
4142
4756
  });
4143
- const action = new TextRenderable4(this.renderer, {
4757
+ const action = new TextRenderable5(this.renderer, {
4144
4758
  id: "omnius-selection-copy",
4145
4759
  content: "COPY",
4146
4760
  height: 1,
@@ -4189,7 +4803,7 @@ var NativeSelectionMenu = class {
4189
4803
  if (!this.panel) return;
4190
4804
  this.panel.backgroundColor = colors.background;
4191
4805
  this.frame?.setColors(colors);
4192
- for (const child of this.panel.getChildren()) if (child instanceof TextRenderable4) {
4806
+ for (const child of this.panel.getChildren()) if (child instanceof TextRenderable5) {
4193
4807
  child.fg = colors.foreground;
4194
4808
  child.bg = colors.background;
4195
4809
  }
@@ -4215,8 +4829,8 @@ var NativeSelectionMenu = class {
4215
4829
  };
4216
4830
 
4217
4831
  // packages/cli/src/tui/native/components/tab-strip.ts
4218
- import { BoxRenderable as BoxRenderable6, CliRenderEvents as CliRenderEvents4, ScrollBoxRenderable as ScrollBoxRenderable4, SliderRenderable, TextAttributes as TextAttributes7, TextRenderable as TextRenderable5 } from "@opentui/core";
4219
- 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 {
4220
4834
  constructor(renderer2, options) {
4221
4835
  super(renderer2, {
4222
4836
  id: options.id,
@@ -4295,7 +4909,7 @@ var NativeTabStrip = class extends BoxRenderable6 {
4295
4909
  for (const [index, action] of items.entries()) {
4296
4910
  let entry = this.buttons.get(action.id);
4297
4911
  if (!entry) {
4298
- const button = new TextRenderable5(this.renderer, {
4912
+ const button = new TextRenderable6(this.renderer, {
4299
4913
  id: `${this.options.prefix}:${action.id}`,
4300
4914
  content: "",
4301
4915
  height: 1,
@@ -4328,7 +4942,7 @@ var NativeTabStrip = class extends BoxRenderable6 {
4328
4942
  entry.blocked = busy || Boolean(action.disabled);
4329
4943
  entry.button.focusable = entry.active || !entry.blocked;
4330
4944
  const label = plainText(action.label);
4331
- const content = entry.active ? `⟋⟋ ${label} ⟍⟍` : ` ${label} `;
4945
+ const content = ` ${label} `;
4332
4946
  if (entry.button.plainText !== content) entry.button.content = content;
4333
4947
  this.style(entry);
4334
4948
  if (this.scroll.getChildren()[index] !== entry.button) this.scroll.add(entry.button, index);
@@ -4382,6 +4996,10 @@ var NativeTabStrip = class extends BoxRenderable6 {
4382
4996
  styleNativeScrollbars(this.scroll, colors);
4383
4997
  styleNativeSlider(this.rail, colors);
4384
4998
  }
4999
+ restoreOffset(offset2) {
5000
+ this.reveal = void 0;
5001
+ this.scroll.scrollLeft = offset2;
5002
+ }
4385
5003
  destroySelf() {
4386
5004
  this.renderer.off(CliRenderEvents4.FRAME, this.syncScroll);
4387
5005
  super.destroySelf();
@@ -4395,13 +5013,13 @@ var NativeFrontend = class {
4395
5013
  this.renderer = renderer2;
4396
5014
  this.emit = emit;
4397
5015
  const colors = palette(initialTheme);
4398
- 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() };
4399
5017
  this.disposeSelectionContrast = installDefaultSelectionContrast(renderer2);
4400
5018
  this.clipboard = options.clipboard ?? createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
4401
5019
  this.selectionMenu = new NativeSelectionMenu(renderer2, colors, (text) => {
4402
5020
  void this.copy(text);
4403
5021
  }, () => this.restoreFocus());
4404
- this.root = new BoxRenderable7(renderer2, {
5022
+ this.root = new BoxRenderable8(renderer2, {
4405
5023
  id: "omnius-native",
4406
5024
  width: "100%",
4407
5025
  height: "100%",
@@ -4409,7 +5027,7 @@ var NativeFrontend = class {
4409
5027
  backgroundColor: colors.background
4410
5028
  });
4411
5029
  renderer2.root.add(this.root);
4412
- this.header = new BoxRenderable7(renderer2, {
5030
+ this.header = new BoxRenderable8(renderer2, {
4413
5031
  id: "omnius-header",
4414
5032
  width: "100%",
4415
5033
  minWidth: 0,
@@ -4424,8 +5042,7 @@ var NativeFrontend = class {
4424
5042
  if (this.header.height > 0) buffer.drawText(
4425
5043
  formatSignalDivider(this.header.width, {
4426
5044
  direction: "top",
4427
- leftTextEnd: this.identity.x - this.header.x + Math.min(this.identity.width, terminalCellWidth(this.identity.plainText)),
4428
- rightTextStart: Math.min(this.detailButton.x, this.headerActions.visible ? this.headerActions.x : this.detailButton.x) - this.header.x
5045
+ sidebarColumn: this.instrumentDivider.visible ? this.instrumentDivider.x - this.header.x : void 0
4429
5046
  }),
4430
5047
  this.header.x,
4431
5048
  this.header.y + this.header.height - 1,
@@ -4434,9 +5051,9 @@ var NativeFrontend = class {
4434
5051
  TextAttributes8.DIM
4435
5052
  );
4436
5053
  };
4437
- 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 });
4438
5055
  this.header.add(headerRow);
4439
- this.identity = new TextRenderable6(renderer2, {
5056
+ this.identity = new TextRenderable7(renderer2, {
4440
5057
  id: "omnius-identity",
4441
5058
  content: "Omnius",
4442
5059
  fg: colors.foreground,
@@ -4474,9 +5091,9 @@ var NativeFrontend = class {
4474
5091
  verticalScrollbarOptions: { visible: false }
4475
5092
  });
4476
5093
  headerRow.add(this.headerActions);
4477
- 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 });
4478
5095
  this.header.add(this.headerPanel);
4479
- this.detailButton = new TextRenderable6(renderer2, {
5096
+ this.detailButton = new TextRenderable7(renderer2, {
4480
5097
  id: "omnius-details-toggle",
4481
5098
  content: "+ DETAILS",
4482
5099
  height: 1,
@@ -4509,7 +5126,7 @@ var NativeFrontend = class {
4509
5126
  control.attributes = attributes;
4510
5127
  });
4511
5128
  }
4512
- this.sessionRow = new BoxRenderable7(renderer2, {
5129
+ this.sessionRow = new BoxRenderable8(renderer2, {
4513
5130
  id: "omnius-sessions-row",
4514
5131
  width: "100%",
4515
5132
  height: 1,
@@ -4527,7 +5144,7 @@ var NativeFrontend = class {
4527
5144
  activate: (id) => this.emitAction("session", id)
4528
5145
  });
4529
5146
  this.sessionRow.add(this.sessions);
4530
- this.newSession = new TextRenderable6(renderer2, {
5147
+ this.newSession = new TextRenderable7(renderer2, {
4531
5148
  id: "omnius-session-new",
4532
5149
  content: "+ NEW",
4533
5150
  width: 5,
@@ -4556,13 +5173,15 @@ var NativeFrontend = class {
4556
5173
  this.newSession.on("focused", styleNew);
4557
5174
  this.newSession.on("blurred", styleNew);
4558
5175
  this.sessionRow.add(this.newSession);
4559
- 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 });
4560
5177
  this.root.add(this.routeWarning);
4561
- 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 });
4562
5179
  this.tasks = new NativeBlockList(this.context, this.taskPane);
4563
5180
  this.root.add(this.taskPane);
4564
- 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 });
4565
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);
4566
5185
  this.scroll = new ScrollBoxRenderable5(renderer2, {
4567
5186
  id: "omnius-conversation",
4568
5187
  flexGrow: 1,
@@ -4618,7 +5237,7 @@ var NativeFrontend = class {
4618
5237
  }
4619
5238
  } }
4620
5239
  });
4621
- this.body.add(this.scroll);
5240
+ this.transcriptPane.add(this.scroll);
4622
5241
  this.content = new NativeBlockList(this.context, this.scroll);
4623
5242
  this.instrumentDivider = new InstrumentDivider(renderer2, {
4624
5243
  resize: (column) => this.resizeInstruments(column),
@@ -4636,7 +5255,7 @@ var NativeFrontend = class {
4636
5255
  this.root.onMouseUp = (event) => this.instrumentDivider.handleRelease(event);
4637
5256
  this.instruments = new InstrumentRail(renderer2, { colors, onInspectAgent: (id) => this.emitAction("view", id), onAction: (command2) => this.emitAction(command2) });
4638
5257
  this.body.add(this.instruments);
4639
- this.embeddedPane = new BoxRenderable7(renderer2, {
5258
+ this.embeddedPane = new BoxRenderable8(renderer2, {
4640
5259
  id: "omnius-editor-pane",
4641
5260
  width: "50%",
4642
5261
  flexShrink: 0,
@@ -4647,7 +5266,7 @@ var NativeFrontend = class {
4647
5266
  visible: false
4648
5267
  });
4649
5268
  this.body.add(this.embeddedPane);
4650
- this.liveButton = new TextRenderable6(renderer2, {
5269
+ this.liveButton = new TextRenderable7(renderer2, {
4651
5270
  id: "omnius-live-button",
4652
5271
  content: "↓ Live",
4653
5272
  width: "100%",
@@ -4669,9 +5288,9 @@ var NativeFrontend = class {
4669
5288
  }
4670
5289
  });
4671
5290
  this.liveButton.focusable = true;
4672
- this.root.add(this.liveButton);
4673
- 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 });
4674
- 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);
4675
5294
  this.suggestions = new NativeSelectRenderable(renderer2, {
4676
5295
  id: "omnius-suggestions",
4677
5296
  width: "100%",
@@ -4688,10 +5307,10 @@ var NativeFrontend = class {
4688
5307
  });
4689
5308
  this.suggestions.onActivate = () => this.acceptSuggestion();
4690
5309
  this.suggestions.on("itemSelected", () => this.acceptSuggestion());
4691
- this.root.add(this.suggestions);
4692
- this.status = new BoxRenderable7(renderer2, { id: "omnius-live-status", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2, visible: false });
4693
- this.root.add(this.status);
4694
- 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, {
4695
5314
  id: "omnius-input-box",
4696
5315
  width: "100%",
4697
5316
  flexShrink: 0,
@@ -4700,7 +5319,7 @@ var NativeFrontend = class {
4700
5319
  flexDirection: "column"
4701
5320
  });
4702
5321
  this.root.add(this.footer);
4703
- this.footerRule = new BoxRenderable7(renderer2, {
5322
+ this.footerRule = new BoxRenderable8(renderer2, {
4704
5323
  id: "omnius-input-rule",
4705
5324
  width: "100%",
4706
5325
  height: 1,
@@ -4725,13 +5344,12 @@ var NativeFrontend = class {
4725
5344
  this.footerRule.renderBefore = (buffer) => {
4726
5345
  buffer.drawText(formatSignalDivider(this.footerRule.width, {
4727
5346
  direction: "bottom",
4728
- leftTextEnd: this.tabs.visible ? this.tabs.x - this.footerRule.x + this.tabs.width : 0,
4729
- sidebarColumn: this.instrumentDivider.visible && this.instruments.visible ? this.instrumentDivider.x - this.footerRule.x : void 0
5347
+ sidebarColumn: this.instrumentDivider.visible ? this.instrumentDivider.x - this.footerRule.x : void 0
4730
5348
  }), this.footerRule.x, this.footerRule.y, this.context.colors.foreground, this.context.colors.background, TextAttributes8.DIM);
4731
5349
  };
4732
- 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 });
4733
5351
  this.footer.add(inputRow);
4734
- 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 });
4735
5353
  inputRow.add(this.prompt);
4736
5354
  this.editor = new TextareaRenderable2(renderer2, {
4737
5355
  id: "omnius-input",
@@ -4751,7 +5369,7 @@ var NativeFrontend = class {
4751
5369
  onCursorChange: () => this.editorChanged(),
4752
5370
  onSubmit: () => this.submit()
4753
5371
  });
4754
- this.vimMode = new TextRenderable6(renderer2, {
5372
+ this.vimMode = new TextRenderable7(renderer2, {
4755
5373
  id: "omnius-vim-mode",
4756
5374
  content: "INSERT",
4757
5375
  height: 1,
@@ -4772,17 +5390,17 @@ var NativeFrontend = class {
4772
5390
  this.renderer.requestRender();
4773
5391
  });
4774
5392
  inputRow.add(this.editor);
4775
- 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 });
4776
5394
  inputRow.add(this.enhance);
4777
- 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 });
4778
5396
  this.status.add(this.activity);
4779
- this.media = new BoxRenderable7(renderer2, { id: "omnius-media", height: 1, minWidth: 0, flexShrink: 1, flexDirection: "row", gap: 1, visible: false });
4780
- 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 });
4781
5399
  this.media.add(this.mediaText);
4782
- 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 });
4783
5401
  this.media.add(this.micTrack);
4784
5402
  this.status.add(this.media);
4785
- this.copyFeedback = new TextRenderable6(renderer2, {
5403
+ this.copyFeedback = new TextRenderable7(renderer2, {
4786
5404
  id: "omnius-copy-feedback",
4787
5405
  content: "",
4788
5406
  height: 1,
@@ -4793,14 +5411,14 @@ var NativeFrontend = class {
4793
5411
  visible: false
4794
5412
  });
4795
5413
  this.status.add(this.copyFeedback);
4796
- this.metricsRow = new BoxRenderable7(renderer2, { id: "omnius-metrics-row", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 1 });
4797
- 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 });
4798
5416
  this.metricsRow.add(this.keyHints);
4799
- 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 });
4800
5418
  this.metricsRow.add(this.metricsStartActions);
4801
- 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 });
4802
5420
  this.metricsRow.add(this.metrics);
4803
- 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 });
4804
5422
  this.metricsRow.add(this.metricsEndActions);
4805
5423
  this.root.add(this.metricsRow);
4806
5424
  this.modals = new NativeModalStack(this.context, () => this.restoreFocus());
@@ -4833,6 +5451,7 @@ var NativeFrontend = class {
4833
5451
  taskPane;
4834
5452
  tasks;
4835
5453
  body;
5454
+ transcriptPane;
4836
5455
  instruments;
4837
5456
  instrumentDivider;
4838
5457
  detailButton;
@@ -4884,6 +5503,9 @@ var NativeFrontend = class {
4884
5503
  destroyed = false;
4885
5504
  pendingScrollRestore;
4886
5505
  viewScroll = /* @__PURE__ */ new Map();
5506
+ viewDisclosures = /* @__PURE__ */ new Map();
5507
+ pendingDisclosures;
5508
+ pendingInterface;
4887
5509
  embeddedId;
4888
5510
  selectionArmed = false;
4889
5511
  update(state, patch) {
@@ -4893,7 +5515,9 @@ var NativeFrontend = class {
4893
5515
  const viewChanged = previousView !== void 0 && previousView !== currentView;
4894
5516
  if (viewChanged) {
4895
5517
  this.viewScroll.set(previousView, { top: this.scroll.scrollTop, live: this.offset === 0 });
5518
+ this.viewDisclosures.set(previousView, this.content.captureDisclosures(this.tasks.captureDisclosures()));
4896
5519
  this.pendingScrollRestore = this.viewScroll.get(currentView) ?? { top: 0, live: true };
5520
+ this.pendingDisclosures = this.viewDisclosures.get(currentView);
4897
5521
  this.newEntries = 0;
4898
5522
  }
4899
5523
  this.state = state;
@@ -4928,6 +5552,11 @@ var NativeFrontend = class {
4928
5552
  if (this.offset > 0 && !viewChanged && !patch.reset) this.newEntries += patch.upsert.filter((block) => !this.content.views.has(block.id)).length;
4929
5553
  this.content.patch(patch);
4930
5554
  }
5555
+ if (this.pendingDisclosures) {
5556
+ this.content.restoreDisclosures(this.pendingDisclosures);
5557
+ this.tasks.restoreDisclosures(this.pendingDisclosures);
5558
+ this.pendingDisclosures = void 0;
5559
+ }
4931
5560
  this.queue.content = state.queued.length ? `Queued (${state.queued.length}) · ${state.queued.join(" · ")}` : "";
4932
5561
  this.queue.visible = state.queued.length > 0;
4933
5562
  const suggestionSignature = JSON.stringify(state.suggestions);
@@ -4976,7 +5605,7 @@ var NativeFrontend = class {
4976
5605
  for (const action of actions) {
4977
5606
  const colors = this.context.colors;
4978
5607
  const attributes = action.disabled ? TextAttributes8.DIM : action.active ? TextAttributes8.BOLD | TextAttributes8.UNDERLINE : 0;
4979
- const button = new TextRenderable6(this.renderer, {
5608
+ const button = new TextRenderable7(this.renderer, {
4980
5609
  id: `${prefix}:${action.id}`,
4981
5610
  content: styledText(action.label),
4982
5611
  flexShrink: 0,
@@ -5122,14 +5751,17 @@ var NativeFrontend = class {
5122
5751
  const shown = !this.embeddedPane.visible && (wide ? this.detailsOpen ?? true : this.narrowDetailsOpen);
5123
5752
  const horizontal = wide || this.embeddedPane.visible && width >= 80;
5124
5753
  this.body.flexDirection = horizontal ? "row" : "column";
5125
- this.scroll.height = horizontal ? "100%" : "auto";
5126
- 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;
5127
5758
  this.instruments.visible = shown;
5128
5759
  this.instrumentDivider.visible = wide && !this.embeddedPane.visible;
5129
5760
  this.instrumentDivider.setCollapsed(!shown);
5130
5761
  const railWidth = Math.max(24, Math.min(width - 41, this.sidebarWidth ?? Math.floor(width * 0.38)));
5131
5762
  this.instruments.width = wide ? railWidth : "100%";
5132
- 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%";
5133
5765
  const inspectorHeight = Math.max(4, Math.min(9, Math.floor(this.renderer.terminalHeight / 3)));
5134
5766
  this.instruments.height = wide ? "100%" : inspectorHeight;
5135
5767
  this.instruments.maxHeight = wide ? "100%" : inspectorHeight;
@@ -5177,6 +5809,13 @@ var NativeFrontend = class {
5177
5809
  }
5178
5810
  onFrame = () => {
5179
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
+ }
5180
5819
  if (this.pendingScrollRestore) {
5181
5820
  const restore = this.pendingScrollRestore;
5182
5821
  this.pendingScrollRestore = void 0;
@@ -5186,10 +5825,11 @@ var NativeFrontend = class {
5186
5825
  const height = Math.max(1, Math.min(8, this.editor.editorView.getTotalVirtualLineCount(), this.renderer.terminalHeight - 8));
5187
5826
  if (height !== this.editor.height) this.editor.height = height;
5188
5827
  this.layoutTabStrips();
5189
- const offset = this.offset;
5190
- if (offset === 0) this.newEntries = 0;
5191
- this.liveButton.visible = offset > 0;
5192
- 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";
5193
5833
  if (this.liveButton.plainText !== liveLabel) this.liveButton.content = liveLabel;
5194
5834
  const event = { type: "viewport", offset: this.scroll.scrollTop, height: this.scroll.viewport.height, width: this.scroll.viewport.width, total: this.scroll.scrollHeight };
5195
5835
  const signature = JSON.stringify(event);
@@ -5456,7 +6096,7 @@ var NativeFrontend = class {
5456
6096
  const target = targets[(current + direction + targets.length) % targets.length];
5457
6097
  target.focus();
5458
6098
  if (target.id.startsWith("header:")) this.headerActions.scrollChildIntoView(target.id);
5459
- 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:")) {
5460
6100
  let block = target;
5461
6101
  while (block && !block.id.startsWith("block:")) block = block.parent;
5462
6102
  if (block) this.scroll.scrollChildIntoView(block.id);
@@ -5628,6 +6268,52 @@ var NativeFrontend = class {
5628
6268
  this.restoreFocus();
5629
6269
  }
5630
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
+ }
5631
6317
  async dispose() {
5632
6318
  if (this.destroyed) return;
5633
6319
  this.destroyed = true;
@@ -5638,9 +6324,14 @@ var NativeFrontend = class {
5638
6324
  this.renderer.keyInput.off("paste", this.onPaste);
5639
6325
  this.renderer.off(CliRenderEvents5.FRAME, this.onFrame);
5640
6326
  this.renderer.off(CliRenderEvents5.RESIZE, this.onResize);
6327
+ const mediaClosed = [...this.context.mediaViews ?? []].map((media) => {
6328
+ media.pausePlayback();
6329
+ return media.closed;
6330
+ });
5641
6331
  this.modals.destroy();
5642
6332
  this.content.destroy();
5643
6333
  this.tasks.destroy();
6334
+ await Promise.allSettled(mediaClosed);
5644
6335
  this.root.destroyRecursively();
5645
6336
  this.context.syntax.destroy();
5646
6337
  await this.clipboard.dispose();
@@ -5660,10 +6351,10 @@ function installTerminalCursorDefaults(renderer2, session) {
5660
6351
  if (disposed || session.disposed) return Promise.resolve();
5661
6352
  requested++;
5662
6353
  if (pending) return pending;
5663
- let resolve;
6354
+ let resolve2;
5664
6355
  let reject;
5665
6356
  const completion = new Promise((done, fail2) => {
5666
- resolve = done;
6357
+ resolve2 = done;
5667
6358
  reject = fail2;
5668
6359
  });
5669
6360
  pending = completion;
@@ -5676,7 +6367,7 @@ function installTerminalCursorDefaults(renderer2, session) {
5676
6367
  restored = generation;
5677
6368
  }
5678
6369
  pending = void 0;
5679
- resolve();
6370
+ resolve2();
5680
6371
  } catch (error) {
5681
6372
  pending = void 0;
5682
6373
  reject(error);
@@ -5742,11 +6433,24 @@ async function command(message) {
5742
6433
  frontend.command(message.command);
5743
6434
  return;
5744
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
+ }
5745
6448
  if (message.type === "state") {
5746
6449
  frontend.update(message.snapshot, message.content);
5747
6450
  renderer.requestRender();
5748
6451
  await renderer.idle();
5749
6452
  } else if (message.type === "suspend") {
6453
+ frontend.pauseMedia();
5750
6454
  await cursorDefaults.restore();
5751
6455
  await renderer.suspend();
5752
6456
  } else if (message.type === "resume") {