@oh-my-pi/pi-tui 18.3.5 → 18.4.0

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.4.0] - 2026-09-28
6
+
7
+ ### Fixed
8
+
9
+ - Prevented interrupted wait operations from appearing in the transcript when a queued completion or message is received.
10
+
5
11
  ## [18.3.5] - 2026-09-27
6
12
 
7
13
  ### Added
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-tui",
4
- "version": "18.3.5",
4
+ "version": "18.4.0",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": {
@@ -41,14 +41,14 @@
41
41
  "gen:glyphs": "bun scripts/gen-glyphs.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@oh-my-pi/omptype": "18.3.5",
45
- "@oh-my-pi/pi-agent-core": "18.3.5",
46
- "@oh-my-pi/pi-ai": "18.3.5",
47
- "@oh-my-pi/pi-catalog": "18.3.5",
48
- "@oh-my-pi/pi-natives": "18.3.5",
49
- "@oh-my-pi/pi-utils": "18.3.5",
50
- "@oh-my-pi/pi-wire": "18.3.5",
51
- "@oh-my-pi/snapcompact": "18.3.5"
44
+ "@oh-my-pi/omptype": "18.4.0",
45
+ "@oh-my-pi/pi-agent-core": "18.4.0",
46
+ "@oh-my-pi/pi-ai": "18.4.0",
47
+ "@oh-my-pi/pi-catalog": "18.4.0",
48
+ "@oh-my-pi/pi-natives": "18.4.0",
49
+ "@oh-my-pi/pi-utils": "18.4.0",
50
+ "@oh-my-pi/pi-wire": "18.4.0",
51
+ "@oh-my-pi/snapcompact": "18.4.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "kitty-vt-wasm": "^0.2.0"
@@ -840,7 +840,9 @@ export class ToolExecutionComponent extends Container {
840
840
  }
841
841
 
842
842
  override render(width: number): readonly string[] {
843
- if (!this.#toolActivityVisible || this.#allocation === 0) return [];
843
+ if (!this.#toolActivityVisible || this.#allocation === 0 || (this.#toolName === "wait" && this.#isBenignSkip())) {
844
+ return [];
845
+ }
844
846
  let lines = super.render(width);
845
847
  if (this.#allocation < 3) {
846
848
  // A squeezed allocation degrades only blocks that genuinely overflow it.
@@ -929,6 +931,12 @@ export class ToolExecutionComponent extends Container {
929
931
  this.#renderState.executionStarted = this.#executionStarted;
930
932
  this.#renderState.spinnerFrame = this.#spinnerFrame;
931
933
 
934
+ // Interrupted waits carry only model-facing retry guidance, not user-facing output.
935
+ if (this.#toolName === "wait" && this.#isBenignSkip()) {
936
+ this.#contentBox.clear();
937
+ return;
938
+ }
939
+
932
940
  // Non-self-framing tools (custom/extension renderers and the generic
933
941
  // fallback) get a padded, state-tinted block — built-ins that draw their
934
942
  // own frame opt out below via the framed-component mark. A benign skip
@@ -9,6 +9,7 @@ import * as os from "node:os";
9
9
  import * as path from "node:path";
10
10
  import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
11
11
  import type { Ellipsis } from "@oh-my-pi/pi-natives";
12
+ import { expandWindowsLongPath, getWindowsShortPath } from "@oh-my-pi/pi-natives/path";
12
13
  import { pluralize, sanitizeText } from "@oh-my-pi/pi-utils";
13
14
  import { formatKeyHint, type KeyId } from "../app-keybindings";
14
15
  import { getKeybindings } from "../keybindings";
@@ -876,22 +877,45 @@ function defaultHomeDir(): string {
876
877
  return cachedHomeDir;
877
878
  }
878
879
 
879
- const homePatternCache = new Map<string, RegExp>();
880
- function homePatternFor(homeDir: string, windowsStyle: boolean): RegExp {
880
+ interface HomePattern {
881
+ leading: RegExp;
882
+ embedded: RegExp;
883
+ }
884
+
885
+ const homePatternCache = new Map<string, HomePattern>();
886
+ function homePatternFor(homeDir: string, windowsStyle: boolean): HomePattern {
881
887
  const key = `${windowsStyle ? 1 : 0} ${homeDir}`;
882
888
  let pattern = homePatternCache.get(key);
883
889
  if (pattern === undefined) {
884
- const escapedHome = windowsStyle
885
- ? homeDir
886
- .replaceAll("/", "\\")
887
- .split("\\")
888
- .map(part => RegExp.escape(part))
889
- .join("[\\\\/]")
890
- : RegExp.escape(homeDir);
891
- pattern = new RegExp(
892
- `[a-zA-Z][a-zA-Z0-9+.-]*://[^\\s"'<>]+|(^|[\\s"'\\x60([{=,:])(${escapedHome})(?=$|[\\\\/\\s"'\\x60)\\]},;:])`,
893
- windowsStyle ? "gi" : "g",
894
- );
890
+ // A trailing separator (`C:\Users\me\`, `/home/me/`) must still match `<home>/child`;
891
+ // roots such as `/` and `C:\` keep theirs.
892
+ const home = homeDir.replace(/(?<=[^\\/:])[\\/]+$/, "");
893
+ let escapedHome = RegExp.escape(home);
894
+ if (windowsStyle) {
895
+ // Query only home, once per cached pattern: descendants need not exist,
896
+ // and rendering must neither resolve junctions nor probe output paths.
897
+ const parts = home.replaceAll("/", "\\").split("\\");
898
+ const longHome = expandWindowsLongPath(home);
899
+ const aliases = [longHome, getWindowsShortPath(longHome)]
900
+ .map(alias => alias.replaceAll("/", "\\").split("\\"))
901
+ .filter(alias => alias.length === parts.length);
902
+ escapedHome = parts
903
+ .map((part, index) => {
904
+ // Each component may independently use its long or short spelling.
905
+ const names = [...new Set([part, ...aliases.map(alias => alias[index]!)])].map(name =>
906
+ RegExp.escape(name),
907
+ );
908
+ return names.length === 1 ? names[0]! : `(?:${names.join("|")})`;
909
+ })
910
+ .join("[\\\\/]");
911
+ }
912
+ pattern = {
913
+ leading: new RegExp(`^${escapedHome}(?=$|[\\\\/])`, windowsStyle ? "i" : ""),
914
+ embedded: new RegExp(
915
+ `[a-zA-Z][a-zA-Z0-9+.-]*://[^\\s"'<>]+|(^|[\\s"'\\x60([{=,:])(${escapedHome})(?=$|[\\\\/\\s"'\\x60)\\]},;:])`,
916
+ windowsStyle ? "gi" : "g",
917
+ ),
918
+ };
895
919
  if (homePatternCache.size >= 16) homePatternCache.clear();
896
920
  homePatternCache.set(key, pattern);
897
921
  }
@@ -904,15 +928,12 @@ export function shortenPath(filePath: unknown, homeDir?: string): string {
904
928
  return "";
905
929
  }
906
930
  const home = homeDir ?? defaultHomeDir();
931
+ if (!home) return filePath;
907
932
  const windowsStyle = /^[A-Za-z]:[\\/]/.test(home) || home.startsWith("\\\\");
908
- const hasHomePrefix = windowsStyle
909
- ? filePath.toLowerCase().startsWith(home.toLowerCase())
910
- : filePath.startsWith(home);
911
- if (home && hasHomePrefix) {
912
- const suffix = filePath.slice(home.length);
913
- if (suffix === "" || suffix.startsWith(path.posix.sep) || suffix.startsWith(path.win32.sep)) {
914
- return `~${suffix.replaceAll(path.win32.sep, path.posix.sep)}`;
915
- }
933
+ const match = homePatternFor(home, windowsStyle).leading.exec(filePath);
934
+ if (match) {
935
+ const suffix = filePath.slice(match[0].length);
936
+ return `~${suffix.replaceAll(path.win32.sep, path.posix.sep)}`;
916
937
  }
917
938
  return filePath;
918
939
  }
@@ -924,7 +945,7 @@ export function shortenEmbeddedPaths(text: string, homeDir?: string, preserveSep
924
945
  const windowsStyle = /^[A-Za-z]:[\\/]/.test(resolvedHome) || resolvedHome.startsWith("\\\\");
925
946
  const homePattern = homePatternFor(resolvedHome, windowsStyle);
926
947
  const textWithShortenedHome = text.replace(
927
- homePattern,
948
+ homePattern.embedded,
928
949
  (match, boundary: string | undefined, candidate: string | undefined) =>
929
950
  candidate === undefined ? match : `${boundary}~`,
930
951
  );