Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/aura/FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ and after every upstream merge.
| `packages/coding-agent/src/cli/commands/init-xdg.ts` | removed the local `APP_NAME = "omp"` shadow; XDG dirs and hint text use the branded constant from pi-utils |
| `packages/coding-agent/src/cli/profile-alias.ts` | alias command, fish conf.d filename, `--wraps`, and rc-file block markers all derive from `APP_NAME` (imported from the `/dirs` subpath to avoid eager env load) |
| Display name "Oh My Pi" → "Aura" | `packages/tui/src/desktop-notify.ts`, `tui/src/terminal-capabilities.ts` (CMUX + OSC-99 titles), `modes/controllers/event-controller.ts`, `debug/index.ts`, `tools/ask.ts`, `dap/session.ts` (clientName), `modes/acp/acp-agent.ts`, `commands/acp.ts`, `prompts/system/system-prompt.md` ("Aura coding harness"), `live/prompts/live-instructions.md` ("Aura Live"), `session/agent-session.ts` power reason, `packages/ai/src/registry/oauth/oauth.html`, and Rust `crates/pi-natives/src/{power,crash_handler,lib}.rs` (crash logs land under `.aura`) |
| Windows Terminal / WSL / tmux rendering (`packages/tui/src/terminal-capabilities.ts`, `packages/coding-agent/src/modes/{interactive-mode.ts,theme/theme.ts}`, `packages/coding-agent/src/config/settings-schema.ts`) | aura hardening for the common WSL setup: `detectTerminalId` treats `WT_SESSION` as truecolor (Windows Terminal never emits `COLORTERM`; WSLENV forwards only `WT_SESSION`), `tui.scrollbackRebuild` defaults on for ConPTY hosts, and a new `TERMINAL.italic` capability (`shouldEnableItalicByDefault` / `italicUserOverride`, `PI_FORCE_ITALIC`/`PI_NO_ITALIC`) suppresses SGR 3 under screen-family terminfo (no `sitm`) so tmux/screen no longer paint italic as reverse-video blocks. `theme.italic` and the HUD-note marker in `interactive-mode.ts` gate on it |
| Release pipeline | `scripts/ci-release-build-binaries.ts` outfiles templated `${APP_NAME}-<os>-<arch>`; `.github/workflows/ci.yml` matrix/codesign paths and `release_brew` (elide-dev/homebrew-tap, `Formula/aura.rb`); `scripts/ci-update-brew-formula.ts` (repo `elide-dev/BREAKDANCE`, class `Aura`, installs `aura`); `scripts/install.sh` + `scripts/install.ps1` (fork repo, `aura-*` assets, installs `aura`); `packages/metaharness/src/launch-args.ts` prebuilt names. Asset basenames must stay equal to update-cli's `getBinaryName()` output |
| `AGENTS.md` | appended the `## Aura fork conventions` section (points contributors at this file, states the runtime naming rule, locates specs/plans) |
| `biome.json` | excludes checked-in `capnp-es` runtime protocol bindings from source formatting/lint; generated output is verified byte-for-byte by `scripts/sync-embedded-runtime-protocol.ts --check` instead |
Expand Down
2 changes: 2 additions & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
- Fixed Kotlin runtime execution missing the bundled standard library, and made Auto runtime selection try Java/Kotlin in-process before falling back when the embedded library does not support them yet.
- Fixed Python path execution through process and embedded runtime adapters omitting `__file__`, script argv identity, and sibling import roots.
- Fixed isolated and containerized Aura launches ignoring the requested runtime adapter by adding the validated `AURA_RUNTIME_ADAPTER` process override.
- Fixed duplicated/leftover scrollback rows under Windows Terminal and WSL by defaulting `tui.scrollbackRebuild` on for ConPTY hosts (native Windows and WSL); a scrolled-off live preview otherwise remained in history with the final block appended below. An explicit setting still overrides the per-host default.
- Fixed the `Tip:` line and other italicized UI (thinking traces, blockquotes, markdown emphasis) rendering as reverse-video highlight blocks under tmux/screen inside Windows Terminal. `theme.italic` now emits plain text when the terminal lacks `sitm` (screen-family terminfo, where the multiplexer substitutes standout for SGR 3), keeping the theme colors readable instead of painting solid inverse bars.

### Removed

Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/src/config/settings-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1077,7 +1077,7 @@ export const SETTINGS_SCHEMA = {
group: "Display",
label: "Rewrite Scrollback",
description:
"Erase and replay terminal scrollback when a block's final form replaces its live preview. When off (default), stale preview copies remain in history and the final content is appended below.",
"Erase and replay terminal scrollback when a block's final form replaces its live preview. Defaults on for Windows/WSL (ConPTY) terminals, where a stale preview copy otherwise remains in history with the final content appended below; off elsewhere. Set explicitly to override the per-host default.",
},
},

Expand Down
11 changes: 9 additions & 2 deletions packages/coding-agent/src/modes/interactive-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
visibleWidth,
} from "@oh-my-pi/pi-tui";
import type { TerminalAppearanceRequestToken } from "@oh-my-pi/pi-tui/terminal";
import { isConPTYHosted } from "@oh-my-pi/pi-tui/terminal";
import { isInsideTerminalMultiplexer } from "@oh-my-pi/pi-tui/terminal-capabilities";
import {
$env,
Expand Down Expand Up @@ -319,7 +320,7 @@ function formatHudNoteMarker(count: number): string {
.split("")
.map(d => HUD_NOTE_SUP_DIGITS[d] ?? d)
.join("");
return theme.fg("dim", chalk.italic(` \u207a${sub}`));
return theme.fg("dim", theme.italic(` \u207a${sub}`));
}

type GoalSubcommand = "set" | "show" | "pause" | "resume" | "drop" | "budget";
Expand Down Expand Up @@ -715,7 +716,13 @@ export class InteractiveMode implements InteractiveModeContext {
setMarkdownMermaidRendering(settings.get("tui.renderMermaid"));
this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
this.ui.setMaxInlineImages(settings.get("tui.maxInlineImages"));
this.ui.setScrollbackRebuild(settings.get("tui.scrollbackRebuild"));
// Scrollback rewrite defaults on for ConPTY hosts (native Windows and WSL),
// where a scrolled-off live preview otherwise leaves a duplicate above its
// committed final form (Windows Terminal viewport-follow). Elsewhere it stays
// off. An explicit user setting always wins.
this.ui.setScrollbackRebuild(
settings.isConfigured("tui.scrollbackRebuild") ? settings.get("tui.scrollbackRebuild") : isConPTYHosted(),
);
// OSC 66 text-sizing is Kitty-only; resolve the setting against the terminal's
// capability (`TERMINAL.textSizing` defaults on for Kitty) so it stays off
// unless the user opts in, and never emits raw escapes on other terminals.
Expand Down
5 changes: 5 additions & 0 deletions packages/coding-agent/src/modes/theme/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
Terminal,
TerminalAppearance,
} from "@oh-my-pi/pi-tui";
import { TERMINAL } from "@oh-my-pi/pi-tui";
import { adjustHsv, colorLuma, getCustomThemesDir, isEnoent, logger, relativeLuminance } from "@oh-my-pi/pi-utils";
import { type } from "arktype";
import chalk from "chalk";
Expand Down Expand Up @@ -1631,6 +1632,10 @@ export class Theme {
}

italic(text: string): string {
// Screen-family terminfo (GNU screen, tmux's default screen-256color)
// lacks `sitm`, so the multiplexer substitutes standout/reverse for
// SGR 3 and paints a solid inverse block. Emit plain text there instead.
if (!TERMINAL.italic) return text;
return chalk.italic(text);
}

Expand Down
5 changes: 5 additions & 0 deletions packages/tui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## [Unreleased]

### Fixed

- Fixed dull, inconsistent colors under Windows Terminal and WSL by detecting `WT_SESSION` as a truecolor terminal in `detectTerminalId`. Windows Terminal never sets `COLORTERM` (and WSLENV forwards only `WT_SESSION`), so `TERMINAL.trueColor` was `false` while the rest of the UI assumed truecolor, quantizing markdown color swatches, LaTeX colors, and the welcome brand gradient to 256-color.
- Fixed italic text (the welcome tip, thinking traces, blockquotes, and markdown emphasis) rendering as a solid reverse-video block under tmux/screen inside Windows Terminal. Screen-family terminfo (`screen-256color`, tmux's historical `default-terminal`) lacks `sitm`, so the multiplexer substitutes the standout attribute for SGR 3. `TERMINAL.italic` (resolved via `shouldEnableItalicByDefault`) is now off for screen-family / GNU screen sessions so italic degrades to plain text; `PI_FORCE_ITALIC=1` / `PI_NO_ITALIC=1` override.

## [17.2.2] - 2026-07-31

### Added
Expand Down
72 changes: 72 additions & 0 deletions packages/tui/src/terminal-capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ export class TerminalInfo {
* (macOS narrow, otherwise UAX#11).
*/
public readonly hangulJamoWidth: HangulCompatibilityJamoWidth = "platform",
/**
* Emits SGR 3 (italic) as slanted text rather than a substituted
* attribute. Runtime-resolved: screen-family terminfo lacks `sitm`, so
* tmux/screen paint standout (reverse video) instead — see
* {@link shouldEnableItalicByDefault}.
*/
public readonly italic: boolean = true,
) {}

/**
Expand Down Expand Up @@ -429,6 +436,53 @@ export function shouldEnableHyperlinksByDefault(
return true;
}

/**
* Resolve an explicit user override for SGR 3 (italic). Returns `false` for an
* opt-out, `true` for a force-on, or `null` when the user has no preference.
* Opt-out beats force-on, mirroring {@link hyperlinksUserOverride}.
*/
export function italicUserOverride(env: NodeJS.ProcessEnv = Bun.env): boolean | null {
if (env.PI_NO_ITALIC === "1") return false;
if (env.PI_FORCE_ITALIC === "1") return true;
return null;
}

/**
* Whether SGR 3 (italic) should be emitted at all.
*
* Italic reaches the screen only when the active terminfo carries `sitm`.
* GNU `screen` and tmux's historical `screen-256color` default-terminal do
* not: tmux/screen then substitute the standout attribute, so every italic run
* paints as a solid reverse-video block (a purple/gray highlight on the welcome
* tip, thinking traces, and markdown emphasis) instead of slanted text. Under
* WSL + Windows Terminal this is the common tmux default. Degrade to plain text
* in that case so the color still reads.
*
* Policy (highest precedence first):
* 1. Explicit override (`PI_NO_ITALIC=1` off, `PI_FORCE_ITALIC=1` on).
* 2. Static terminal capability (`TerminalInfo.italic`).
* 3. GNU screen marker (`STY`) — screen-family terminfo lacks `sitm`.
* 4. `TERM=screen*` (tmux's historical default or bare screen) — lacks `sitm`.
* 5. Otherwise on: `tmux-256color`, `xterm*`, and modern direct terminals
* all advertise `sitm`. A user who forces italic on a sitm-less TERM can
* use `PI_FORCE_ITALIC=1`.
*/
export function shouldEnableItalicByDefault(
env: NodeJS.ProcessEnv = Bun.env,
terminalId: TerminalId = TERMINAL_ID,
): boolean {
const override = italicUserOverride(env);
if (override !== null) return override;

if (!getTerminalInfo(terminalId).italic) return false;

if (env.STY) return false;
const term = env.TERM?.toLowerCase() ?? "";
if (term.startsWith("screen")) return false;

return true;
}

function getFallbackImageProtocol(terminalId: TerminalId): ImageProtocol | null {
if (!process.stdout.isTTY) return null;
if (terminalId === "vscode" || terminalId === "alacritty") return null;
Expand Down Expand Up @@ -501,6 +555,7 @@ export function detectTerminalId(env: NodeJS.ProcessEnv = Bun.env): TerminalId {
TERM_PROGRAM,
TERM,
COLORTERM,
WT_SESSION,
} = env;

if (KITTY_WINDOW_ID) return "kitty";
Expand All @@ -525,6 +580,12 @@ export function detectTerminalId(env: NodeJS.ProcessEnv = Bun.env): TerminalId {
if (COLORTERM) {
if (caseEq(COLORTERM, "truecolor") || caseEq(COLORTERM, "24bit")) return "trueColor";
}
// Windows Terminal advertises 24-bit color but sets neither COLORTERM (which
// it never emits) nor a TERM_PROGRAM marker, and under WSL only WT_SESSION is
// forwarded through WSLENV. Treat its presence as truecolor so TERMINAL.trueColor
// agrees with the coding-agent color-mode and synchronized-output detectors,
// which already key off WT_SESSION.
if (WT_SESSION) return "trueColor";
return "base";
}

Expand All @@ -542,6 +603,7 @@ export interface RuntimeTerminal extends TerminalInfo {
deccara: boolean;
supportsScreenToScrollback: boolean;
textSizing: boolean;
italic: boolean;
}

export const TERMINAL: RuntimeTerminal = (() => {
Expand Down Expand Up @@ -570,6 +632,11 @@ export const TERMINAL: RuntimeTerminal = (() => {
// ignores DECCARA) exercises the padded-string fallback. Integration tests opt
// in explicitly through setTerminalDeccara.
resolved.deccara = detectRectangularSgrSupport(resolved.id, Bun.env) && !isBunTestRuntime();
// Italic (SGR 3). Emitted directly by modern terminals; suppressed under
// screen-family terminfo (GNU screen, tmux's historical screen-256color
// default) where tmux/screen substitute standout/reverse for the missing
// `sitm` and paint a solid inverse block instead of slanted text.
resolved.italic = shouldEnableItalicByDefault(Bun.env, resolved.id);
return resolved;
})();

Expand Down Expand Up @@ -609,6 +676,11 @@ export function setTerminalTextSizing(enabled: boolean): void {
TERMINAL.textSizing = enabled;
}

/** Override italic (SGR 3) emission at runtime; tests flip it directly. */
export function setTerminalItalic(enabled: boolean): void {
TERMINAL.italic = enabled;
}

export function getTerminalInfo(
terminalId: TerminalId,
platform: NodeJS.Platform = process.platform,
Expand Down
54 changes: 54 additions & 0 deletions packages/tui/test/terminal-capabilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
getTerminalInfo,
hyperlinksUserOverride,
ImageProtocol,
italicUserOverride,
NotifyProtocol,
resolveWarpImageProtocol,
shouldEnableHyperlinksByDefault,
shouldEnableItalicByDefault,
shouldEnableSynchronizedOutputByDefault,
synchronizedOutputUserOverride,
} from "@oh-my-pi/pi-tui/terminal-capabilities";
Expand All @@ -21,6 +23,12 @@ describe("detectTerminalId", () => {

expect(detectTerminalId(env)).toBe("trueColor");
});

it("treats Windows Terminal / WSL as truecolor via WT_SESSION when COLORTERM is absent", () => {
// Windows Terminal never sets COLORTERM, and WSLENV forwards only WT_SESSION,
// so the true-color fallback must key off WT_SESSION rather than COLORTERM.
expect(detectTerminalId({ WT_SESSION: "abc", TERM: "xterm-256color" })).toBe("trueColor");
});
});

describe("synchronizedOutputUserOverride", () => {
Expand Down Expand Up @@ -373,3 +381,49 @@ describe("shouldEnableHyperlinksByDefault", () => {
expect(shouldEnableHyperlinksByDefault({ PI_FORCE_HYPERLINKS: "1", STY: "1.pts-0" }, "kitty")).toBe(true);
});
});

describe("italicUserOverride", () => {
it("returns null when neither override is set", () => {
expect(italicUserOverride({})).toBeNull();
expect(italicUserOverride({ TERM: "screen-256color" })).toBeNull();
});

it("returns true for the force-on flag and false for the opt-out", () => {
expect(italicUserOverride({ PI_FORCE_ITALIC: "1" })).toBe(true);
expect(italicUserOverride({ PI_NO_ITALIC: "1" })).toBe(false);
});

it("resolves opt-out ahead of force-on when both are set", () => {
expect(italicUserOverride({ PI_NO_ITALIC: "1", PI_FORCE_ITALIC: "1" })).toBe(false);
});
});

describe("shouldEnableItalicByDefault", () => {
it("enables italic on modern direct terminals and sitm-carrying TERMs", () => {
expect(shouldEnableItalicByDefault({ TERM: "xterm-256color" }, "kitty")).toBe(true);
expect(shouldEnableItalicByDefault({ TERM: "tmux-256color", TMUX: "1" }, "trueColor")).toBe(true);
expect(shouldEnableItalicByDefault({}, "base")).toBe(true);
});

it("disables italic under screen-family terminfo (no sitm -> tmux/screen paint reverse video)", () => {
// tmux's historical default-terminal is screen-256color, so tmux inside
// Windows Terminal substitutes standout for SGR 3 and paints a solid
// inverse block instead of slanted text.
expect(shouldEnableItalicByDefault({ TERM: "screen-256color", TMUX: "1" }, "trueColor")).toBe(false);
expect(shouldEnableItalicByDefault({ TERM: "screen" }, "base")).toBe(false);
});

it("disables italic when GNU screen is in the path", () => {
expect(shouldEnableItalicByDefault({ STY: "1234.pts-0.host", TERM: "xterm-256color" }, "kitty")).toBe(false);
});

it("lets PI_FORCE_ITALIC override a sitm-less TERM", () => {
expect(
shouldEnableItalicByDefault({ PI_FORCE_ITALIC: "1", TERM: "screen-256color", TMUX: "1" }, "trueColor"),
).toBe(true);
});

it("lets PI_NO_ITALIC force italic off on a capable terminal", () => {
expect(shouldEnableItalicByDefault({ PI_NO_ITALIC: "1", TERM: "xterm-256color" }, "kitty")).toBe(false);
});
});
Loading