omnius 1.0.864 → 1.0.866

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/index.js +2 -2
  2. package/dist/tui/native/worker.js +728 -166
  3. package/dist/update-worker.js +21 -0
  4. package/docs/DISCOVERY.json +1680 -538
  5. package/docs/DISCOVERY.md +32 -0
  6. package/docs/work-orders/2026-10-16-browser-instrument-dashboard.md +91 -0
  7. package/docs/work-orders/2026-10-16-interface-delivery.md +53 -0
  8. package/docs/work-orders/desktop-projects.md +9 -3
  9. package/docs/work-orders/evidence/browser-controls-deployment-20261016.json +28 -0
  10. package/docs/work-orders/evidence/browser-controls-package-verification-20261016.json +68 -0
  11. package/docs/work-orders/evidence/desktop-indicator-registration-20261016.json +11 -6
  12. package/docs/work-orders/evidence/instrument-100x30.png +0 -0
  13. package/docs/work-orders/evidence/instrument-120x36.png +0 -0
  14. package/docs/work-orders/evidence/instrument-160x48.png +0 -0
  15. package/docs/work-orders/evidence/instrument-60x20.png +0 -0
  16. package/docs/work-orders/evidence/instrument-80x24.png +0 -0
  17. package/docs/work-orders/evidence/instrument-clean-transcript-120x36.png +0 -0
  18. package/docs/work-orders/evidence/instrument-collapse-120x36.png +0 -0
  19. package/docs/work-orders/evidence/instrument-devices-expanded-120x36.png +0 -0
  20. package/docs/work-orders/evidence/instrument-divider-collapsed-160x48.png +0 -0
  21. package/docs/work-orders/evidence/instrument-divider-resized-160x48.png +0 -0
  22. package/docs/work-orders/evidence/instrument-help-scroll-120x36.png +0 -0
  23. package/docs/work-orders/evidence/instrument-package-install-20261016-updated.json +149 -0
  24. package/docs/work-orders/evidence/instrument-popup-api-120x36.png +0 -0
  25. package/docs/work-orders/evidence/instrument-popup-api-input-120x36.png +0 -0
  26. package/docs/work-orders/evidence/instrument-popup-config-120x36.png +0 -0
  27. package/docs/work-orders/evidence/instrument-popup-dark-120x36.png +0 -0
  28. package/docs/work-orders/evidence/instrument-popup-dark-60x20.png +0 -0
  29. package/docs/work-orders/evidence/instrument-popup-light-120x36.png +0 -0
  30. package/docs/work-orders/evidence/instrument-popup-model-120x36.png +0 -0
  31. package/docs/work-orders/evidence/instrument-popup-network-120x36.png +0 -0
  32. package/docs/work-orders/evidence/instrument-production-terminal-live.json +3521 -2398
  33. package/docs/work-orders/evidence/instrument-report-broker-120x36.png +0 -0
  34. package/docs/work-orders/evidence/instrument-report-gpu-120x36.png +0 -0
  35. package/docs/work-orders/evidence/instrument-report-stats-120x36.png +0 -0
  36. package/docs/work-orders/evidence/instrument-selection-copied-120x36.png +0 -0
  37. package/docs/work-orders/evidence/instrument-selection-copy-menu-120x36.png +0 -0
  38. package/docs/work-orders/evidence/instrument-sessions-narrow-60x20.png +0 -0
  39. package/docs/work-orders/evidence/instrument-sessions-restored-120x36.png +0 -0
  40. package/docs/work-orders/evidence/instrument-terminal-light-120x36.png +0 -0
  41. package/docs/work-orders/evidence/instrument-transcript-selection-120x36.png +0 -0
  42. package/docs/work-orders/evidence/instrument-vim-composer-120x36.png +0 -0
  43. package/docs/work-orders/evidence/interface-daemon-deployment-20261016.json +56 -0
  44. package/docs/work-orders/evidence/interface-targeted-verification-20261016-final.json +463 -0
  45. package/docs/work-orders/evidence/native-session-lifecycle-verification-20261016.json +79 -0
  46. package/docs/work-orders/evidence/native-session-regression-verification-20261016.json +65 -0
  47. package/docs/work-orders/evidence/native-session-widget-verification-20261016.json +60 -0
  48. package/docs/work-orders/evidence/native-sessions-installed-pty-20261016.json +2608 -0
  49. package/docs/work-orders/evidence/native-sessions-production-pty-20261016.json +3348 -0
  50. package/docs/work-orders/evidence/native-sessions-production-vte-20261016.json +2032 -0
  51. package/docs/work-orders/evidence/session-tabs-installation-20261016.json +37 -0
  52. package/docs/work-orders/evidence/session-tabs-package-verification-20261016.json +50 -0
  53. package/docs/work-orders/evidence/tray-checked-menu-final.png +0 -0
  54. package/docs/work-orders/evidence/tray-project-3.png +0 -0
  55. package/docs/work-orders/evidence/tray-project-4.png +0 -0
  56. package/docs/work-orders/evidence/tray-project-5.png +0 -0
  57. package/docs/work-orders/evidence/tray-project-7.png +0 -0
  58. package/docs/work-orders/evidence/tray-projects-live.json +307 -47
  59. package/docs/work-orders/evidence/tray-projects-long-menu.png +0 -0
  60. package/docs/work-orders/evidence/tray-projects-menu.png +0 -0
  61. package/docs/work-orders/evidence/web-instrument-dark-desktop-20261016.png +0 -0
  62. package/docs/work-orders/evidence/web-instrument-dark-phone-20261016.png +0 -0
  63. package/docs/work-orders/evidence/web-instrument-dashboard-live-20261016.json +110 -0
  64. package/docs/work-orders/evidence/web-instrument-light-desktop-20261016.png +0 -0
  65. package/docs/work-orders/evidence/web-instrument-light-phone-20261016.png +0 -0
  66. package/docs/work-orders/evidence/web-media-runtime-verification-20261016.json +75 -0
  67. package/docs/work-orders/evidence/web-sidebar-send-dark-desktop-20261016.png +0 -0
  68. package/docs/work-orders/evidence/web-sidebar-send-light-phone-20261016.png +0 -0
  69. package/docs/work-orders/evidence/web-sidebar-send-repair-20261016.json +163 -0
  70. package/docs/work-orders/instrument-tui.md +66 -4
  71. package/npm-shrinkwrap.json +15 -15
  72. package/package.json +1 -1
@@ -2,19 +2,18 @@
2
2
  import { createWriteStream } from "node:fs";
3
3
  import { createInterface } from "node:readline";
4
4
  import { PassThrough } from "node:stream";
5
- import { CliRenderEvents as CliRenderEvents5, NativeSession, RGBA as RGBA3, createCliRenderer } from "@opentui/core";
5
+ import { CliRenderEvents as CliRenderEvents7, NativeSession, RGBA as RGBA3, createCliRenderer } from "@opentui/core";
6
6
 
7
7
  // packages/cli/src/tui/native/components/frontend.ts
8
8
  import {
9
- BoxRenderable as BoxRenderable5,
10
- CliRenderEvents as CliRenderEvents3,
9
+ BoxRenderable as BoxRenderable7,
10
+ CliRenderEvents as CliRenderEvents5,
11
11
  EmbeddedTerminalRenderable as EmbeddedTerminalRenderable2,
12
- ScrollBoxRenderable as ScrollBoxRenderable4,
12
+ ScrollBoxRenderable as ScrollBoxRenderable5,
13
13
  SyntaxStyle as SyntaxStyle2,
14
- TabSelectRenderable,
15
14
  TextareaRenderable as TextareaRenderable2,
16
- TextRenderable as TextRenderable4,
17
- TextAttributes as TextAttributes6,
15
+ TextRenderable as TextRenderable6,
16
+ TextAttributes as TextAttributes8,
18
17
  createClipboard,
19
18
  createHostClipboard,
20
19
  createRendererClipboardAdapter
@@ -799,49 +798,51 @@ function drawDotRail(buffer, x, y, length, horizontal, progress, colors) {
799
798
  function styleNativeScrollbars(scroll, colors) {
800
799
  for (const bar of [scroll.horizontalScrollBar, scroll.verticalScrollBar]) {
801
800
  bar.showArrows = false;
802
- const slider = bar.slider;
803
- slider.foregroundColor = colors.foreground;
804
- slider.backgroundColor = colors.background;
805
- slider.renderAfter = (buffer) => {
806
- const horizontal = slider.orientation === "horizontal";
807
- drawDotRail(
808
- buffer,
809
- slider.x,
810
- slider.y,
811
- horizontal ? slider.width : slider.height,
812
- horizontal,
813
- slider.max > slider.min ? (slider.value - slider.min) / (slider.max - slider.min) : 0,
814
- colors
815
- );
816
- };
817
- let dragging = false;
818
- const seek = (event) => {
819
- const horizontal = slider.orientation === "horizontal";
820
- const length = horizontal ? slider.width : slider.height;
821
- const offset = horizontal ? event.x - slider.x : event.y - slider.y;
822
- const progress = length > 1 ? Math.max(0, Math.min(1, offset / (length - 1))) : 0;
823
- slider.value = slider.min + progress * (slider.max - slider.min);
824
- event.preventDefault();
825
- event.stopPropagation();
826
- };
827
- slider.onMouseDown = (event) => {
828
- if (event.button === 0) {
829
- dragging = true;
830
- seek(event);
831
- }
832
- };
833
- slider.onMouseDrag = (event) => {
834
- if (dragging) seek(event);
835
- };
836
- slider.onMouseUp = (event) => {
837
- if (dragging) seek(event);
838
- dragging = false;
839
- };
840
- slider.onMouseDragEnd = () => {
841
- dragging = false;
842
- };
801
+ styleNativeSlider(bar.slider, colors);
843
802
  }
844
803
  }
804
+ function styleNativeSlider(slider, colors) {
805
+ slider.foregroundColor = colors.foreground;
806
+ slider.backgroundColor = colors.background;
807
+ slider.renderAfter = (buffer) => {
808
+ const horizontal = slider.orientation === "horizontal";
809
+ drawDotRail(
810
+ buffer,
811
+ slider.x,
812
+ slider.y,
813
+ horizontal ? slider.width : slider.height,
814
+ horizontal,
815
+ slider.max > slider.min ? (slider.value - slider.min) / (slider.max - slider.min) : 0,
816
+ colors
817
+ );
818
+ };
819
+ let dragging = false;
820
+ const seek = (event) => {
821
+ const horizontal = slider.orientation === "horizontal";
822
+ 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;
825
+ slider.value = slider.min + progress * (slider.max - slider.min);
826
+ event.preventDefault();
827
+ event.stopPropagation();
828
+ };
829
+ slider.onMouseDown = (event) => {
830
+ if (event.button === 0) {
831
+ dragging = true;
832
+ seek(event);
833
+ }
834
+ };
835
+ slider.onMouseDrag = (event) => {
836
+ if (dragging) seek(event);
837
+ };
838
+ slider.onMouseUp = (event) => {
839
+ if (dragging) seek(event);
840
+ dragging = false;
841
+ };
842
+ slider.onMouseDragEnd = () => {
843
+ dragging = false;
844
+ };
845
+ }
845
846
 
846
847
  // packages/cli/src/tui/native/components/select.ts
847
848
  var NativeSelectRenderable = class extends SelectRenderable {
@@ -1650,6 +1651,58 @@ import {
1650
1651
  TextRenderable as TextRenderable3
1651
1652
  } from "@opentui/core";
1652
1653
 
1654
+ // packages/cli/src/tui/native/components/device-history.ts
1655
+ var DeviceHistory = class {
1656
+ samples = [];
1657
+ append(sample) {
1658
+ if (!Number.isFinite(sample.at) || sample.at <= (this.samples.at(-1)?.at ?? 0)) return;
1659
+ this.samples.push(sample);
1660
+ if (this.samples.length > 120) this.samples.splice(0, this.samples.length - 120);
1661
+ }
1662
+ };
1663
+ function deviceHistorySparkline(samples, field, width) {
1664
+ const columns = Math.max(0, Math.floor(width));
1665
+ const latest = samples.at(-1)?.at;
1666
+ const values = Array(columns).fill(void 0);
1667
+ if (latest !== void 0) for (const sample of samples) {
1668
+ const x = columns - 1 - Math.round((latest - sample.at) / 2e3);
1669
+ if (x >= 0 && x < columns) values[x] = sample[field];
1670
+ }
1671
+ return values.map((value) => value === void 0 || !Number.isFinite(value) || value < 0 ? " " : "▁▂▃▄▅▆▇█"[Math.round(Math.min(100, value) * 7 / 100)]).join("");
1672
+ }
1673
+ function deviceHistoryGraph(samples, field, width) {
1674
+ const columns = Math.max(0, Math.floor(width) - 5), pixels = columns * 2;
1675
+ if (columns < 2) return "";
1676
+ const cells = Array.from({ length: 3 }, () => Array(columns).fill(0));
1677
+ const latest = samples.at(-1)?.at;
1678
+ const dots = [[1, 8], [2, 16], [4, 32], [64, 128]];
1679
+ const point = (x, y) => {
1680
+ if (x < 0 || x >= pixels || y < 0 || y >= 12) return;
1681
+ cells[Math.floor(y / 4)][Math.floor(x / 2)] |= dots[y % 4][x % 2];
1682
+ };
1683
+ let previous;
1684
+ if (latest !== void 0) for (const sample of samples) {
1685
+ const value = sample[field];
1686
+ const x = pixels - 1 - Math.round((latest - sample.at) / 1e3);
1687
+ if (x < 0) {
1688
+ previous = void 0;
1689
+ continue;
1690
+ }
1691
+ if (value === void 0 || !Number.isFinite(value) || value < 0) {
1692
+ previous = void 0;
1693
+ continue;
1694
+ }
1695
+ const y = Math.round(11 * (1 - Math.min(100, value) / 100));
1696
+ if (previous && sample.at - previous.at <= 5e3) {
1697
+ const distance = Math.max(1, Math.abs(x - previous.x), Math.abs(y - previous.y));
1698
+ for (let step = 0; step <= distance; step++) point(Math.round(previous.x + (x - previous.x) * step / distance), Math.round(previous.y + (y - previous.y) * step / distance));
1699
+ } else point(x, y);
1700
+ previous = { x, y, at: sample.at };
1701
+ }
1702
+ const labels = ["100%│", " 50%│", " 0%└"];
1703
+ return cells.map((row, y) => labels[y] + row.map((bits, x) => bits ? String.fromCodePoint(10240 + bits) : x % 4 === 0 ? "·" : " ").join("")).join("\n");
1704
+ }
1705
+
1653
1706
  // packages/cli/src/tui/terminal-cells.ts
1654
1707
  var graphemes = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1655
1708
  var extendedPictographic = /\p{Extended_Pictographic}/u;
@@ -1729,6 +1782,20 @@ function terminalCellWidth(value) {
1729
1782
 
1730
1783
  // packages/cli/src/tui/native/components/instrument-format.ts
1731
1784
  var graphemes2 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1785
+ function formatSignalDivider(width, geometry = { direction: "top" }) {
1786
+ const cells = Math.max(0, Math.floor(width));
1787
+ if (cells < 8) return "─".repeat(cells);
1788
+ const buffer = Math.max(1, Math.floor(geometry.buffer ?? 2));
1789
+ const requestedRight = geometry.direction === "bottom" && geometry.sidebarColumn !== void 0 ? geometry.sidebarColumn : (geometry.rightTextStart ?? cells) - buffer - 1;
1790
+ const right = Math.max(4, Math.min(cells - 2, Math.floor(requestedRight)));
1791
+ const requestedLeft = Math.floor(geometry.leftTextEnd ?? 0) + buffer;
1792
+ if (geometry.direction === "top" && requestedLeft > right - 2) return "─".repeat(cells);
1793
+ const left = Math.max(1, Math.min(right - 2, requestedLeft));
1794
+ const outside = geometry.direction === "top" ? "▁" : "▔";
1795
+ const inside = geometry.direction === "top" ? "▔" : "▁";
1796
+ const fall = geometry.direction === "top" ? "╱" : "╲", rise = geometry.direction === "top" ? "╲" : "╱";
1797
+ return outside.repeat(left) + fall + inside.repeat(right - left - 1) + rise + outside.repeat(cells - right - 1);
1798
+ }
1732
1799
  function fitInstrumentText(value, cells, marker = "~") {
1733
1800
  const text = stripTerminalSequences(normalizeTerminalLine(value));
1734
1801
  const width = Math.max(0, Math.floor(cells));
@@ -1989,13 +2056,17 @@ var InstrumentDivider = class extends TextRenderable3 {
1989
2056
  };
1990
2057
  var DeviceMonitor = class extends BoxRenderable4 {
1991
2058
  constructor(renderer2, action) {
1992
- super(renderer2, { id: "omnius-devices", width: "100%", height: 3, minHeight: 3, maxHeight: "60%", flexShrink: 1, flexDirection: "column", visible: false });
2059
+ super(renderer2, { id: "omnius-devices", width: "100%", height: 5, minHeight: 5, maxHeight: "60%", flexShrink: 1, flexDirection: "column", visible: false });
1993
2060
  this.action = action;
1994
2061
  this.heading = new SectionBanner(renderer2, "DEVICES", "omnius-devices:heading", () => this.toggle());
1995
2062
  this.heading.update("DEVICES", false);
1996
2063
  this.add(this.heading);
1997
2064
  this.summary = new TextRenderable3(renderer2, { id: "omnius-devices:summary", content: "--", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
1998
2065
  this.add(this.summary);
2066
+ this.compactLoad = new TextRenderable3(renderer2, { id: "omnius-devices:load-history", content: "", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2067
+ this.compactMemory = new TextRenderable3(renderer2, { id: "omnius-devices:memory-history", content: "", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2068
+ this.add(this.compactLoad);
2069
+ this.add(this.compactMemory);
1999
2070
  this.scroll = new ScrollBoxRenderable3(renderer2, {
2000
2071
  id: "omnius-devices:scroll",
2001
2072
  width: "100%",
@@ -2036,11 +2107,15 @@ var DeviceMonitor = class extends BoxRenderable4 {
2036
2107
  heading;
2037
2108
  scroll;
2038
2109
  summary;
2110
+ compactLoad;
2111
+ compactMemory;
2039
2112
  policy;
2040
2113
  status;
2041
2114
  notes;
2042
2115
  expanded = false;
2116
+ availableRows = 100;
2043
2117
  snapshot;
2118
+ historySource = "";
2044
2119
  rows = /* @__PURE__ */ new Map();
2045
2120
  button(renderer2, id, label, activate) {
2046
2121
  const button = new TextRenderable3(renderer2, {
@@ -2086,7 +2161,12 @@ var DeviceMonitor = class extends BoxRenderable4 {
2086
2161
  }
2087
2162
  update(snapshot) {
2088
2163
  this.snapshot = snapshot;
2089
- this.visible = Boolean(snapshot);
2164
+ this.visible = Boolean(snapshot) && this.availableRows >= 3;
2165
+ const historySource = `${snapshot?.source}:${snapshot?.local}`;
2166
+ if (this.historySource !== historySource) {
2167
+ for (const row of this.rows.values()) row.history.samples.splice(0);
2168
+ this.historySource = historySource;
2169
+ }
2090
2170
  const allowed = this.canSelect();
2091
2171
  if (this.policy.focused && !allowed) this.heading.focus();
2092
2172
  this.policy.focusable = allowed;
@@ -2108,7 +2188,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2108
2188
  flexShrink: 0,
2109
2189
  onSizeChange: () => {
2110
2190
  if (!row) return;
2111
- for (const child of [row.title, row.load, row.memory, row.reason]) child.width = row.root.width;
2191
+ for (const child of [row.title, row.load, row.memory, row.reason, row.loadGraph, row.memoryGraph]) child.width = row.root.width;
2112
2192
  this.refresh();
2113
2193
  }
2114
2194
  });
@@ -2120,6 +2200,8 @@ var DeviceMonitor = class extends BoxRenderable4 {
2120
2200
  title.on(RenderableEvents3.FOCUSED, () => this.scroll.scrollChildIntoView(root.id));
2121
2201
  const load = new TelemetryRow(this._ctx, "LOAD", `${root.id}:load`, 16);
2122
2202
  const memory = new TelemetryRow(this._ctx, "VRAM", `${root.id}:memory`, 16);
2203
+ const plot = (name) => new TextRenderable3(this._ctx, { id: `${root.id}:${name}-history`, content: "", width: "100%", height: 3, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true, selectable: false });
2204
+ const loadGraph = plot("load"), memoryGraph = plot("memory"), history = new DeviceHistory();
2123
2205
  const reason = new TextRenderable3(this._ctx, {
2124
2206
  id: `${root.id}:reason`,
2125
2207
  content: "",
@@ -2134,12 +2216,19 @@ var DeviceMonitor = class extends BoxRenderable4 {
2134
2216
  });
2135
2217
  root.add(title);
2136
2218
  root.add(load);
2219
+ root.add(loadGraph);
2137
2220
  root.add(memory);
2221
+ root.add(memoryGraph);
2138
2222
  root.add(reason);
2139
2223
  this.scroll.add(root);
2140
- row = { root, title, load, memory, reason };
2224
+ row = { root, title, load, memory, reason, history, loadGraph, memoryGraph };
2141
2225
  this.rows.set(device.uuid, row);
2142
2226
  }
2227
+ if (snapshot?.sampledAt !== void 0) row.history.append({
2228
+ at: snapshot.sampledAt,
2229
+ load: device.utilization,
2230
+ memory: device.memoryUsedMB !== void 0 && device.memoryUsedMB >= 0 && device.memoryTotalMB !== void 0 && device.memoryTotalMB > 0 ? device.memoryUsedMB * 100 / device.memoryTotalMB : void 0
2231
+ });
2143
2232
  const selectable = this.canSelectDevice(device);
2144
2233
  if (row.title.focused && !selectable) this.heading.focus();
2145
2234
  row.title.focusable = selectable;
@@ -2156,11 +2245,20 @@ var DeviceMonitor = class extends BoxRenderable4 {
2156
2245
  toggle() {
2157
2246
  this.expanded = !this.expanded;
2158
2247
  this.scroll.visible = this.expanded;
2159
- this.minHeight = this.expanded ? 4 : 3;
2160
2248
  this.scroll.minHeight = this.expanded ? 1 : 0;
2161
- this.height = this.expanded ? Math.min(12, Math.max(5, Math.floor(this._ctx.terminalHeight / 3))) : 3;
2249
+ this.fitHeight(this.availableRows + 2);
2162
2250
  this.refresh();
2163
2251
  }
2252
+ fitHeight(railRows) {
2253
+ this.availableRows = Math.max(0, railRows - 2);
2254
+ this.visible = Boolean(this.snapshot) && this.availableRows >= 3;
2255
+ this.minHeight = Math.min(5, this.availableRows);
2256
+ this.maxHeight = this.availableRows;
2257
+ this.summary.visible = this.availableRows >= 5;
2258
+ this.compactLoad.visible = !this.expanded;
2259
+ this.compactMemory.visible = !this.expanded && this.availableRows >= 4;
2260
+ this.height = Math.min(this.availableRows, this.expanded ? Math.min(18, Math.max(6, Math.floor(railRows * 0.6))) : 5);
2261
+ }
2164
2262
  onResize(width, height) {
2165
2263
  super.onResize(width, height);
2166
2264
  this.refresh();
@@ -2176,13 +2274,28 @@ var DeviceMonitor = class extends BoxRenderable4 {
2176
2274
  this.notes.content = [
2177
2275
  data?.local === false ? "REMOTE" : void 0,
2178
2276
  data ? `${data.source.toUpperCase()} · ${data.policy.toUpperCase()} · ${data.broker.toUpperCase()}` : void 0,
2277
+ "2s/col",
2179
2278
  data?.warnings.length ? `${data.warnings.length} notices` : void 0
2180
2279
  ].filter(Boolean).join(" · ");
2280
+ const primary = devices.find((device) => device.preferred) ?? devices.find((device) => device.enabled) ?? devices[0];
2281
+ const compactHistory = primary ? this.rows.get(primary.uuid)?.history.samples ?? [] : [];
2282
+ for (const [element, field, label] of [[this.compactLoad, "load", "UTIL"], [this.compactMemory, "memory", primary?.unifiedMemory ? "UMA" : "VRAM"]]) {
2283
+ const current = primary ? field === "load" ? primary.utilization : primary.memoryUsedMB !== void 0 && primary.memoryTotalMB ? primary.memoryUsedMB / primary.memoryTotalMB * 100 : void 0 : void 0;
2284
+ const value = instrumentNumber(current, 0, "%");
2285
+ const caption = `${label}${primary?.index ?? "-"}`;
2286
+ const plotWidth = Math.max(0, this.width - caption.length - value.length - 2);
2287
+ const next = `${caption} ${deviceHistorySparkline(compactHistory, field, plotWidth)} ${value}`;
2288
+ if (element.plainText !== next) element.content = next;
2289
+ }
2181
2290
  for (const device of devices) {
2182
2291
  const row = this.rows.get(device.uuid);
2183
2292
  if (!row) continue;
2184
2293
  const state = device.enabled === void 0 ? "--" : device.enabled ? "ON" : "OFF";
2185
2294
  row.title.content = formatTelemetryRow(`GPU ${device.index} ${device.name}`, `${state}${device.preferred ? " *" : ""}`, row.root.width, 6);
2295
+ for (const [plot, field] of [[row.loadGraph, "load"], [row.memoryGraph, "memory"]]) {
2296
+ const next = deviceHistoryGraph(row.history.samples, field, row.root.width);
2297
+ if (plot.plainText !== next) plot.content = next;
2298
+ }
2186
2299
  }
2187
2300
  }
2188
2301
  focusTargets() {
@@ -2305,6 +2418,10 @@ var InstrumentRail = class extends BoxRenderable4 {
2305
2418
  contextMeter;
2306
2419
  contextHeader;
2307
2420
  contextDetails;
2421
+ onResize(width, height) {
2422
+ super.onResize(width, height);
2423
+ this.devices?.fitHeight(height);
2424
+ }
2308
2425
  constructor(renderer2, options = {}) {
2309
2426
  const id = options.id ?? "omnius-instruments";
2310
2427
  super(renderer2, {
@@ -3964,17 +4081,327 @@ var VimComposer = class {
3964
4081
  }
3965
4082
  };
3966
4083
 
4084
+ // packages/cli/src/tui/native/components/selection-menu.ts
4085
+ import { BoxRenderable as BoxRenderable5, CliRenderEvents as CliRenderEvents3, TextAttributes as TextAttributes6, TextRenderable as TextRenderable4 } from "@opentui/core";
4086
+ var NativeSelectionMenu = class {
4087
+ constructor(renderer2, colors, copy, restoreFocus) {
4088
+ this.renderer = renderer2;
4089
+ this.colors = colors;
4090
+ this.copy = copy;
4091
+ this.restoreFocus = restoreFocus;
4092
+ renderer2.on(CliRenderEvents3.RESIZE, this.position);
4093
+ }
4094
+ renderer;
4095
+ colors;
4096
+ copy;
4097
+ restoreFocus;
4098
+ overlay;
4099
+ panel;
4100
+ frame;
4101
+ previousFocus;
4102
+ anchor = { x: 0, y: 0 };
4103
+ selectedText = "";
4104
+ get active() {
4105
+ return Boolean(this.overlay);
4106
+ }
4107
+ open(x, y, text) {
4108
+ if (!text) return;
4109
+ this.close();
4110
+ this.selectedText = text;
4111
+ this.anchor = { x, y };
4112
+ this.previousFocus = this.renderer.currentFocusedRenderable;
4113
+ this.overlay = new BoxRenderable5(this.renderer, {
4114
+ id: "omnius-selection-menu",
4115
+ position: "absolute",
4116
+ left: 0,
4117
+ top: 0,
4118
+ width: "100%",
4119
+ height: "100%",
4120
+ backgroundColor: "transparent",
4121
+ shouldFill: false,
4122
+ zIndex: 120,
4123
+ onMouseDown: (event) => {
4124
+ this.close();
4125
+ event.preventDefault();
4126
+ event.stopPropagation();
4127
+ }
4128
+ });
4129
+ this.panel = new BoxRenderable5(this.renderer, {
4130
+ id: "omnius-selection-menu-panel",
4131
+ position: "absolute",
4132
+ width: 14,
4133
+ height: 3,
4134
+ flexDirection: "column",
4135
+ paddingX: 2,
4136
+ paddingY: 1,
4137
+ backgroundColor: this.colors.background,
4138
+ onMouseDown: (event) => {
4139
+ event.preventDefault();
4140
+ event.stopPropagation();
4141
+ }
4142
+ });
4143
+ const action = new TextRenderable4(this.renderer, {
4144
+ id: "omnius-selection-copy",
4145
+ content: "COPY",
4146
+ height: 1,
4147
+ width: "100%",
4148
+ selectable: false,
4149
+ fg: this.colors.foreground,
4150
+ bg: this.colors.background,
4151
+ attributes: TextAttributes6.BOLD | TextAttributes6.INVERSE,
4152
+ onMouseDown: (event) => {
4153
+ if (event.button === 0) this.confirm();
4154
+ event.preventDefault();
4155
+ event.stopPropagation();
4156
+ }
4157
+ });
4158
+ action.focusable = true;
4159
+ this.panel.add(action);
4160
+ this.overlay.add(this.panel);
4161
+ this.frame = new NativeCornerFrame(this.panel, this.colors, this.renderer, this.overlay);
4162
+ this.renderer.root.add(this.overlay);
4163
+ this.position();
4164
+ action.focus();
4165
+ this.renderer.requestRender();
4166
+ }
4167
+ position = () => {
4168
+ if (!this.panel) return;
4169
+ const width = Math.min(14, this.renderer.terminalWidth);
4170
+ this.panel.width = width;
4171
+ this.panel.left = Math.max(0, Math.min(this.anchor.x, this.renderer.terminalWidth - width));
4172
+ this.panel.top = Math.max(0, Math.min(this.anchor.y + 1, this.renderer.terminalHeight - 3));
4173
+ };
4174
+ handleKey(key) {
4175
+ if (key.eventType !== "release") {
4176
+ if (key.name === "return" || key.sequence === " " || key.ctrl && key.shift && key.name.toLowerCase() === "c") this.confirm();
4177
+ else if (key.name === "escape" || key.name === "tab") this.close();
4178
+ }
4179
+ key.preventDefault();
4180
+ key.stopPropagation();
4181
+ }
4182
+ confirm() {
4183
+ const text = this.selectedText;
4184
+ this.close();
4185
+ this.copy(text);
4186
+ }
4187
+ applyTheme(colors) {
4188
+ this.colors = colors;
4189
+ if (!this.panel) return;
4190
+ this.panel.backgroundColor = colors.background;
4191
+ this.frame?.setColors(colors);
4192
+ for (const child of this.panel.getChildren()) if (child instanceof TextRenderable4) {
4193
+ child.fg = colors.foreground;
4194
+ child.bg = colors.background;
4195
+ }
4196
+ }
4197
+ close() {
4198
+ if (!this.overlay) return;
4199
+ const previous = this.previousFocus;
4200
+ this.previousFocus = void 0;
4201
+ this.frame?.destroy();
4202
+ this.frame = void 0;
4203
+ this.overlay.destroyRecursively();
4204
+ this.overlay = void 0;
4205
+ this.panel = void 0;
4206
+ this.selectedText = "";
4207
+ if (previous && !previous.isDestroyed && previous.visible && previous.focusable) previous.focus();
4208
+ else this.restoreFocus();
4209
+ this.renderer.requestRender();
4210
+ }
4211
+ destroy() {
4212
+ this.close();
4213
+ this.renderer.off(CliRenderEvents3.RESIZE, this.position);
4214
+ }
4215
+ };
4216
+
4217
+ // packages/cli/src/tui/native/components/tab-strip.ts
4218
+ import { BoxRenderable as BoxRenderable6, CliRenderEvents as CliRenderEvents4, ScrollBoxRenderable as ScrollBoxRenderable4, SliderRenderable, TextAttributes as TextAttributes7, TextRenderable as TextRenderable5 } from "@opentui/core";
4219
+ var NativeTabStrip = class extends BoxRenderable6 {
4220
+ constructor(renderer2, options) {
4221
+ super(renderer2, {
4222
+ id: options.id,
4223
+ height: 1,
4224
+ minHeight: 1,
4225
+ maxHeight: 1,
4226
+ minWidth: 0,
4227
+ flexGrow: 1,
4228
+ flexShrink: 1,
4229
+ flexDirection: "row",
4230
+ shouldFill: false
4231
+ });
4232
+ this.renderer = renderer2;
4233
+ this.options = options;
4234
+ this.colors = options.colors;
4235
+ this.scroll = new ScrollBoxRenderable4(renderer2, {
4236
+ id: `${options.id}:scroll`,
4237
+ height: 1,
4238
+ minHeight: 1,
4239
+ maxHeight: 1,
4240
+ minWidth: 0,
4241
+ flexGrow: 1,
4242
+ flexShrink: 1,
4243
+ scrollX: true,
4244
+ scrollY: false,
4245
+ viewportCulling: true,
4246
+ viewportOptions: { height: 1, minHeight: 1, maxHeight: 1 },
4247
+ contentOptions: { flexDirection: "row", gap: 1 },
4248
+ horizontalScrollbarOptions: { visible: false },
4249
+ verticalScrollbarOptions: { visible: false }
4250
+ });
4251
+ this.scroll.focusable = false;
4252
+ this.scroll.horizontalScrollBar.visible = this.scroll.verticalScrollBar.visible = false;
4253
+ styleNativeScrollbars(this.scroll, this.colors);
4254
+ this.add(this.scroll);
4255
+ this.rail = new SliderRenderable(renderer2, {
4256
+ id: `${options.id}:rail`,
4257
+ orientation: "horizontal",
4258
+ width: 5,
4259
+ minWidth: 5,
4260
+ height: 1,
4261
+ flexShrink: 0,
4262
+ visible: false,
4263
+ min: 0,
4264
+ max: 0,
4265
+ value: 0,
4266
+ onChange: (value) => {
4267
+ if (!this.syncing) this.scroll.scrollLeft = value;
4268
+ }
4269
+ });
4270
+ this.rail.focusable = false;
4271
+ this.add(this.rail);
4272
+ styleNativeSlider(this.rail, this.colors);
4273
+ renderer2.on(CliRenderEvents4.FRAME, this.syncScroll);
4274
+ }
4275
+ renderer;
4276
+ options;
4277
+ scroll;
4278
+ rail;
4279
+ buttons = /* @__PURE__ */ new Map();
4280
+ activeId;
4281
+ signature = "";
4282
+ syncing = false;
4283
+ reveal;
4284
+ colors;
4285
+ naturalWidth = 0;
4286
+ update(items, activeId, busy = false) {
4287
+ const signature = JSON.stringify([items, activeId, busy]);
4288
+ if (signature === this.signature) return;
4289
+ this.signature = signature;
4290
+ const ids = new Set(items.map((item) => item.id));
4291
+ for (const [id, entry] of this.buttons) if (!ids.has(id)) {
4292
+ entry.button.destroy();
4293
+ this.buttons.delete(id);
4294
+ }
4295
+ for (const [index, action] of items.entries()) {
4296
+ let entry = this.buttons.get(action.id);
4297
+ if (!entry) {
4298
+ const button = new TextRenderable5(this.renderer, {
4299
+ id: `${this.options.prefix}:${action.id}`,
4300
+ content: "",
4301
+ height: 1,
4302
+ flexShrink: 0,
4303
+ wrapMode: "none",
4304
+ selectable: false,
4305
+ fg: this.colors.foreground,
4306
+ bg: this.colors.background,
4307
+ onMouseDown: (event) => {
4308
+ if (event.button === 0) {
4309
+ button.focus();
4310
+ if (!entry.blocked) this.options.activate(action.id);
4311
+ }
4312
+ event.preventDefault();
4313
+ event.stopPropagation();
4314
+ },
4315
+ onKeyDown: (key) => this.handleKey(entry, key)
4316
+ });
4317
+ entry = { action, active: false, blocked: false, button };
4318
+ this.buttons.set(action.id, entry);
4319
+ button.on("focused", () => {
4320
+ this.style(entry);
4321
+ this.scroll.scrollChildIntoView(button.id);
4322
+ this.reveal = button.id;
4323
+ });
4324
+ button.on("blurred", () => this.style(entry));
4325
+ }
4326
+ entry.action = action;
4327
+ entry.active = action.id === activeId;
4328
+ entry.blocked = busy || Boolean(action.disabled);
4329
+ entry.button.focusable = entry.active || !entry.blocked;
4330
+ const label = plainText(action.label);
4331
+ const content = entry.active ? `⟋⟋ ${label} ⟍⟍` : ` ${label} `;
4332
+ if (entry.button.plainText !== content) entry.button.content = content;
4333
+ this.style(entry);
4334
+ if (this.scroll.getChildren()[index] !== entry.button) this.scroll.add(entry.button, index);
4335
+ }
4336
+ this.naturalWidth = Array.from(this.buttons.values()).reduce((width, entry) => width + terminalCellWidth(entry.button.plainText), 0) + Math.max(0, items.length - 1);
4337
+ const focused = this.renderer.currentFocusedRenderable;
4338
+ if (focused && !focused.focusable && Array.from(this.buttons.values()).some((entry) => entry.button === focused)) this.buttons.get(activeId)?.button.focus();
4339
+ if (activeId !== this.activeId) this.reveal = `${this.options.prefix}:${activeId}`;
4340
+ this.activeId = activeId;
4341
+ this.renderer.requestRender();
4342
+ }
4343
+ style(entry) {
4344
+ entry.button.attributes = (entry.active ? TextAttributes7.BOLD | TextAttributes7.UNDERLINE : entry.blocked ? TextAttributes7.DIM : 0) | (entry.button.focused ? TextAttributes7.INVERSE : 0);
4345
+ }
4346
+ handleKey(entry, key) {
4347
+ if (key.eventType === "release") return;
4348
+ if (key.name === "return" || key.sequence === " ") {
4349
+ if (!entry.blocked) this.options.activate(entry.action.id);
4350
+ } else if (["left", "right", "home", "end"].includes(key.name)) {
4351
+ const targets = this.scroll.getChildren().filter((button) => button.focusable);
4352
+ const index = targets.indexOf(entry.button);
4353
+ const next = key.name === "home" ? 0 : key.name === "end" ? targets.length - 1 : (index + (key.name === "left" ? -1 : 1) + targets.length) % targets.length;
4354
+ targets[next]?.focus();
4355
+ } else return;
4356
+ key.preventDefault();
4357
+ key.stopPropagation();
4358
+ }
4359
+ syncScroll = () => {
4360
+ if (!this.visible || this.width < 1) return;
4361
+ const overflow = this.naturalWidth > this.width;
4362
+ if (this.rail.visible !== overflow) {
4363
+ this.rail.visible = overflow;
4364
+ this.renderer.requestRender();
4365
+ }
4366
+ if (this.reveal) {
4367
+ this.scroll.scrollChildIntoView(this.reveal);
4368
+ this.reveal = void 0;
4369
+ }
4370
+ this.syncing = true;
4371
+ this.rail.max = Math.max(0, this.scroll.scrollWidth - this.scroll.viewport.width);
4372
+ this.rail.viewPortSize = this.scroll.viewport.width;
4373
+ this.rail.value = this.scroll.scrollLeft;
4374
+ this.syncing = false;
4375
+ };
4376
+ applyTheme(colors) {
4377
+ this.colors = colors;
4378
+ for (const { button } of this.buttons.values()) {
4379
+ button.fg = colors.foreground;
4380
+ button.bg = colors.background;
4381
+ }
4382
+ styleNativeScrollbars(this.scroll, colors);
4383
+ styleNativeSlider(this.rail, colors);
4384
+ }
4385
+ destroySelf() {
4386
+ this.renderer.off(CliRenderEvents4.FRAME, this.syncScroll);
4387
+ super.destroySelf();
4388
+ }
4389
+ };
4390
+
3967
4391
  // packages/cli/src/tui/native/components/frontend.ts
3968
4392
  var initialTheme = { mode: "system", bg: -1, accent: -1, textPrimary: -1, textDim: -1, boxColor: -1 };
3969
4393
  var NativeFrontend = class {
3970
- constructor(renderer2, emit) {
4394
+ constructor(renderer2, emit, options = {}) {
3971
4395
  this.renderer = renderer2;
3972
4396
  this.emit = emit;
3973
4397
  const colors = palette(initialTheme);
3974
4398
  this.context = { renderer: renderer2, colors, syntax: SyntaxStyle2.fromStyles({}, renderer2.nativeScene), emit, terminals: /* @__PURE__ */ new Map() };
3975
4399
  this.disposeSelectionContrast = installDefaultSelectionContrast(renderer2);
3976
- this.clipboard = createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
3977
- this.root = new BoxRenderable5(renderer2, {
4400
+ this.clipboard = options.clipboard ?? createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
4401
+ this.selectionMenu = new NativeSelectionMenu(renderer2, colors, (text) => {
4402
+ void this.copy(text);
4403
+ }, () => this.restoreFocus());
4404
+ this.root = new BoxRenderable7(renderer2, {
3978
4405
  id: "omnius-native",
3979
4406
  width: "100%",
3980
4407
  height: "100%",
@@ -3982,7 +4409,7 @@ var NativeFrontend = class {
3982
4409
  backgroundColor: colors.background
3983
4410
  });
3984
4411
  renderer2.root.add(this.root);
3985
- this.header = new BoxRenderable5(renderer2, {
4412
+ this.header = new BoxRenderable7(renderer2, {
3986
4413
  id: "omnius-header",
3987
4414
  width: "100%",
3988
4415
  minWidth: 0,
@@ -3993,9 +4420,23 @@ var NativeFrontend = class {
3993
4420
  borderColor: colors.border
3994
4421
  });
3995
4422
  this.root.add(this.header);
3996
- const headerRow = new BoxRenderable5(renderer2, { id: "omnius-header-row", width: "100%", minWidth: 0, minHeight: 1, flexDirection: "row", gap: 1 });
4423
+ this.header.renderAfter = (buffer) => {
4424
+ if (this.header.height > 0) buffer.drawText(
4425
+ formatSignalDivider(this.header.width, {
4426
+ direction: "top",
4427
+ leftTextEnd: this.identity.x - this.header.x + Math.min(this.identity.width, terminalCellWidth(this.identity.plainText)),
4428
+ rightTextStart: Math.min(this.detailButton.x, this.headerActions.visible ? this.headerActions.x : this.detailButton.x) - this.header.x
4429
+ }),
4430
+ this.header.x,
4431
+ this.header.y + this.header.height - 1,
4432
+ this.context.colors.foreground,
4433
+ this.context.colors.background,
4434
+ TextAttributes8.DIM
4435
+ );
4436
+ };
4437
+ const headerRow = new BoxRenderable7(renderer2, { id: "omnius-header-row", width: "100%", minWidth: 0, minHeight: 1, flexDirection: "row", gap: 1 });
3997
4438
  this.header.add(headerRow);
3998
- this.identity = new TextRenderable4(renderer2, {
4439
+ this.identity = new TextRenderable6(renderer2, {
3999
4440
  id: "omnius-identity",
4000
4441
  content: "Omnius",
4001
4442
  fg: colors.foreground,
@@ -4005,7 +4446,7 @@ var NativeFrontend = class {
4005
4446
  truncate: true,
4006
4447
  minWidth: 18,
4007
4448
  selectable: false,
4008
- attributes: TextAttributes6.BOLD,
4449
+ attributes: TextAttributes8.BOLD,
4009
4450
  onMouseDown: (event) => {
4010
4451
  if (event.button === 0) {
4011
4452
  this.toggleHeaderPanel();
@@ -4020,7 +4461,7 @@ var NativeFrontend = class {
4020
4461
  }
4021
4462
  });
4022
4463
  headerRow.add(this.identity);
4023
- this.headerActions = new ScrollBoxRenderable4(renderer2, {
4464
+ this.headerActions = new ScrollBoxRenderable5(renderer2, {
4024
4465
  id: "omnius-header-actions",
4025
4466
  flexGrow: 0,
4026
4467
  flexShrink: 1,
@@ -4033,16 +4474,16 @@ var NativeFrontend = class {
4033
4474
  verticalScrollbarOptions: { visible: false }
4034
4475
  });
4035
4476
  headerRow.add(this.headerActions);
4036
- this.headerPanel = new TextRenderable4(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
4477
+ this.headerPanel = new TextRenderable6(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
4037
4478
  this.header.add(this.headerPanel);
4038
- this.detailButton = new TextRenderable4(renderer2, {
4479
+ this.detailButton = new TextRenderable6(renderer2, {
4039
4480
  id: "omnius-details-toggle",
4040
4481
  content: "+ DETAILS",
4041
4482
  height: 1,
4042
4483
  width: 9,
4043
4484
  flexShrink: 0,
4044
4485
  fg: colors.foreground,
4045
- attributes: TextAttributes6.DIM,
4486
+ attributes: TextAttributes8.DIM,
4046
4487
  selectable: false,
4047
4488
  onMouseDown: (event) => {
4048
4489
  if (event.button === 0) {
@@ -4062,60 +4503,67 @@ var NativeFrontend = class {
4062
4503
  for (const control of [this.identity, this.detailButton]) {
4063
4504
  const attributes = control.attributes;
4064
4505
  control.on("focused", () => {
4065
- control.attributes = attributes | TextAttributes6.INVERSE;
4506
+ control.attributes = attributes | TextAttributes8.INVERSE;
4066
4507
  });
4067
4508
  control.on("blurred", () => {
4068
4509
  control.attributes = attributes;
4069
4510
  });
4070
4511
  }
4071
- this.routeWarning = new TextRenderable4(renderer2, { id: "omnius-route-warning", content: "", width: "100%", wrapMode: "word", fg: colors.warning, attributes: TextAttributes6.BOLD | TextAttributes6.UNDERLINE, visible: false, flexShrink: 0 });
4072
- this.root.add(this.routeWarning);
4073
- this.tabs = new TabSelectRenderable(renderer2, {
4074
- id: "omnius-agent-tabs",
4512
+ this.sessionRow = new BoxRenderable7(renderer2, {
4513
+ id: "omnius-sessions-row",
4075
4514
  width: "100%",
4076
- height: 2,
4077
- showDescription: false,
4078
- showUnderline: true,
4079
- showScrollArrows: false,
4080
- tabWidth: 18,
4081
- wrapSelection: true,
4082
- visible: false,
4083
- backgroundColor: colors.background,
4084
- focusedBackgroundColor: colors.background,
4085
- textColor: colors.dim,
4086
- selectedTextColor: colors.foreground,
4087
- selectedBackgroundColor: colors.selection,
4515
+ height: 1,
4516
+ minWidth: 0,
4517
+ flexShrink: 0,
4518
+ flexDirection: "row",
4519
+ gap: 1,
4520
+ visible: false
4521
+ });
4522
+ this.header.add(this.sessionRow);
4523
+ this.sessions = new NativeTabStrip(renderer2, {
4524
+ id: "omnius-session-tabs",
4525
+ prefix: "session",
4526
+ colors,
4527
+ activate: (id) => this.emitAction("session", id)
4528
+ });
4529
+ this.sessionRow.add(this.sessions);
4530
+ this.newSession = new TextRenderable6(renderer2, {
4531
+ id: "omnius-session-new",
4532
+ content: "+ NEW",
4533
+ width: 5,
4534
+ height: 1,
4535
+ flexShrink: 0,
4536
+ selectable: false,
4537
+ fg: colors.foreground,
4538
+ bg: colors.background,
4088
4539
  onMouseDown: (event) => {
4089
- if (event.button !== 0) return;
4090
- const count = Math.max(1, Math.floor(this.tabs.width / this.tabs.getTabWidth()));
4091
- const start = Math.max(0, Math.min(this.tabs.getSelectedIndex() - Math.floor(count / 2), this.tabs.options.length - count));
4092
- this.tabs.setSelectedIndex(start + Math.floor((event.x - this.tabs.x) / this.tabs.getTabWidth()));
4540
+ if (event.button === 0 && !this.state?.sessions?.busy) this.emitAction("session-new");
4541
+ event.preventDefault();
4093
4542
  event.stopPropagation();
4543
+ },
4544
+ onKeyDown: (key) => {
4545
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
4546
+ if (!this.state?.sessions?.busy) this.emitAction("session-new");
4547
+ key.preventDefault();
4548
+ key.stopPropagation();
4549
+ }
4094
4550
  }
4095
4551
  });
4096
- this.tabs.on("selectionChanged", () => {
4097
- if (this.changing) return;
4098
- const selected = this.tabs.getSelectedOption();
4099
- if (selected) this.emitAction("view", String(selected.value));
4100
- this.editor.focus();
4101
- });
4102
- this.tabs.renderAfter = (buffer) => {
4103
- if (this.tabs.height < 2) return;
4104
- const colors2 = this.context.colors;
4105
- const tabWidth = Math.min(this.tabs.getTabWidth(), this.tabs.width);
4106
- const count = Math.max(1, Math.floor(this.tabs.width / Math.max(1, tabWidth)));
4107
- const selected = this.tabs.getSelectedIndex();
4108
- const start = Math.max(0, Math.min(selected - Math.floor(count / 2), this.tabs.options.length - count));
4109
- buffer.fillRect(0, 1, this.tabs.width, 1, colors2.background);
4110
- buffer.drawText("─".repeat(Math.max(0, tabWidth)), (selected - start) * tabWidth, 1, colors2.foreground, colors2.background, TextAttributes6.DIM);
4552
+ this.newSession.focusable = true;
4553
+ const styleNew = () => {
4554
+ this.newSession.attributes = (this.state?.sessions?.busy ? TextAttributes8.DIM : 0) | (this.newSession.focused ? TextAttributes8.INVERSE : 0);
4111
4555
  };
4112
- this.root.add(this.tabs);
4113
- this.taskPane = new BoxRenderable5(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
4556
+ this.newSession.on("focused", styleNew);
4557
+ this.newSession.on("blurred", styleNew);
4558
+ this.sessionRow.add(this.newSession);
4559
+ this.routeWarning = new TextRenderable6(renderer2, { id: "omnius-route-warning", content: "", width: "100%", wrapMode: "word", fg: colors.warning, attributes: TextAttributes8.BOLD | TextAttributes8.UNDERLINE, visible: false, flexShrink: 0 });
4560
+ this.root.add(this.routeWarning);
4561
+ this.taskPane = new BoxRenderable7(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
4114
4562
  this.tasks = new NativeBlockList(this.context, this.taskPane);
4115
4563
  this.root.add(this.taskPane);
4116
- this.body = new BoxRenderable5(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
4564
+ this.body = new BoxRenderable7(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
4117
4565
  this.root.add(this.body);
4118
- this.scroll = new ScrollBoxRenderable4(renderer2, {
4566
+ this.scroll = new ScrollBoxRenderable5(renderer2, {
4119
4567
  id: "omnius-conversation",
4120
4568
  flexGrow: 1,
4121
4569
  flexShrink: 1,
@@ -4128,6 +4576,15 @@ var NativeFrontend = class {
4128
4576
  viewportCulling: true,
4129
4577
  verticalScrollbarOptions: { showArrows: false, trackOptions: { foregroundColor: colors.dim, backgroundColor: colors.background } },
4130
4578
  contentOptions: { flexDirection: "column", onMouseDown: (event) => {
4579
+ if (event.button === 2) {
4580
+ const selected = this.renderer.getSelection()?.getSelectedText();
4581
+ if (selected) {
4582
+ this.selectionMenu.open(event.x, event.y, selected);
4583
+ event.preventDefault();
4584
+ event.stopPropagation();
4585
+ }
4586
+ return;
4587
+ }
4131
4588
  if (!this.selectionArmed || event.button !== 0 || !event.target) return;
4132
4589
  this.selectionArmed = false;
4133
4590
  let block = event.target;
@@ -4147,6 +4604,18 @@ var NativeFrontend = class {
4147
4604
  }
4148
4605
  event.preventDefault();
4149
4606
  event.stopPropagation();
4607
+ }, onMouseDrag: (event) => {
4608
+ if (!event.isDragging || !event.target || !this.renderer.getSelection()?.isDragging) return;
4609
+ let container = this.renderer.getSelectionContainer();
4610
+ while (container) {
4611
+ let current = event.target;
4612
+ while (current && current !== container) current = current.parent;
4613
+ if (current) break;
4614
+ this.renderer.updateSelection(event.target, event.x, event.y);
4615
+ const next = this.renderer.getSelectionContainer();
4616
+ if (next === container) break;
4617
+ container = next;
4618
+ }
4150
4619
  } }
4151
4620
  });
4152
4621
  this.body.add(this.scroll);
@@ -4167,7 +4636,7 @@ var NativeFrontend = class {
4167
4636
  this.root.onMouseUp = (event) => this.instrumentDivider.handleRelease(event);
4168
4637
  this.instruments = new InstrumentRail(renderer2, { colors, onInspectAgent: (id) => this.emitAction("view", id), onAction: (command2) => this.emitAction(command2) });
4169
4638
  this.body.add(this.instruments);
4170
- this.embeddedPane = new BoxRenderable5(renderer2, {
4639
+ this.embeddedPane = new BoxRenderable7(renderer2, {
4171
4640
  id: "omnius-editor-pane",
4172
4641
  width: "50%",
4173
4642
  flexShrink: 0,
@@ -4178,7 +4647,7 @@ var NativeFrontend = class {
4178
4647
  visible: false
4179
4648
  });
4180
4649
  this.body.add(this.embeddedPane);
4181
- this.liveButton = new TextRenderable4(renderer2, {
4650
+ this.liveButton = new TextRenderable6(renderer2, {
4182
4651
  id: "omnius-live-button",
4183
4652
  content: "↓ Live",
4184
4653
  width: "100%",
@@ -4201,7 +4670,7 @@ var NativeFrontend = class {
4201
4670
  });
4202
4671
  this.liveButton.focusable = true;
4203
4672
  this.root.add(this.liveButton);
4204
- this.queue = new TextRenderable4(renderer2, { id: "omnius-steering-queue", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, visible: false, flexShrink: 0 });
4673
+ this.queue = new TextRenderable6(renderer2, { id: "omnius-steering-queue", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, visible: false, flexShrink: 0 });
4205
4674
  this.root.add(this.queue);
4206
4675
  this.suggestions = new NativeSelectRenderable(renderer2, {
4207
4676
  id: "omnius-suggestions",
@@ -4220,21 +4689,49 @@ var NativeFrontend = class {
4220
4689
  this.suggestions.onActivate = () => this.acceptSuggestion();
4221
4690
  this.suggestions.on("itemSelected", () => this.acceptSuggestion());
4222
4691
  this.root.add(this.suggestions);
4223
- this.status = new BoxRenderable5(renderer2, { id: "omnius-live-status", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2, visible: false });
4692
+ this.status = new BoxRenderable7(renderer2, { id: "omnius-live-status", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2, visible: false });
4224
4693
  this.root.add(this.status);
4225
- this.footer = new BoxRenderable5(renderer2, {
4694
+ this.footer = new BoxRenderable7(renderer2, {
4226
4695
  id: "omnius-input-box",
4227
4696
  width: "100%",
4228
4697
  flexShrink: 0,
4229
- border: ["top"],
4230
- borderStyle: "single",
4698
+ border: [],
4231
4699
  borderColor: colors.border,
4232
4700
  flexDirection: "column"
4233
4701
  });
4234
4702
  this.root.add(this.footer);
4235
- const inputRow = new BoxRenderable5(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
4703
+ this.footerRule = new BoxRenderable7(renderer2, {
4704
+ id: "omnius-input-rule",
4705
+ width: "100%",
4706
+ height: 1,
4707
+ minHeight: 1,
4708
+ maxHeight: 1,
4709
+ flexShrink: 0,
4710
+ flexDirection: "row",
4711
+ paddingLeft: 2,
4712
+ shouldFill: false
4713
+ });
4714
+ this.footer.add(this.footerRule);
4715
+ this.tabs = new NativeTabStrip(renderer2, {
4716
+ id: "omnius-agent-tabs",
4717
+ prefix: "agent",
4718
+ colors,
4719
+ activate: (id) => this.emitAction("view", id)
4720
+ });
4721
+ this.tabs.flexGrow = 0;
4722
+ this.tabs.flexShrink = 0;
4723
+ this.tabs.visible = false;
4724
+ this.footerRule.add(this.tabs);
4725
+ this.footerRule.renderBefore = (buffer) => {
4726
+ buffer.drawText(formatSignalDivider(this.footerRule.width, {
4727
+ direction: "bottom",
4728
+ leftTextEnd: this.tabs.visible ? this.tabs.x - this.footerRule.x + this.tabs.width : 0,
4729
+ sidebarColumn: this.instrumentDivider.visible && this.instruments.visible ? this.instrumentDivider.x - this.footerRule.x : void 0
4730
+ }), this.footerRule.x, this.footerRule.y, this.context.colors.foreground, this.context.colors.background, TextAttributes8.DIM);
4731
+ };
4732
+ const inputRow = new BoxRenderable7(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
4236
4733
  this.footer.add(inputRow);
4237
- this.prompt = new TextRenderable4(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
4734
+ this.prompt = new TextRenderable6(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
4238
4735
  inputRow.add(this.prompt);
4239
4736
  this.editor = new TextareaRenderable2(renderer2, {
4240
4737
  id: "omnius-input",
@@ -4254,14 +4751,14 @@ var NativeFrontend = class {
4254
4751
  onCursorChange: () => this.editorChanged(),
4255
4752
  onSubmit: () => this.submit()
4256
4753
  });
4257
- this.vimMode = new TextRenderable4(renderer2, {
4754
+ this.vimMode = new TextRenderable6(renderer2, {
4258
4755
  id: "omnius-vim-mode",
4259
4756
  content: "INSERT",
4260
4757
  height: 1,
4261
4758
  maxWidth: 12,
4262
4759
  flexShrink: 0,
4263
4760
  fg: colors.foreground,
4264
- attributes: TextAttributes6.DIM,
4761
+ attributes: TextAttributes8.DIM,
4265
4762
  wrapMode: "none",
4266
4763
  truncate: true,
4267
4764
  visible: false,
@@ -4275,31 +4772,42 @@ var NativeFrontend = class {
4275
4772
  this.renderer.requestRender();
4276
4773
  });
4277
4774
  inputRow.add(this.editor);
4278
- this.enhance = new BoxRenderable5(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
4775
+ this.enhance = new BoxRenderable7(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
4279
4776
  inputRow.add(this.enhance);
4280
- this.activity = new TextRenderable4(renderer2, { id: "omnius-activity", content: "", flexGrow: 1, minWidth: 0, height: 1, wrapMode: "none", truncate: true, visible: false, fg: colors.accent });
4777
+ this.activity = new TextRenderable6(renderer2, { id: "omnius-activity", content: "", flexGrow: 1, minWidth: 0, height: 1, wrapMode: "none", truncate: true, visible: false, fg: colors.accent });
4281
4778
  this.status.add(this.activity);
4282
- this.media = new BoxRenderable5(renderer2, { id: "omnius-media", height: 1, minWidth: 0, flexShrink: 1, flexDirection: "row", gap: 1, visible: false });
4283
- this.mediaText = new TextRenderable4(renderer2, { id: "omnius-media-text", content: "", flexGrow: 1, minWidth: 0, wrapMode: "none", truncate: true, fg: colors.accent });
4779
+ this.media = new BoxRenderable7(renderer2, { id: "omnius-media", height: 1, minWidth: 0, flexShrink: 1, flexDirection: "row", gap: 1, visible: false });
4780
+ this.mediaText = new TextRenderable6(renderer2, { id: "omnius-media-text", content: "", flexGrow: 1, minWidth: 0, wrapMode: "none", truncate: true, fg: colors.accent });
4284
4781
  this.media.add(this.mediaText);
4285
- this.micTrack = new TextRenderable4(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, fg: colors.foreground, visible: false });
4782
+ this.micTrack = new TextRenderable6(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, fg: colors.foreground, visible: false });
4286
4783
  this.media.add(this.micTrack);
4287
4784
  this.status.add(this.media);
4288
- this.metricsRow = new BoxRenderable5(renderer2, { id: "omnius-metrics-row", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 1 });
4289
- this.keyHints = new TextRenderable4(renderer2, { id: "omnius-key-hints", content: "F1 HELP TAB FOCUS", height: 1, flexShrink: 0, attributes: TextAttributes6.DIM, fg: colors.foreground, selectable: false });
4785
+ this.copyFeedback = new TextRenderable6(renderer2, {
4786
+ id: "omnius-copy-feedback",
4787
+ content: "",
4788
+ height: 1,
4789
+ flexShrink: 0,
4790
+ fg: colors.foreground,
4791
+ attributes: TextAttributes8.DIM,
4792
+ selectable: false,
4793
+ visible: false
4794
+ });
4795
+ this.status.add(this.copyFeedback);
4796
+ this.metricsRow = new BoxRenderable7(renderer2, { id: "omnius-metrics-row", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 1 });
4797
+ this.keyHints = new TextRenderable6(renderer2, { id: "omnius-key-hints", content: "F1 HELP TAB FOCUS", height: 1, flexShrink: 0, attributes: TextAttributes8.DIM, fg: colors.foreground, selectable: false });
4290
4798
  this.metricsRow.add(this.keyHints);
4291
- this.metricsStartActions = new BoxRenderable5(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4799
+ this.metricsStartActions = new BoxRenderable7(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4292
4800
  this.metricsRow.add(this.metricsStartActions);
4293
- this.metrics = new TextRenderable4(renderer2, { id: "omnius-metrics", content: "", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, flexShrink: 1, flexGrow: 1, minWidth: 0 });
4801
+ this.metrics = new TextRenderable6(renderer2, { id: "omnius-metrics", content: "", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, flexShrink: 1, flexGrow: 1, minWidth: 0 });
4294
4802
  this.metricsRow.add(this.metrics);
4295
- this.metricsEndActions = new BoxRenderable5(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4803
+ this.metricsEndActions = new BoxRenderable7(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4296
4804
  this.metricsRow.add(this.metricsEndActions);
4297
4805
  this.root.add(this.metricsRow);
4298
4806
  this.modals = new NativeModalStack(this.context, () => this.restoreFocus());
4299
4807
  renderer2.keyInput.on("keypress", this.onKey);
4300
4808
  renderer2.keyInput.on("paste", this.onPaste);
4301
- renderer2.on(CliRenderEvents3.FRAME, this.onFrame);
4302
- renderer2.on(CliRenderEvents3.RESIZE, this.onResize);
4809
+ renderer2.on(CliRenderEvents5.FRAME, this.onFrame);
4810
+ renderer2.on(CliRenderEvents5.RESIZE, this.onResize);
4303
4811
  this.applyTheme(initialTheme);
4304
4812
  this.editor.focus();
4305
4813
  }
@@ -4312,12 +4820,16 @@ var NativeFrontend = class {
4312
4820
  content;
4313
4821
  modals;
4314
4822
  context;
4823
+ selectionMenu;
4315
4824
  header;
4316
4825
  identity;
4317
4826
  headerActions;
4318
4827
  headerPanel;
4319
4828
  routeWarning;
4320
4829
  tabs;
4830
+ sessionRow;
4831
+ sessions;
4832
+ newSession;
4321
4833
  taskPane;
4322
4834
  tasks;
4323
4835
  body;
@@ -4329,6 +4841,7 @@ var NativeFrontend = class {
4329
4841
  queue;
4330
4842
  suggestions;
4331
4843
  footer;
4844
+ footerRule;
4332
4845
  prompt;
4333
4846
  vimMode;
4334
4847
  enhance;
@@ -4343,10 +4856,11 @@ var NativeFrontend = class {
4343
4856
  mediaText;
4344
4857
  micTrack;
4345
4858
  clipboard;
4859
+ copyFeedback;
4860
+ copyFeedbackTimer;
4346
4861
  state;
4347
4862
  themeSignature = "";
4348
4863
  headerSignature = "";
4349
- tabsSignature = "";
4350
4864
  suggestionSignature = "";
4351
4865
  metricsActionsSignature = "";
4352
4866
  changing = false;
@@ -4374,8 +4888,8 @@ var NativeFrontend = class {
4374
4888
  selectionArmed = false;
4375
4889
  update(state, patch) {
4376
4890
  const inputWasEnabled = this.state?.input.enabled;
4377
- const previousView = this.state ? `${this.state.activeViewId}:${this.state.presentation ?? "stack"}` : void 0;
4378
- const currentView = `${state.activeViewId}:${state.presentation ?? "stack"}`;
4891
+ const previousView = this.state ? `${this.state.sessions?.activeId ?? ""}:${this.state.activeViewId}:${this.state.presentation ?? "stack"}` : void 0;
4892
+ const currentView = `${state.sessions?.activeId ?? ""}:${state.activeViewId}:${state.presentation ?? "stack"}`;
4379
4893
  const viewChanged = previousView !== void 0 && previousView !== currentView;
4380
4894
  if (viewChanged) {
4381
4895
  this.viewScroll.set(previousView, { top: this.scroll.scrollTop, live: this.offset === 0 });
@@ -4401,14 +4915,13 @@ var NativeFrontend = class {
4401
4915
  }
4402
4916
  this.routeWarning.content = state.routeWarning ? `! ${state.routeWarning}` : "";
4403
4917
  this.routeWarning.visible = Boolean(state.routeWarning);
4404
- const tabsSignature = JSON.stringify(state.tabs);
4405
- if (tabsSignature !== this.tabsSignature) {
4406
- this.tabsSignature = tabsSignature;
4407
- this.tabs.setOptions(state.tabs.map((tab) => ({ name: fitInstrumentText(plainText(tab.label), this.tabs.getTabWidth() - 2), description: "", value: tab.id })));
4408
- }
4409
- this.tabs.visible = state.tabs.length > 1;
4410
- const activeTab = state.tabs.findIndex((tab) => tab.id === state.activeViewId);
4411
- if (activeTab >= 0 && activeTab !== this.tabs.getSelectedIndex()) this.tabs.setSelectedIndex(activeTab);
4918
+ this.tabs.update(state.tabs, state.activeViewId);
4919
+ this.tabs.visible = state.tabs.some((tab) => tab.id !== "main");
4920
+ this.sessionRow.visible = Boolean(state.sessions);
4921
+ this.sessions.update(state.sessions?.items ?? [], state.sessions?.activeId ?? "", state.sessions?.busy);
4922
+ this.newSession.focusable = !state.sessions?.busy;
4923
+ if (state.sessions?.busy && this.newSession.focused) this.getRenderable(`session:${state.sessions.activeId}`)?.focus();
4924
+ this.newSession.attributes = state.sessions?.busy ? TextAttributes8.DIM : this.newSession.focused ? TextAttributes8.INVERSE : 0;
4412
4925
  this.tasks.replace(state.tasks);
4413
4926
  this.taskPane.visible = state.tasks.length > 0;
4414
4927
  if (patch) {
@@ -4430,7 +4943,7 @@ var NativeFrontend = class {
4430
4943
  this.prompt.content = styledText(state.prompt.trimEnd() || "❯");
4431
4944
  if (!this.editorInitialized) {
4432
4945
  this.editor.setText(state.input.text);
4433
- this.editor.cursorOffset = Math.max(0, Math.min(state.input.text.length, state.input.cursor));
4946
+ this.placeInputCursor(state.input.text, state.input.cursor);
4434
4947
  this.editorInitialized = true;
4435
4948
  }
4436
4949
  this.editor.focusable = state.input.enabled && !this.paused;
@@ -4439,6 +4952,7 @@ var NativeFrontend = class {
4439
4952
  this.metrics.visible = state.metricsVisible;
4440
4953
  this.instruments.update(state);
4441
4954
  this.layoutInstruments();
4955
+ this.layoutTabStrips();
4442
4956
  const metricsActions = JSON.stringify(state.metricsActions ?? []);
4443
4957
  if (metricsActions !== this.metricsActionsSignature) {
4444
4958
  this.metricsActionsSignature = metricsActions;
@@ -4461,8 +4975,8 @@ var NativeFrontend = class {
4461
4975
  for (const child of [...parent.getChildren()]) child.destroy();
4462
4976
  for (const action of actions) {
4463
4977
  const colors = this.context.colors;
4464
- const attributes = action.disabled ? TextAttributes6.DIM : action.active ? TextAttributes6.BOLD | TextAttributes6.UNDERLINE : 0;
4465
- const button = new TextRenderable4(this.renderer, {
4978
+ const attributes = action.disabled ? TextAttributes8.DIM : action.active ? TextAttributes8.BOLD | TextAttributes8.UNDERLINE : 0;
4979
+ const button = new TextRenderable6(this.renderer, {
4466
4980
  id: `${prefix}:${action.id}`,
4467
4981
  content: styledText(action.label),
4468
4982
  flexShrink: 0,
@@ -4485,16 +4999,16 @@ var NativeFrontend = class {
4485
4999
  button.focusable = !action.disabled;
4486
5000
  if (!action.disabled) {
4487
5001
  button.on("focused", () => {
4488
- button.attributes = attributes | TextAttributes6.INVERSE;
5002
+ button.attributes = attributes | TextAttributes8.INVERSE;
4489
5003
  });
4490
5004
  button.on("blurred", () => {
4491
5005
  button.attributes = attributes;
4492
5006
  });
4493
5007
  button.onMouseOver = () => {
4494
- button.attributes = attributes | TextAttributes6.UNDERLINE | (button.focused ? TextAttributes6.INVERSE : 0);
5008
+ button.attributes = attributes | TextAttributes8.UNDERLINE | (button.focused ? TextAttributes8.INVERSE : 0);
4495
5009
  };
4496
5010
  button.onMouseOut = () => {
4497
- button.attributes = attributes | (button.focused ? TextAttributes6.INVERSE : 0);
5011
+ button.attributes = attributes | (button.focused ? TextAttributes8.INVERSE : 0);
4498
5012
  };
4499
5013
  }
4500
5014
  parent.add(button);
@@ -4513,12 +5027,12 @@ var NativeFrontend = class {
4513
5027
  const reading = activity?.label || stage || this.state?.instruments?.runtimeStatus || "";
4514
5028
  this.activity.visible = Boolean(activity?.processing || reading);
4515
5029
  this.activity.content = `${activity?.processing ? "RUN · " : ""}${reading}`;
4516
- this.status.visible = this.activity.visible || this.media.visible;
5030
+ this.status.visible = this.activity.visible || this.media.visible || this.copyFeedback.visible;
4517
5031
  }
4518
5032
  renderMedia() {
4519
5033
  const media = this.state?.media;
4520
5034
  this.media.visible = Boolean(media && (media.audio || media.video || media.voiceEnabled || media.recording || media.countdown > 0 || media.label));
4521
- this.status.visible = this.activity.visible || this.media.visible;
5035
+ this.status.visible = this.activity.visible || this.media.visible || this.copyFeedback.visible;
4522
5036
  if (!media) return;
4523
5037
  this.mediaText.content = [
4524
5038
  media.recording ? "● REC" : void 0,
@@ -4556,10 +5070,10 @@ var NativeFrontend = class {
4556
5070
  this.editor.backgroundColor = this.editor.focusedBackgroundColor = colors.background;
4557
5071
  this.editor.textColor = this.editor.focusedTextColor = colors.foreground;
4558
5072
  this.editor.editorView.setSelectionColors(void 0, void 0);
4559
- this.tabs.backgroundColor = this.tabs.focusedBackgroundColor = colors.background;
4560
- this.tabs.textColor = colors.dim;
4561
- this.tabs.selectedTextColor = colors.foreground;
4562
- this.tabs.selectedBackgroundColor = colors.selection;
5073
+ this.tabs.applyTheme(colors);
5074
+ this.sessions.applyTheme(colors);
5075
+ this.newSession.fg = colors.foreground;
5076
+ this.newSession.bg = colors.background;
4563
5077
  this.suggestions.backgroundColor = this.suggestions.focusedBackgroundColor = colors.background;
4564
5078
  this.suggestions.textColor = colors.dim;
4565
5079
  this.suggestions.selectedTextColor = colors.foreground;
@@ -4569,6 +5083,8 @@ var NativeFrontend = class {
4569
5083
  this.instruments.applyTheme(colors);
4570
5084
  this.instrumentDivider.fg = colors.foreground;
4571
5085
  this.instrumentDivider.bg = colors.background;
5086
+ this.copyFeedback.fg = colors.foreground;
5087
+ this.selectionMenu.applyTheme(colors);
4572
5088
  this.content.applyTheme();
4573
5089
  this.tasks.applyTheme();
4574
5090
  this.modals?.applyTheme();
@@ -4669,6 +5185,7 @@ var NativeFrontend = class {
4669
5185
  }
4670
5186
  const height = Math.max(1, Math.min(8, this.editor.editorView.getTotalVirtualLineCount(), this.renderer.terminalHeight - 8));
4671
5187
  if (height !== this.editor.height) this.editor.height = height;
5188
+ this.layoutTabStrips();
4672
5189
  const offset = this.offset;
4673
5190
  if (offset === 0) this.newEntries = 0;
4674
5191
  this.liveButton.visible = offset > 0;
@@ -4683,8 +5200,16 @@ var NativeFrontend = class {
4683
5200
  };
4684
5201
  onResize = () => {
4685
5202
  this.layoutInstruments();
5203
+ this.layoutTabStrips();
4686
5204
  this.emit({ type: "resize", columns: this.renderer.terminalWidth, rows: this.renderer.terminalHeight });
4687
5205
  };
5206
+ layoutTabStrips() {
5207
+ const width = this.renderer.terminalWidth;
5208
+ const junction = this.instrumentDivider.visible && this.instruments.visible ? this.instrumentDivider.x : width - 3;
5209
+ const available = Math.max(0, junction - 6);
5210
+ const desired = Math.min(this.tabs.naturalWidth, available);
5211
+ if (this.tabs.width !== desired) this.tabs.width = desired;
5212
+ }
4688
5213
  editorChanged() {
4689
5214
  if (!this.editor || this.changing || this.destroyed || this.inputEventPending) return;
4690
5215
  this.inputEventPending = true;
@@ -4698,7 +5223,7 @@ var NativeFrontend = class {
4698
5223
  }
4699
5224
  flushLatestInput() {
4700
5225
  const text = this.editor.plainText;
4701
- const cursor = this.editor.cursorOffset;
5226
+ const cursor = this.inputCursor();
4702
5227
  if (text === this.lastInput && cursor === this.lastCursor) return;
4703
5228
  this.lastInput = text;
4704
5229
  this.lastCursor = cursor;
@@ -4741,10 +5266,17 @@ var NativeFrontend = class {
4741
5266
  }
4742
5267
  setInput(text, cursor = text.length) {
4743
5268
  this.editor.replaceText(text);
4744
- this.editor.cursorOffset = Math.max(0, Math.min(text.length, cursor));
5269
+ this.placeInputCursor(text, cursor);
4745
5270
  this.editorInitialized = true;
4746
5271
  this.editorChanged();
4747
5272
  }
5273
+ inputCursor() {
5274
+ return this.editor.getTextRange(0, this.editor.cursorOffset).length;
5275
+ }
5276
+ placeInputCursor(text, cursor) {
5277
+ const index = Math.max(0, Math.min(text.length, cursor));
5278
+ this.editor.cursorOffset = this.editor.editBuffer.measureText(text.slice(0, index));
5279
+ }
4748
5280
  shortcut(name, key) {
4749
5281
  this.flushLatestInput();
4750
5282
  this.emit({ type: "shortcut", name });
@@ -4756,6 +5288,10 @@ var NativeFrontend = class {
4756
5288
  this.emit({ type: "action", action, ...id === void 0 ? {} : { id } });
4757
5289
  }
4758
5290
  onKey = (key) => {
5291
+ if (this.selectionMenu.active) {
5292
+ this.selectionMenu.handleKey(key);
5293
+ return;
5294
+ }
4759
5295
  if (this.modals.active) {
4760
5296
  this.modals.active.handleKey(key);
4761
5297
  return;
@@ -4908,7 +5444,7 @@ var NativeFrontend = class {
4908
5444
  const targets = [];
4909
5445
  const visit = (node) => {
4910
5446
  if (!node.visible) return;
4911
- if (node.focusable && node !== this.scroll && !(node instanceof ScrollBoxRenderable4) && node.height > 0 && node.width > 0) targets.push(node);
5447
+ if (node.focusable && node !== this.scroll && !(node instanceof ScrollBoxRenderable5) && node.height > 0 && node.width > 0) targets.push(node);
4912
5448
  for (const child of node.getChildren()) visit(child);
4913
5449
  };
4914
5450
  visit(this.root);
@@ -4927,6 +5463,10 @@ var NativeFrontend = class {
4927
5463
  }
4928
5464
  }
4929
5465
  onPaste = (event) => {
5466
+ if (this.selectionMenu.active) {
5467
+ event.preventDefault();
5468
+ return;
5469
+ }
4930
5470
  if (this.modals.active) {
4931
5471
  this.modals.active.handlePaste(event);
4932
5472
  return;
@@ -4943,8 +5483,27 @@ var NativeFrontend = class {
4943
5483
  else if (!this.paused && this.state?.input.enabled !== false) this.editor.focus();
4944
5484
  }
4945
5485
  async copy(text) {
4946
- const value = text ?? this.renderer.getSelection()?.getSelectedText() ?? this.editor.getSelectedText();
4947
- if (value) await this.clipboard.writeText(value, { destination: "best-available" });
5486
+ const value = text ?? (this.renderer.getSelection()?.getSelectedText() || this.editor.getSelectedText());
5487
+ if (!value) return;
5488
+ let message;
5489
+ try {
5490
+ const result = await this.clipboard.writeText(value, { destination: "best-available" });
5491
+ message = result.host.status === "written" ? "COPIED" : result.terminal.status === "attempted" ? "COPY SENT TO TERMINAL" : "COPY UNAVAILABLE";
5492
+ } catch {
5493
+ message = "COPY FAILED";
5494
+ }
5495
+ if (this.destroyed) return;
5496
+ if (this.copyFeedbackTimer) clearTimeout(this.copyFeedbackTimer);
5497
+ this.copyFeedback.content = message;
5498
+ this.copyFeedback.visible = true;
5499
+ this.status.visible = true;
5500
+ this.renderer.requestRender();
5501
+ this.copyFeedbackTimer = setTimeout(() => {
5502
+ this.copyFeedbackTimer = void 0;
5503
+ this.copyFeedback.visible = false;
5504
+ this.status.visible = this.activity.visible || this.media.visible;
5505
+ this.renderer.requestRender();
5506
+ }, 3e3);
4948
5507
  }
4949
5508
  async paste() {
4950
5509
  const result = await this.clipboard.read({ preferredTypes: ["text/plain;charset=utf-8", "text/plain"] });
@@ -4967,7 +5526,7 @@ var NativeFrontend = class {
4967
5526
  break;
4968
5527
  }
4969
5528
  case "input":
4970
- if (command2.expectedText !== void 0 && command2.expectedText !== this.editor.plainText || command2.expectedCursor !== void 0 && command2.expectedCursor !== this.editor.cursorOffset) {
5529
+ if (command2.expectedText !== void 0 && command2.expectedText !== this.editor.plainText || command2.expectedCursor !== void 0 && command2.expectedCursor !== this.inputCursor()) {
4971
5530
  this.flushLatestInput();
4972
5531
  } else this.setInput(command2.text, command2.cursor);
4973
5532
  break;
@@ -4988,6 +5547,7 @@ var NativeFrontend = class {
4988
5547
  this.renderer.setTerminalTitle(command2.text);
4989
5548
  break;
4990
5549
  case "modal-open":
5550
+ this.selectionMenu.close();
4991
5551
  this.modals.open(command2.modal);
4992
5552
  break;
4993
5553
  case "modal-update":
@@ -5071,11 +5631,13 @@ var NativeFrontend = class {
5071
5631
  async dispose() {
5072
5632
  if (this.destroyed) return;
5073
5633
  this.destroyed = true;
5634
+ if (this.copyFeedbackTimer) clearTimeout(this.copyFeedbackTimer);
5635
+ this.selectionMenu.destroy();
5074
5636
  this.disposeSelectionContrast();
5075
5637
  this.renderer.keyInput.off("keypress", this.onKey);
5076
5638
  this.renderer.keyInput.off("paste", this.onPaste);
5077
- this.renderer.off(CliRenderEvents3.FRAME, this.onFrame);
5078
- this.renderer.off(CliRenderEvents3.RESIZE, this.onResize);
5639
+ this.renderer.off(CliRenderEvents5.FRAME, this.onFrame);
5640
+ this.renderer.off(CliRenderEvents5.RESIZE, this.onResize);
5079
5641
  this.modals.destroy();
5080
5642
  this.content.destroy();
5081
5643
  this.tasks.destroy();
@@ -5086,7 +5648,7 @@ var NativeFrontend = class {
5086
5648
  };
5087
5649
 
5088
5650
  // packages/cli/src/tui/native/components/cursor.ts
5089
- import { CliRenderEvents as CliRenderEvents4 } from "@opentui/core";
5651
+ import { CliRenderEvents as CliRenderEvents6 } from "@opentui/core";
5090
5652
  function installTerminalCursorDefaults(renderer2, session) {
5091
5653
  const reset = new TextEncoder().encode("\x1B]112\x07");
5092
5654
  let disposed = false;
@@ -5136,11 +5698,11 @@ function installTerminalCursorDefaults(renderer2, session) {
5136
5698
  const dispose = () => {
5137
5699
  if (disposed) return;
5138
5700
  disposed = true;
5139
- renderer2.off(CliRenderEvents4.FRAME, afterFrame);
5140
- renderer2.off(CliRenderEvents4.DESTROY, dispose);
5701
+ renderer2.off(CliRenderEvents6.FRAME, afterFrame);
5702
+ renderer2.off(CliRenderEvents6.DESTROY, dispose);
5141
5703
  };
5142
- renderer2.on(CliRenderEvents4.FRAME, afterFrame);
5143
- renderer2.once(CliRenderEvents4.DESTROY, dispose);
5704
+ renderer2.on(CliRenderEvents6.FRAME, afterFrame);
5705
+ renderer2.once(CliRenderEvents6.DESTROY, dispose);
5144
5706
  return { restore, dispose };
5145
5707
  }
5146
5708
 
@@ -5216,10 +5778,10 @@ try {
5216
5778
  maxFps: 60,
5217
5779
  clearOnShutdown: false
5218
5780
  });
5219
- renderer.on(CliRenderEvents5.RENDER_ERROR, (event) => {
5781
+ renderer.on(CliRenderEvents7.RENDER_ERROR, (event) => {
5220
5782
  void fail(event.error);
5221
5783
  });
5222
- renderer.on(CliRenderEvents5.HANDLER_ERROR, (event) => {
5784
+ renderer.on(CliRenderEvents7.HANDLER_ERROR, (event) => {
5223
5785
  void fail(event.error);
5224
5786
  });
5225
5787
  renderer.on("error", (event) => {