diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 2ac2e6373..3a441ca01 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -174,6 +174,7 @@ - Added env-driven OpenTelemetry trace export. When `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) is set, `omp` registers a global OTLP/proto trace exporter and switches on the agent loop's telemetry, so the `invoke_agent` / `chat` / `execute_tool` spans actually reach a collector instead of a no-op tracer. Honors the standard `OTEL_*` env contract (endpoint, headers, `OTEL_SERVICE_NAME`, `OTEL_SDK_DISABLED` and `OTEL_TRACES_EXPORTER=none` parsed case-insensitively) and the `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` capture toggle; it is a no-op when no endpoint is configured. Only the `http/protobuf` transport is supported — a `grpc` or `http/json` `OTEL_EXPORTER_OTLP*_PROTOCOL` declines rather than misrouting spans. This makes the existing telemetry usable from headless hosts that run `omp` as a spawned child process, where an in-process `TracerProvider` registered by the parent can't reach the child. Uses the `@opentelemetry/exporter-trace-otlp-proto` 2.x line, which exports cleanly under Bun. ## Fixed +- Fixed paste and image placeholders crashing when the editor renders before theme initialization. - Fixed the status line session name (and the editor border / status-line gap fill) being nearly illegible on light themes. - Added `IndexedSessionStorage` and `SessionStorageBackend` exports to support shared metadata-indexed session backends - Added the `tui.maxInlineImages` setting (default `8`) capping how many inline images render as live terminal graphics. Once a new image pushes the count past the cap, the oldest images are hidden via a full redraw — replaced by their `[Image: …]` text placeholder and purged from the terminal's graphics store — so long sessions with many screenshots/diagrams stop piling up images (and, on Kitty, stop leaving scrollback ghosts). Set to `0` to keep every image inline. diff --git a/packages/coding-agent/src/modes/components/custom-editor.test.ts b/packages/coding-agent/src/modes/components/custom-editor.test.ts index 979d29dc0..71d50cfa0 100644 --- a/packages/coding-agent/src/modes/components/custom-editor.test.ts +++ b/packages/coding-agent/src/modes/components/custom-editor.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeAll, describe, expect, it, vi } from "bun:test"; +import { $ } from "bun"; import { getEditorTheme, initTheme } from "../theme/theme"; import { CustomEditor, SPACE_HOLD_RELEASE_MS, SPACE_HOLD_THRESHOLD } from "./custom-editor"; @@ -15,6 +16,32 @@ function holdSpace(editor: CustomEditor, count: number): void { for (let i = 0; i < count; i++) editor.handleInput(" "); } +async function decorateInFreshProcess(text: string): Promise { + const customEditorUrl = new URL("./custom-editor.ts", import.meta.url).href; + const script = ` +import { CustomEditor } from ${JSON.stringify(customEditorUrl)}; +const editor = new CustomEditor({}); +process.stdout.write(editor.decorateText(${JSON.stringify(text)})); +`; + const child = await $`bun -e ${script}`.quiet().nothrow(); + const stdout = child.stdout.toString(); + const stderr = child.stderr.toString(); + if (child.exitCode !== 0) throw new Error(stderr || stdout || `decorate subprocess exited with ${child.exitCode}`); + return stdout; +} + +describe("CustomEditor placeholder decoration", () => { + it("renders paste placeholders before theme initialization", async () => { + const output = await decorateInFreshProcess("[Paste #1, +30 lines]"); + expect(output).toBe("[Paste #1, +30 lines]"); + }); + + it("renders image placeholders before theme initialization", async () => { + const output = await decorateInFreshProcess("[Image #1]"); + expect(output).toBe("[Image #1]"); + }); +}); + describe("CustomEditor space-hold push-to-talk", () => { beforeAll(async () => { await initTheme(); diff --git a/packages/coding-agent/src/modes/components/custom-editor.ts b/packages/coding-agent/src/modes/components/custom-editor.ts index 501b2a3f6..df111f0f1 100644 --- a/packages/coding-agent/src/modes/components/custom-editor.ts +++ b/packages/coding-agent/src/modes/components/custom-editor.ts @@ -3,7 +3,7 @@ import type { AppKeybinding } from "../../config/keybindings"; import { isSettingsInitialized, settings } from "../../config/settings"; import { imageReferenceHyperlink, PLACEHOLDER_REGEX, renderPlaceholders } from "../image-references"; import { hasMagicKeyword, highlightMagicKeywords } from "../magic-keywords"; -import { theme } from "../theme/theme"; +import { fgOrPlain } from "../theme/theme"; type ConfigurableEditorAction = Extract< AppKeybinding, @@ -178,9 +178,9 @@ export class CustomEditor extends Editor { renderReference: (value, kind, index) => kind === "image" ? imageReferenceHyperlink(value, index, this.imageLinks, label => - theme.fg("accent", `\x1b[1m\x1b[4m${label}\x1b[24m\x1b[22m`), + fgOrPlain("accent", label, `\x1b[1m\x1b[4m${label}\x1b[24m\x1b[22m`), ) - : theme.fg("accent", `\x1b[1m${value}\x1b[22m`), + : fgOrPlain("accent", value, `\x1b[1m${value}\x1b[22m`), }); }; diff --git a/packages/coding-agent/src/modes/theme/theme.ts b/packages/coding-agent/src/modes/theme/theme.ts index 130f09b03..7ab65b264 100644 --- a/packages/coding-agent/src/modes/theme/theme.ts +++ b/packages/coding-agent/src/modes/theme/theme.ts @@ -2098,6 +2098,11 @@ var currentThemeName: string | undefined; export function getCurrentThemeName(): string | undefined { return currentThemeName; } + +/** Returns unstyled `text` before `initTheme()` assigns the global theme; use only for early-render paths. */ +export function fgOrPlain(color: ThemeColor, text: string, styledText: string = text): string { + return typeof theme === "undefined" ? text : theme.fg(color, styledText); +} var currentSymbolPresetOverride: SymbolPreset | undefined; var currentColorBlindMode: boolean = false; var themeWatcher: fs.FSWatcher | undefined;