omnius 1.0.884 → 1.0.887

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.
@@ -2,7 +2,7 @@
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 CliRenderEvents10, NativeSession, RGBA as RGBA4, createCliRenderer } from "@opentui/core";
5
+ import { CliRenderEvents as CliRenderEvents11, NativeSession, RGBA as RGBA4, createCliRenderer } from "@opentui/core";
6
6
 
7
7
  // packages/cli/src/tui/native/components/frontend.ts
8
8
  import {
@@ -179,6 +179,27 @@ function nativeTextFromAnsi(value) {
179
179
  }
180
180
 
181
181
  // packages/cli/src/tui/native/components/text.ts
182
+ var colorCache = /* @__PURE__ */ new Map();
183
+ function nativeColor(value, background2 = false) {
184
+ if (value === void 0 || value === -1 || value === "default")
185
+ return background2 ? RGBA.defaultBackground() : RGBA.defaultForeground();
186
+ const key = String(value);
187
+ if (key === "index:-1") return background2 ? RGBA.defaultBackground() : RGBA.defaultForeground();
188
+ let color = colorCache.get(key);
189
+ if (!color) {
190
+ const index = typeof value === "number" || /^\d+$/.test(key) ? Number(value) : key.startsWith("index:") ? Number(key.slice(6)) : void 0;
191
+ if (index !== void 0 ? !Number.isInteger(index) || index < 0 || index > 255 : !/^#?(?:[\da-f]{3}|[\da-f]{4}|[\da-f]{6}|[\da-f]{8})$/i.test(key))
192
+ return background2 ? RGBA.defaultBackground() : RGBA.defaultForeground();
193
+ try {
194
+ color = index === void 0 ? RGBA.fromHex(key) : RGBA.fromIndex(index);
195
+ } catch {
196
+ return background2 ? RGBA.defaultBackground() : RGBA.defaultForeground();
197
+ }
198
+ if (colorCache.size >= 1024) colorCache.delete(colorCache.keys().next().value);
199
+ colorCache.set(key, color);
200
+ }
201
+ return color;
202
+ }
182
203
  function plainText(value) {
183
204
  return typeof value === "string" ? stripTextControls(value) : (value ?? []).map((span) => stripTextControls(span.text)).join("");
184
205
  }
@@ -189,6 +210,7 @@ function styledText(value) {
189
210
  return new StyledText(textSpans(value).map((span) => ({
190
211
  __isChunk: true,
191
212
  text: span.text,
213
+ ...span.fg === void 0 ? {} : { fg: nativeColor(span.fg) },
192
214
  ...span.attributes === void 0 ? {} : { attributes: span.attributes },
193
215
  ...span.link ? { link: { url: span.link } } : {}
194
216
  })));
@@ -1653,6 +1675,16 @@ var NativeBlockView = class {
1653
1675
  get block() {
1654
1676
  return this.spec;
1655
1677
  }
1678
+ get trailingSpace() {
1679
+ return this.spec.kind === "markdown" && this.spec.presentation === "final-answer" ? 1 : 0;
1680
+ }
1681
+ get hasVisibleContent() {
1682
+ return this.spec.kind !== "column" && this.spec.kind !== "row" || Boolean(this.spec.title || this.spec.text !== void 0 || this.spec.actions?.length || [...this.children?.views.values() ?? []].some((view) => view.hasVisibleContent));
1683
+ }
1684
+ setTurnSeparation(separate, previousBottom = 0) {
1685
+ const answer = this.spec.kind === "markdown" && this.spec.presentation === "final-answer";
1686
+ this.root.marginTop = separate ? Math.max(0, 1 - previousBottom) : answer ? 1 : 0;
1687
+ }
1656
1688
  build() {
1657
1689
  const { renderer: renderer2, colors, syntax } = this.context;
1658
1690
  const spec = this.spec;
@@ -1951,12 +1983,13 @@ var NativeBlockView = class {
1951
1983
  }
1952
1984
  applyTheme() {
1953
1985
  const colors = this.context.colors;
1986
+ const heading = this.spec.color === void 0 ? colors.foreground : nativeColor(this.spec.color);
1954
1987
  this.root.backgroundColor = colors.background;
1955
1988
  this.root.shouldFill = this.spec.kind !== "model3d";
1956
- if (this.spec.kind === "card") this.root.borderColor = colors.border;
1989
+ if (this.spec.kind === "card") this.root.borderColor = this.spec.color === void 0 ? colors.border : heading;
1957
1990
  else this.root.border = false;
1958
1991
  if (this.title) {
1959
- this.title.fg = colors.foreground;
1992
+ this.title.fg = heading;
1960
1993
  this.title.bg = colors.background;
1961
1994
  }
1962
1995
  if (this.body) {
@@ -1964,11 +1997,11 @@ var NativeBlockView = class {
1964
1997
  this.body.shouldFill = this.spec.kind !== "model3d";
1965
1998
  }
1966
1999
  if (this.text) {
1967
- this.text.fg = colors.foreground;
2000
+ this.text.fg = this.spec.kind === "text" || this.spec.kind === "progress" ? heading : colors.foreground;
1968
2001
  this.text.bg = colors.background;
1969
2002
  }
1970
2003
  if (this.progress) {
1971
- this.progress.fg = colors.foreground;
2004
+ this.progress.fg = this.spec.color === void 0 ? colors.accent : heading;
1972
2005
  this.progress.bg = colors.background;
1973
2006
  }
1974
2007
  for (const action of this.actions?.getChildren() ?? []) if (action instanceof TextRenderable3) {
@@ -1998,12 +2031,14 @@ var NativeBlockView = class {
1998
2031
  }
1999
2032
  };
2000
2033
  var NativeBlockList = class {
2001
- constructor(context, parent) {
2034
+ constructor(context, parent, separateTurns = false) {
2002
2035
  this.context = context;
2003
2036
  this.parent = parent;
2037
+ this.separateTurns = separateTurns;
2004
2038
  }
2005
2039
  context;
2006
2040
  parent;
2041
+ separateTurns;
2007
2042
  views = /* @__PURE__ */ new Map();
2008
2043
  order = [];
2009
2044
  patch(patch) {
@@ -2021,6 +2056,20 @@ var NativeBlockList = class {
2021
2056
  }
2022
2057
  }
2023
2058
  if (patch.order) this.reorder(patch.order);
2059
+ if (this.separateTurns) {
2060
+ let turnId, previousBottom = 0;
2061
+ for (const id of this.order) {
2062
+ const view = this.views.get(id);
2063
+ if (!view.hasVisibleContent) {
2064
+ view.setTurnSeparation(false);
2065
+ continue;
2066
+ }
2067
+ const nextTurn = view.block.turnId;
2068
+ view.setTurnSeparation(Boolean(turnId && nextTurn && turnId !== nextTurn), previousBottom);
2069
+ if (nextTurn) turnId = nextTurn;
2070
+ previousBottom = view.trailingSpace;
2071
+ }
2072
+ }
2024
2073
  }
2025
2074
  replace(blocks) {
2026
2075
  const keep = new Set(blocks.map((block) => block.id));
@@ -7671,7 +7720,7 @@ var NativeTabStrip = class extends BoxRenderable11 {
7671
7720
  },
7672
7721
  onKeyDown: (key) => this.handleKey(entry, key)
7673
7722
  });
7674
- entry = { action, active: false, blocked: false, button };
7723
+ entry = { action, active: false, blocked: false, closeBlocked: false, button };
7675
7724
  this.buttons.set(action.id, entry);
7676
7725
  button.on("focused", () => {
7677
7726
  this.style(entry);
@@ -7691,7 +7740,7 @@ var NativeTabStrip = class extends BoxRenderable11 {
7691
7740
  fg: this.colors.foreground,
7692
7741
  bg: this.colors.background,
7693
7742
  onMouseDown: (event) => {
7694
- if (event.button === 0 && !entry.blocked) {
7743
+ if (event.button === 0 && !entry.closeBlocked) {
7695
7744
  close2.focus();
7696
7745
  this.options.close(entry.action.id);
7697
7746
  }
@@ -7712,8 +7761,9 @@ var NativeTabStrip = class extends BoxRenderable11 {
7712
7761
  entry.action = action;
7713
7762
  entry.active = action.id === activeId;
7714
7763
  entry.blocked = busy || Boolean(action.disabled);
7764
+ entry.closeBlocked = entry.blocked || Boolean(action.closeDisabled);
7715
7765
  entry.button.focusable = entry.active || !entry.blocked;
7716
- if (entry.close) entry.close.focusable = !entry.blocked;
7766
+ if (entry.close) entry.close.focusable = !entry.closeBlocked;
7717
7767
  const label3 = plainText(action.label);
7718
7768
  const content = ` ${label3} `;
7719
7769
  if (entry.button.plainText !== content) entry.button.content = content;
@@ -7731,17 +7781,17 @@ var NativeTabStrip = class extends BoxRenderable11 {
7731
7781
  }
7732
7782
  style(entry) {
7733
7783
  entry.button.attributes = (entry.active ? TextAttributes10.BOLD | TextAttributes10.UNDERLINE : entry.blocked ? TextAttributes10.DIM : 0) | (entry.button.focused ? TextAttributes10.INVERSE : 0);
7734
- if (entry.close) entry.close.attributes = (entry.blocked || !entry.close.focused ? TextAttributes10.DIM : 0) | (entry.close.focused ? TextAttributes10.INVERSE : 0);
7784
+ if (entry.close) entry.close.attributes = (entry.closeBlocked || !entry.close.focused ? TextAttributes10.DIM : 0) | (entry.close.focused ? TextAttributes10.INVERSE : 0);
7735
7785
  }
7736
7786
  handleKey(entry, key, close2 = false) {
7737
7787
  if (key.eventType === "release") return;
7738
7788
  if (key.name === "return" || key.sequence === " ") {
7739
- if (!entry.blocked) {
7789
+ if (close2 ? !entry.closeBlocked : !entry.blocked) {
7740
7790
  if (close2) this.options.close?.(entry.action.id);
7741
7791
  else this.options.activate(entry.action.id);
7742
7792
  }
7743
7793
  } else if (this.options.close && (key.name === "delete" || key.ctrl && key.name.toLowerCase() === "w")) {
7744
- if (!entry.blocked) this.options.close(entry.action.id);
7794
+ if (!entry.closeBlocked) this.options.close(entry.action.id);
7745
7795
  } else if (["left", "right", "home", "end"].includes(key.name)) {
7746
7796
  const targets = this.scroll.getChildren().filter((button) => button.focusable && button.id.startsWith(`${this.options.prefix}:`));
7747
7797
  const index = targets.indexOf(entry.button);
@@ -7975,13 +8025,13 @@ var NativeFrontend = class {
7975
8025
  fg: colors.foreground,
7976
8026
  bg: colors.background,
7977
8027
  onMouseDown: (event) => {
7978
- if (event.button === 0 && !this.state?.sessions?.busy) this.emitAction("session-new");
8028
+ if (event.button === 0) this.emitAction("session-new");
7979
8029
  event.preventDefault();
7980
8030
  event.stopPropagation();
7981
8031
  },
7982
8032
  onKeyDown: (key) => {
7983
8033
  if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
7984
- if (!this.state?.sessions?.busy) this.emitAction("session-new");
8034
+ this.emitAction("session-new");
7985
8035
  key.preventDefault();
7986
8036
  key.stopPropagation();
7987
8037
  }
@@ -7989,7 +8039,7 @@ var NativeFrontend = class {
7989
8039
  });
7990
8040
  this.newSession.focusable = true;
7991
8041
  const styleNew = () => {
7992
- this.newSession.attributes = (this.state?.sessions?.busy ? TextAttributes11.DIM : 0) | (this.newSession.focused ? TextAttributes11.INVERSE : 0);
8042
+ this.newSession.attributes = this.newSession.focused ? TextAttributes11.INVERSE : 0;
7993
8043
  };
7994
8044
  this.newSession.on("focused", styleNew);
7995
8045
  this.newSession.on("blurred", styleNew);
@@ -8004,13 +8054,13 @@ var NativeFrontend = class {
8004
8054
  fg: colors.foreground,
8005
8055
  bg: colors.background,
8006
8056
  onMouseDown: (event) => {
8007
- if (event.button === 0 && !this.state?.sessions?.busy) this.emitAction("session-picker");
8057
+ if (event.button === 0) this.emitAction("session-picker");
8008
8058
  event.preventDefault();
8009
8059
  event.stopPropagation();
8010
8060
  },
8011
8061
  onKeyDown: (key) => {
8012
8062
  if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
8013
- if (!this.state?.sessions?.busy) this.emitAction("session-picker");
8063
+ this.emitAction("session-picker");
8014
8064
  key.preventDefault();
8015
8065
  key.stopPropagation();
8016
8066
  }
@@ -8018,7 +8068,7 @@ var NativeFrontend = class {
8018
8068
  });
8019
8069
  this.sessionPicker.focusable = true;
8020
8070
  const stylePicker = () => {
8021
- this.sessionPicker.attributes = (this.state?.sessions?.busy ? TextAttributes11.DIM : 0) | (this.sessionPicker.focused ? TextAttributes11.INVERSE : 0);
8071
+ this.sessionPicker.attributes = this.sessionPicker.focused ? TextAttributes11.INVERSE : 0;
8022
8072
  };
8023
8073
  this.sessionPicker.on("focused", stylePicker);
8024
8074
  this.sessionPicker.on("blurred", stylePicker);
@@ -8096,7 +8146,7 @@ var NativeFrontend = class {
8096
8146
  } }
8097
8147
  });
8098
8148
  this.transcriptPane.add(this.scroll);
8099
- this.content = new NativeBlockList(this.context, this.scroll);
8149
+ this.content = new NativeBlockList(this.context, this.scroll, true);
8100
8150
  this.instrumentDivider = new InstrumentDivider(renderer2, {
8101
8151
  resize: (column3) => this.resizeInstruments(column3),
8102
8152
  toggle: () => this.toggleDetails(),
@@ -8392,6 +8442,7 @@ var NativeFrontend = class {
8392
8442
  history = [];
8393
8443
  historyIndex = -1;
8394
8444
  savedInput = "";
8445
+ savedInputCursor = 0;
8395
8446
  disposeSelectionContrast;
8396
8447
  actionSignatures = /* @__PURE__ */ new WeakMap();
8397
8448
  detailsOpen;
@@ -8416,6 +8467,7 @@ var NativeFrontend = class {
8416
8467
  const previousView = this.state ? `${this.state.sessions?.activeId ?? ""}:${this.state.activeViewId}:${this.state.presentation ?? "stack"}` : void 0;
8417
8468
  const currentView = `${state.sessions?.activeId ?? ""}:${state.activeViewId}:${state.presentation ?? "stack"}`;
8418
8469
  const viewChanged = previousView !== void 0 && previousView !== currentView;
8470
+ if (this.state?.sessions?.activeId !== state.sessions?.activeId) this.resetHistoryNavigation();
8419
8471
  if (viewChanged) {
8420
8472
  this.viewScroll.set(previousView, { top: this.scroll.scrollTop, live: this.offset === 0 });
8421
8473
  this.viewDisclosures.set(previousView, this.content.captureDisclosures(this.tasks.captureDisclosures()));
@@ -8446,12 +8498,14 @@ var NativeFrontend = class {
8446
8498
  this.tabs.visible = state.tabs.some((tab) => tab.id !== "main");
8447
8499
  this.viewTabsRow.visible = this.tabs.visible;
8448
8500
  this.sessionRow.visible = Boolean(state.sessions);
8449
- this.sessions.update(state.sessions?.items ?? [], state.sessions?.activeId ?? "", state.sessions?.busy);
8450
- this.newSession.focusable = !state.sessions?.busy;
8451
- this.sessionPicker.focusable = !state.sessions?.busy;
8452
- if (state.sessions?.busy && (this.newSession.focused || this.sessionPicker.focused)) this.getRenderable(`session:${state.sessions.activeId}`)?.focus();
8453
- this.newSession.attributes = state.sessions?.busy ? TextAttributes11.DIM : this.newSession.focused ? TextAttributes11.INVERSE : 0;
8454
- this.sessionPicker.attributes = state.sessions?.busy ? TextAttributes11.DIM : this.sessionPicker.focused ? TextAttributes11.INVERSE : 0;
8501
+ this.sessions.update((state.sessions?.items ?? []).map((item) => ({
8502
+ ...item,
8503
+ disabled: item.disabled ?? false,
8504
+ closeDisabled: item.closeDisabled ?? Boolean(state.sessions?.busy)
8505
+ })), state.sessions?.activeId ?? "");
8506
+ this.newSession.focusable = this.sessionPicker.focusable = true;
8507
+ this.newSession.attributes = this.newSession.focused ? TextAttributes11.INVERSE : 0;
8508
+ this.sessionPicker.attributes = this.sessionPicker.focused ? TextAttributes11.INVERSE : 0;
8455
8509
  this.tasks.replace(state.taskNotes ? [] : state.tasks);
8456
8510
  this.taskPane.visible = !state.taskNotes && state.tasks.length > 0;
8457
8511
  this.taskStrip.update(state.taskNotes);
@@ -8474,7 +8528,7 @@ var NativeFrontend = class {
8474
8528
  const index = state.suggestions.findIndex((item) => item.value === selected);
8475
8529
  if (index >= 0) this.suggestions.setSelectedIndex(index);
8476
8530
  }
8477
- this.suggestions.visible = state.suggestions.length > 0;
8531
+ this.suggestions.visible = this.historyIndex < 0 && state.suggestions.length > 0;
8478
8532
  this.suggestions.height = Math.min(4, state.suggestions.length || 1);
8479
8533
  this.prompt.content = styledText(state.prompt.trimEnd() || "❯");
8480
8534
  if (!this.editorInitialized) {
@@ -8724,6 +8778,28 @@ var NativeFrontend = class {
8724
8778
  return visit(this.renderer.root);
8725
8779
  }
8726
8780
  onFrame = () => {
8781
+ if (this.destroyed) return;
8782
+ this.flushPendingInterfaceLayout();
8783
+ const height = Math.max(1, Math.min(8, this.editor.editorView.getTotalVirtualLineCount(), this.renderer.terminalHeight - 8));
8784
+ if (height !== this.editor.height) this.editor.height = height;
8785
+ this.layoutTabStrips();
8786
+ 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 && !this.modals.active);
8787
+ for (const model of this.context.modelViews ?? []) model.reconcile(this.scroll.viewport, !this.paused && !this.modals.active);
8788
+ this.modals.reconcileMedia();
8789
+ const offset2 = this.offset;
8790
+ if (offset2 === 0) this.newEntries = 0;
8791
+ this.liveButton.visible = offset2 > 0;
8792
+ const liveLabel = offset2 > 0 ? `↓ Live${this.newEntries ? ` · ${this.newEntries} new` : ""}` : "↓ Live";
8793
+ if (this.liveButton.plainText !== liveLabel) this.liveButton.content = liveLabel;
8794
+ const event = { type: "viewport", offset: this.scroll.scrollTop, height: this.scroll.viewport.height, width: this.scroll.viewport.width, total: this.scroll.scrollHeight };
8795
+ const signature = JSON.stringify(event);
8796
+ if (signature !== this.viewportSignature) {
8797
+ this.viewportSignature = signature;
8798
+ this.emit(event);
8799
+ }
8800
+ };
8801
+ /** Restore presentation after native layout, without requiring terminal drain. */
8802
+ flushPendingInterfaceLayout() {
8727
8803
  if (this.destroyed) return;
8728
8804
  if (this.pendingInterface) {
8729
8805
  const state = this.pendingInterface;
@@ -8747,24 +8823,7 @@ var NativeFrontend = class {
8747
8823
  this.scroll.scrollTo(restore.live ? this.scroll.scrollHeight : restore.top);
8748
8824
  this.renderer.requestRender();
8749
8825
  }
8750
- const height = Math.max(1, Math.min(8, this.editor.editorView.getTotalVirtualLineCount(), this.renderer.terminalHeight - 8));
8751
- if (height !== this.editor.height) this.editor.height = height;
8752
- this.layoutTabStrips();
8753
- 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 && !this.modals.active);
8754
- for (const model of this.context.modelViews ?? []) model.reconcile(this.scroll.viewport, !this.paused && !this.modals.active);
8755
- this.modals.reconcileMedia();
8756
- const offset2 = this.offset;
8757
- if (offset2 === 0) this.newEntries = 0;
8758
- this.liveButton.visible = offset2 > 0;
8759
- const liveLabel = offset2 > 0 ? `↓ Live${this.newEntries ? ` · ${this.newEntries} new` : ""}` : "↓ Live";
8760
- if (this.liveButton.plainText !== liveLabel) this.liveButton.content = liveLabel;
8761
- const event = { type: "viewport", offset: this.scroll.scrollTop, height: this.scroll.viewport.height, width: this.scroll.viewport.width, total: this.scroll.scrollHeight };
8762
- const signature = JSON.stringify(event);
8763
- if (signature !== this.viewportSignature) {
8764
- this.viewportSignature = signature;
8765
- this.emit(event);
8766
- }
8767
- };
8826
+ }
8768
8827
  onResize = () => {
8769
8828
  this.layoutInstruments();
8770
8829
  this.layoutTabStrips();
@@ -8792,6 +8851,7 @@ var NativeFrontend = class {
8792
8851
  const text = this.editor.plainText;
8793
8852
  const cursor = this.inputCursor();
8794
8853
  if (text === this.lastInput && cursor === this.lastCursor) return;
8854
+ if (this.historyIndex >= 0 && text !== this.history[this.historyIndex]) this.resetHistoryNavigation();
8795
8855
  this.lastInput = text;
8796
8856
  this.lastCursor = cursor;
8797
8857
  this.renderEnhance();
@@ -8808,19 +8868,31 @@ var NativeFrontend = class {
8808
8868
  this.history.unshift(text);
8809
8869
  this.history.length = Math.min(this.history.length, 1e3);
8810
8870
  }
8811
- this.historyIndex = -1;
8812
- this.savedInput = "";
8871
+ this.resetHistoryNavigation();
8813
8872
  this.editor.clear();
8814
8873
  this.emit({ type: "submit", text });
8815
8874
  this.editorChanged();
8816
8875
  }
8876
+ resetHistoryNavigation() {
8877
+ this.historyIndex = -1;
8878
+ this.savedInput = "";
8879
+ this.savedInputCursor = 0;
8880
+ }
8817
8881
  navigateHistory(direction) {
8818
8882
  if (this.history.length === 0) return;
8819
8883
  if (direction < 0) {
8820
- if (this.historyIndex < 0) this.savedInput = this.editor.plainText;
8884
+ if (this.historyIndex < 0) {
8885
+ this.savedInput = this.editor.plainText;
8886
+ this.savedInputCursor = this.inputCursor();
8887
+ }
8821
8888
  this.historyIndex = Math.min(this.history.length - 1, this.historyIndex + 1);
8822
- } else this.historyIndex = Math.max(-1, this.historyIndex - 1);
8823
- this.setInput(this.historyIndex < 0 ? this.savedInput : this.history[this.historyIndex]);
8889
+ } else {
8890
+ if (this.historyIndex < 0) return;
8891
+ this.historyIndex--;
8892
+ }
8893
+ this.suggestions.visible = false;
8894
+ if (this.historyIndex < 0) this.setInput(this.savedInput, this.savedInputCursor, true);
8895
+ else this.setInput(this.history[this.historyIndex], void 0, true);
8824
8896
  }
8825
8897
  acceptSuggestion() {
8826
8898
  const selected = this.suggestions.getSelectedOption();
@@ -8831,7 +8903,8 @@ var NativeFrontend = class {
8831
8903
  this.emitAction("completion", text);
8832
8904
  return true;
8833
8905
  }
8834
- setInput(text, cursor = text.length) {
8906
+ setInput(text, cursor = text.length, fromHistory = false) {
8907
+ if (!fromHistory) this.resetHistoryNavigation();
8835
8908
  this.editor.replaceText(text);
8836
8909
  this.placeInputCursor(text, cursor);
8837
8910
  this.editorInitialized = true;
@@ -8983,14 +9056,26 @@ var NativeFrontend = class {
8983
9056
  return;
8984
9057
  }
8985
9058
  if (key.name === "up" || key.name === "down") {
8986
- if (this.suggestions.visible) {
8987
- if (key.name === "up") this.suggestions.moveUp();
8988
- else this.suggestions.moveDown();
9059
+ if (this.renderer.currentFocusedRenderable === this.scroll) {
9060
+ this.scroll.scrollBy(key.name === "up" ? -1 : 1, "absolute");
8989
9061
  key.preventDefault();
8990
9062
  return;
8991
9063
  }
8992
- if (this.renderer.currentFocusedRenderable === this.scroll || this.offset > 0 || this.state?.activity?.processing && !this.editor.plainText) {
8993
- this.scroll.scrollBy(key.name === "up" ? -1 : 1, "absolute");
9064
+ if (this.paused || this.state?.input.enabled === false) {
9065
+ key.preventDefault();
9066
+ return;
9067
+ }
9068
+ if (this.suggestions.visible) {
9069
+ const index = this.suggestions.getSelectedIndex();
9070
+ const edge = key.name === "up" ? index <= 0 : index >= this.suggestions.options.length - 1;
9071
+ if (edge) {
9072
+ this.suggestions.visible = false;
9073
+ this.navigateHistory(key.name === "up" ? -1 : 1);
9074
+ key.preventDefault();
9075
+ return;
9076
+ }
9077
+ if (key.name === "up") this.suggestions.moveUp();
9078
+ else this.suggestions.moveDown();
8994
9079
  key.preventDefault();
8995
9080
  return;
8996
9081
  }
@@ -9001,6 +9086,7 @@ var NativeFrontend = class {
9001
9086
  key.preventDefault();
9002
9087
  return;
9003
9088
  }
9089
+ this.editorChanged();
9004
9090
  }
9005
9091
  if (this.paused || this.state?.input.enabled === false) {
9006
9092
  if (this.renderer.currentFocusedRenderable === this.editor) key.preventDefault();
@@ -9244,6 +9330,11 @@ var NativeFrontend = class {
9244
9330
  if (this.state) this.update(this.state);
9245
9331
  this.layoutInstruments();
9246
9332
  this.renderer.requestRender();
9333
+ const pendingScroll = this.pendingScrollRestore;
9334
+ return () => {
9335
+ if (this.pendingInterface === state) this.pendingInterface = void 0;
9336
+ if (this.pendingScrollRestore === pendingScroll) this.pendingScrollRestore = void 0;
9337
+ };
9247
9338
  }
9248
9339
  async dispose() {
9249
9340
  if (this.destroyed) return;
@@ -9338,6 +9429,70 @@ function installTerminalCursorDefaults(renderer2, session) {
9338
9429
  return { restore, dispose };
9339
9430
  }
9340
9431
 
9432
+ // packages/cli/src/tui/native/layout-barrier.ts
9433
+ import { CliRenderEvents as CliRenderEvents10 } from "@opentui/core";
9434
+ var NATIVE_INTERFACE_LAYOUT_TIMEOUT_MS = 1e3;
9435
+ function waitForNativeLayout(renderer2, applyPendingLayout, options = {}) {
9436
+ const deadline = options.deadline ?? performance.now() + (options.timeoutMs ?? NATIVE_INTERFACE_LAYOUT_TIMEOUT_MS);
9437
+ return new Promise((resolve2, reject) => {
9438
+ let settled = false;
9439
+ let queued = false;
9440
+ let timer;
9441
+ const finish = (error) => {
9442
+ if (settled) return;
9443
+ settled = true;
9444
+ clearTimeout(timer);
9445
+ renderer2.removePostProcessFn(afterLayout);
9446
+ renderer2.off(CliRenderEvents10.DESTROY, destroyed);
9447
+ options.signal?.removeEventListener("abort", aborted);
9448
+ if (error) reject(error);
9449
+ else resolve2();
9450
+ };
9451
+ const destroyed = () => finish(new Error("OpenTUI closed before interface layout completed"));
9452
+ const aborted = () => finish(new Error("OpenTUI interface layout was cancelled"));
9453
+ const afterLayout = (_buffer) => {
9454
+ if (queued || settled) return;
9455
+ queued = true;
9456
+ queueMicrotask(() => {
9457
+ if (settled) return;
9458
+ if (performance.now() >= deadline) {
9459
+ timedOut();
9460
+ return;
9461
+ }
9462
+ try {
9463
+ applyPendingLayout();
9464
+ finish();
9465
+ } catch (error) {
9466
+ finish(error instanceof Error ? error : new Error("OpenTUI interface layout failed"));
9467
+ }
9468
+ });
9469
+ };
9470
+ renderer2.addPostProcessFn(afterLayout);
9471
+ renderer2.once(CliRenderEvents10.DESTROY, destroyed);
9472
+ options.signal?.addEventListener("abort", aborted, { once: true });
9473
+ if (options.signal?.aborted) {
9474
+ aborted();
9475
+ return;
9476
+ }
9477
+ if (renderer2.isDestroyed) {
9478
+ destroyed();
9479
+ return;
9480
+ }
9481
+ const timeoutMs = deadline - performance.now();
9482
+ const timedOut = () => finish(new Error("OpenTUI interface layout did not complete; terminal output may be paused"));
9483
+ if (timeoutMs <= 0) {
9484
+ timedOut();
9485
+ return;
9486
+ }
9487
+ timer = setTimeout(timedOut, timeoutMs);
9488
+ try {
9489
+ renderer2.requestRender();
9490
+ } catch (error) {
9491
+ finish(error instanceof Error ? error : new Error("OpenTUI interface layout failed"));
9492
+ }
9493
+ });
9494
+ }
9495
+
9341
9496
  // packages/cli/src/tui/native/worker.ts
9342
9497
  var replies = createWriteStream("", { fd: 3, autoClose: false });
9343
9498
  var reply = (message) => replies.write(JSON.stringify(message) + "\n");
@@ -9347,9 +9502,11 @@ var frontend;
9347
9502
  var cursorDefaults;
9348
9503
  var stopping = false;
9349
9504
  var failed = false;
9505
+ var layoutController = new AbortController();
9350
9506
  async function close() {
9351
9507
  if (stopping) return;
9352
9508
  stopping = true;
9509
+ layoutController.abort();
9353
9510
  try {
9354
9511
  await frontend?.dispose();
9355
9512
  await cursorDefaults?.restore();
@@ -9369,27 +9526,35 @@ async function fail(error) {
9369
9526
  });
9370
9527
  replies.end(() => process.exit(1));
9371
9528
  }
9372
- async function command(message) {
9529
+ async function command(message, layoutDeadline) {
9373
9530
  if (message.type === "command") {
9374
9531
  frontend.command(message.command);
9375
9532
  return;
9376
9533
  }
9377
- if (message.type === "interface-capture") {
9378
- await renderer.idle();
9379
- reply({ type: "interface-state", id: message.id, state: frontend.captureInterfaceState() });
9380
- return;
9381
- }
9382
- if (message.type === "interface-restore") {
9383
- frontend.restoreInterfaceState(message.state);
9384
- renderer.requestRender();
9385
- await renderer.idle();
9386
- reply({ type: "ack", id: message.id });
9534
+ if (message.type === "interface-capture" || message.type === "interface-restore") {
9535
+ if (performance.now() >= layoutDeadline) {
9536
+ reply({ type: "request-error", id: message.id, message: "OpenTUI interface request expired before layout could begin" });
9537
+ return;
9538
+ }
9539
+ let cancelRestore;
9540
+ try {
9541
+ await waitForNativeLayout(renderer, () => {
9542
+ if (message.type === "interface-restore") cancelRestore = frontend.restoreInterfaceState(message.state);
9543
+ frontend.flushPendingInterfaceLayout();
9544
+ }, { signal: layoutController.signal, deadline: layoutDeadline });
9545
+ } catch (error) {
9546
+ cancelRestore?.();
9547
+ if (!stopping) reply({ type: "request-error", id: message.id, message: error instanceof Error ? error.message : "OpenTUI interface layout failed" });
9548
+ return;
9549
+ }
9550
+ if (stopping) return;
9551
+ if (message.type === "interface-capture") reply({ type: "interface-state", id: message.id, state: frontend.captureInterfaceState() });
9552
+ else reply({ type: "ack", id: message.id });
9387
9553
  return;
9388
9554
  }
9389
9555
  if (message.type === "state") {
9390
9556
  frontend.update(message.snapshot, message.content);
9391
9557
  renderer.requestRender();
9392
- await renderer.idle();
9393
9558
  } else if (message.type === "suspend") {
9394
9559
  frontend.pauseMedia();
9395
9560
  await cursorDefaults.restore();
@@ -9397,7 +9562,6 @@ async function command(message) {
9397
9562
  } else if (message.type === "resume") {
9398
9563
  await renderer.resume();
9399
9564
  renderer.requestRender();
9400
- await renderer.idle();
9401
9565
  await cursorDefaults.restore();
9402
9566
  } else if (message.type === "stop") await close();
9403
9567
  reply({ type: "ack", id: message.id });
@@ -9423,10 +9587,10 @@ try {
9423
9587
  maxFps: 60,
9424
9588
  clearOnShutdown: false
9425
9589
  });
9426
- renderer.on(CliRenderEvents10.RENDER_ERROR, (event) => {
9590
+ renderer.on(CliRenderEvents11.RENDER_ERROR, (event) => {
9427
9591
  void fail(event.error);
9428
9592
  });
9429
- renderer.on(CliRenderEvents10.HANDLER_ERROR, (event) => {
9593
+ renderer.on(CliRenderEvents11.HANDLER_ERROR, (event) => {
9430
9594
  void fail(event.error);
9431
9595
  });
9432
9596
  renderer.on("error", (event) => {
@@ -9437,6 +9601,7 @@ try {
9437
9601
  const controls = createInterface({ input: process.stdin, crlfDelay: Infinity });
9438
9602
  let pending = Promise.resolve();
9439
9603
  controls.on("line", (line) => {
9604
+ const layoutDeadline = performance.now() + NATIVE_INTERFACE_LAYOUT_TIMEOUT_MS;
9440
9605
  let message;
9441
9606
  try {
9442
9607
  message = JSON.parse(line);
@@ -9452,7 +9617,7 @@ try {
9452
9617
  } catch (error) {
9453
9618
  void fail(error);
9454
9619
  }
9455
- } else pending = pending.then(() => command(message)).catch(fail);
9620
+ } else pending = pending.then(() => command(message, layoutDeadline)).catch(fail);
9456
9621
  });
9457
9622
  controls.on("close", () => {
9458
9623
  void pending.then(close).finally(() => process.exit(0));