omnius 1.0.867 → 1.0.869

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