From 0ffec67ca32c7d3c67e66cf962c95b4eb72e8161 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 11:25:03 -0500 Subject: [PATCH] refactor: remove unused V2 code (#39699) Co-authored-by: Aiden Cline --- packages/core/src/plugin/agent.ts | 2 - packages/tui/src/component/bg-pulse-render.ts | 436 ------------------ packages/tui/src/component/bg-pulse.tsx | 102 ---- .../tui/src/component/dialog-retry-action.tsx | 164 ------- .../dialog-session-delete-failed.tsx | 113 ----- packages/tui/src/component/dialog-tag.tsx | 52 --- packages/tui/src/component/prompt/cwd.ts | 0 packages/tui/src/context/directory.ts | 13 - .../src/routes/session/dialog-subagent.tsx | 26 -- packages/tui/src/routes/session/footer.tsx | 91 ---- .../src/routes/session/subagent-footer.tsx | 115 ----- 11 files changed, 1114 deletions(-) delete mode 100644 packages/tui/src/component/bg-pulse-render.ts delete mode 100644 packages/tui/src/component/bg-pulse.tsx delete mode 100644 packages/tui/src/component/dialog-retry-action.tsx delete mode 100644 packages/tui/src/component/dialog-session-delete-failed.tsx delete mode 100644 packages/tui/src/component/dialog-tag.tsx delete mode 100644 packages/tui/src/component/prompt/cwd.ts delete mode 100644 packages/tui/src/context/directory.ts delete mode 100644 packages/tui/src/routes/session/dialog-subagent.tsx delete mode 100644 packages/tui/src/routes/session/footer.tsx delete mode 100644 packages/tui/src/routes/session/subagent-footer.tsx diff --git a/packages/core/src/plugin/agent.ts b/packages/core/src/plugin/agent.ts index ac1adb71f12..c94e735faa2 100644 --- a/packages/core/src/plugin/agent.ts +++ b/packages/core/src/plugin/agent.ts @@ -11,8 +11,6 @@ import { Permission } from "../permission" // Combined output files written by the Shell service, e.g. `/shell//.out`. // Whitelisted so agents can read a command's full captured output without an external-directory prompt. const SHELL_OUTPUT_GLOB = path.join(Global.Path.data, "shell", "*", "*") -const BUILD_SYSTEM = - "You are an AI coding agent. Help the user accomplish software engineering tasks by inspecting the workspace, making targeted changes, and using tools according to the configured permissions." const PROMPT_EXPLORE = `You are a file search specialist. You excel at thoroughly navigating and exploring codebases. diff --git a/packages/tui/src/component/bg-pulse-render.ts b/packages/tui/src/component/bg-pulse-render.ts deleted file mode 100644 index 122017e677d..00000000000 --- a/packages/tui/src/component/bg-pulse-render.ts +++ /dev/null @@ -1,436 +0,0 @@ -import { OptimizedBuffer, RGBA, TextAttributes } from "@opentui/core" -import { go } from "../logo" - -const PERIOD = 4600 -const RINGS = 3 -const WIDTH = 3.8 -const TAIL = 9.5 -const AMP = 0.55 -const TAIL_AMP = 0.16 -const BREATH_AMP = 0.05 -const BREATH_SPEED = 0.0008 -// Offset so the bg ring emits from the estimated GO center when the logo shimmer peaks. -const PHASE_OFFSET = 0.29 -const LOGO_GAP = 1 -const LOGO_TOP_BIAS = -1 -const LOGO_LEFT_WIDTH = go.left[0]?.length ?? 0 -const LOGO_LINES = go.left.map((line, index) => line + " ".repeat(LOGO_GAP) + go.right[index]) -const LOGO_WIDTH = LOGO_LINES[0]?.length ?? 0 -const LOGO_HEIGHT = LOGO_LINES.length -const SPACE = " ".codePointAt(0)! -const TOP_HALF = "▀".codePointAt(0)! -const FULL_BLOCK = "█".codePointAt(0)! -const RING_SCALE = 1 / RINGS -const TAIL_SCALE = 1 / TAIL -const LOGO_REACH = Math.hypot(LOGO_WIDTH, LOGO_HEIGHT * 2) + 3 - -const enum LogoCellKind { - Background, - Top, - ShadowTop, - Solid, - Char, -} - -type LogoTemplateCell = { - x: number - y: number - kind: LogoCellKind - charCode: number - attributes: number - topDist: number - bottomDist: number -} - -const LOGO_TEMPLATE: LogoTemplateCell[] = LOGO_LINES.flatMap((line, y) => - Array.from(line) - .map((char, x) => { - if (char === " ") return - const kind = - char === "_" - ? LogoCellKind.Background - : char === "^" - ? LogoCellKind.Top - : char === "~" - ? LogoCellKind.ShadowTop - : char === "█" - ? LogoCellKind.Solid - : LogoCellKind.Char - return { - x, - y, - kind, - charCode: char.codePointAt(0) ?? SPACE, - attributes: x > LOGO_LEFT_WIDTH ? TextAttributes.BOLD : 0, - topDist: Math.hypot(x + 0.5 - LOGO_WIDTH / 2, y * 2 - LOGO_HEIGHT), - bottomDist: Math.hypot(x + 0.5 - LOGO_WIDTH / 2, y * 2 + 1 - LOGO_HEIGHT), - } - }) - .filter((cell): cell is LogoTemplateCell => !!cell), -) - -export type Rgb = [number, number, number] - -export type GoUpsellArtRenderOptions = { - deltaTime?: number - rgb?: boolean - cache?: boolean -} - -const CACHE_FRAME_COUNT = Math.round(PERIOD / (1000 / 30)) -const CACHE_FRAMES_PER_RENDER = 1 - -export function toRgb(color: RGBA): Rgb { - const [r, g, b] = color.toInts() - return [r, g, b] -} - -function clamp(n: number) { - return Math.max(0, Math.min(1, n)) -} - -function writeRgb(buffer: Uint16Array, offset: number, r: number, g: number, b: number, a = 255) { - buffer[offset] = r - buffer[offset + 1] = g - buffer[offset + 2] = b - buffer[offset + 3] = a -} - -function mixChannel(base: number, overlay: number, alpha: number) { - return Math.round(base + (overlay - base) * clamp(alpha)) -} - -function writeLogoTint( - buffer: Uint16Array, - offset: number, - base: Rgb, - primary: Rgb, - primaryMix: number, - peakMix: number, -) { - const p = clamp(primaryMix) - const q = clamp(peakMix) - const r = mixChannel(mixChannel(base[0], primary[0], p), 255, q) - const g = mixChannel(mixChannel(base[1], primary[1], p), 255, q) - const b = mixChannel(mixChannel(base[2], primary[2], p), 255, q) - writeRgb(buffer, offset, r, g, b) -} - -function sameRgb(a: Rgb, b: Rgb) { - return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] -} - -export class GoUpsellArtPainter { - private panelRgb: Rgb = [0, 0, 0] - private primaryRgb: Rgb = [255, 255, 255] - private logoBaseRgb: Rgb = [180, 180, 180] - private elapsed = 0 - private distances = new Float32Array(0) - private edgeFalloff = new Float32Array(0) - private geometryWidth = 0 - private geometryHeight = 0 - private reach = 1 - private logoX = 0 - private logoY = 0 - private logoIndexes = new Int32Array(0) - private logoRgb: boolean | undefined - private pulsePeak = 0 - private pulsePrimary = 0 - private cacheDirty = true - private frameCache: Array<{ fg: Uint16Array; bg: Uint16Array }> = [] - private cacheBuildIndex = 0 - - setBackgroundPanel(value: RGBA | Rgb | undefined) { - if (!value) return false - const next = value instanceof RGBA ? toRgb(value) : value - if (sameRgb(this.panelRgb, next)) return false - this.panelRgb = next - this.invalidateCache() - return true - } - - setLogoBase(value: RGBA | Rgb | undefined) { - if (!value) return false - const next = value instanceof RGBA ? toRgb(value) : value - if (sameRgb(this.logoBaseRgb, next)) return false - this.logoBaseRgb = next - this.invalidateCache() - return true - } - - setPrimary(value: RGBA | Rgb | undefined) { - if (!value) return false - const next = value instanceof RGBA ? toRgb(value) : value - if (sameRgb(this.primaryRgb, next)) return false - this.primaryRgb = next - this.invalidateCache() - return true - } - - render(frameBuffer: OptimizedBuffer, options: GoUpsellArtRenderOptions = {}) { - const rgb = options.rgb === true - this.elapsed = (this.elapsed + (options.deltaTime ?? 0)) % PERIOD - this.rebuildGeometry(frameBuffer, rgb) - if (options.cache !== false) { - this.drawCached(frameBuffer, rgb) - return - } - this.drawBackground(frameBuffer, this.elapsed) - this.drawLogo(frameBuffer, this.elapsed, rgb) - } - - private invalidateCache() { - this.cacheDirty = true - this.cacheBuildIndex = 0 - this.frameCache = [] - } - - private rebuildGeometry(frameBuffer: OptimizedBuffer, rgb: boolean) { - const width = frameBuffer.width - const height = frameBuffer.height - const geometryChanged = width !== this.geometryWidth || height !== this.geometryHeight - const logoTemplateChanged = this.logoRgb !== rgb - if (!geometryChanged && !logoTemplateChanged) return - - if (geometryChanged) { - this.geometryWidth = width - this.geometryHeight = height - this.logoX = Math.max(0, Math.floor((width - LOGO_WIDTH) / 2)) - this.logoY = Math.max( - 0, - Math.min(Math.max(0, height - LOGO_HEIGHT), Math.round((height - LOGO_HEIGHT) / 2) + LOGO_TOP_BIAS), - ) - - const centerX = this.logoX + LOGO_WIDTH / 2 - const centerY = this.logoY + LOGO_HEIGHT / 2 - this.reach = Math.hypot(Math.max(centerX, width - centerX), Math.max(centerY, height - centerY) * 2) + TAIL - this.distances = new Float32Array(width * height) - this.edgeFalloff = new Float32Array(width * height) - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - const index = y * width + x - const dist = Math.hypot(x + 0.5 - centerX, (y + 0.5 - centerY) * 2) - this.distances[index] = dist - this.edgeFalloff[index] = Math.max(0, 1 - (dist / (this.reach * 0.85)) ** 2) - } - } - } - - this.logoRgb = rgb - this.invalidateCache() - this.rebuildCellTemplate(frameBuffer, rgb) - } - - private drawCached(frameBuffer: OptimizedBuffer, rgb: boolean) { - if (this.cacheDirty) this.startFrameCache(frameBuffer, rgb) - if (this.cacheBuildIndex < CACHE_FRAME_COUNT) { - this.buildFrameCache(frameBuffer, rgb) - this.drawBackground(frameBuffer, this.elapsed) - this.drawLogo(frameBuffer, this.elapsed, rgb) - return - } - - const frame = this.frameCache[Math.floor((this.elapsed / PERIOD) * CACHE_FRAME_COUNT) % CACHE_FRAME_COUNT] - if (frame) { - frameBuffer.buffers.fg.set(frame.fg) - frameBuffer.buffers.bg.set(frame.bg) - } - } - - private startFrameCache(frameBuffer: OptimizedBuffer, rgb: boolean) { - this.frameCache = [] - this.cacheBuildIndex = 0 - this.rebuildCellTemplate(frameBuffer, rgb) - this.cacheDirty = false - } - - private buildFrameCache(frameBuffer: OptimizedBuffer, rgb: boolean) { - const end = Math.min(CACHE_FRAME_COUNT, this.cacheBuildIndex + CACHE_FRAMES_PER_RENDER) - for (; this.cacheBuildIndex < end; this.cacheBuildIndex++) { - const t = (this.cacheBuildIndex / CACHE_FRAME_COUNT) * PERIOD - this.drawBackground(frameBuffer, t) - this.drawLogo(frameBuffer, t, rgb) - this.frameCache.push({ - fg: new Uint16Array(frameBuffer.buffers.fg), - bg: new Uint16Array(frameBuffer.buffers.bg), - }) - } - } - - private rebuildCellTemplate(frameBuffer: OptimizedBuffer, rgb: boolean) { - const buffers = frameBuffer.buffers - buffers.char.fill(SPACE) - buffers.attributes.fill(0) - - if (this.geometryWidth < LOGO_WIDTH || this.geometryHeight < LOGO_HEIGHT) { - this.logoIndexes = new Int32Array(0) - return - } - - this.logoIndexes = new Int32Array(LOGO_TEMPLATE.length) - for (let i = 0; i < LOGO_TEMPLATE.length; i++) { - const cell = LOGO_TEMPLATE[i]! - const index = (this.logoY + cell.y) * this.geometryWidth + this.logoX + cell.x - this.logoIndexes[i] = index - buffers.attributes[index] = cell.attributes - buffers.char[index] = - cell.kind === LogoCellKind.Background - ? SPACE - : cell.kind === LogoCellKind.Top || cell.kind === LogoCellKind.ShadowTop - ? TOP_HALF - : cell.kind === LogoCellKind.Solid - ? rgb - ? TOP_HALF - : FULL_BLOCK - : cell.charCode - } - } - - private drawBackground(frameBuffer: OptimizedBuffer, t: number) { - const buffers = frameBuffer.buffers - const fg = buffers.fg - const bg = buffers.bg - const distances = this.distances - const edgeFalloff = this.edgeFalloff - const baseR = this.panelRgb[0] - const baseG = this.panelRgb[1] - const baseB = this.panelRgb[2] - const deltaR = this.primaryRgb[0] - baseR - const deltaG = this.primaryRgb[1] - baseG - const deltaB = this.primaryRgb[2] - baseB - const breath = (0.5 + 0.5 * Math.sin(t * BREATH_SPEED)) * BREATH_AMP - - const phase0 = (t / PERIOD - PHASE_OFFSET + 1) % 1 - const phase1 = (t / PERIOD + 1 / RINGS - PHASE_OFFSET + 1) % 1 - const phase2 = (t / PERIOD + 2 / RINGS - PHASE_OFFSET + 1) % 1 - const envelope0 = Math.sin(phase0 * Math.PI) - const envelope1 = Math.sin(phase1 * Math.PI) - const envelope2 = Math.sin(phase2 * Math.PI) - const eased0 = envelope0 * envelope0 * (3 - 2 * envelope0) - const eased1 = envelope1 * envelope1 * (3 - 2 * envelope1) - const eased2 = envelope2 * envelope2 * (3 - 2 * envelope2) - const head0 = phase0 * this.reach - const head1 = phase1 * this.reach - const head2 = phase2 * this.reach - - for (let index = 0; index < distances.length; index++) { - const dist = distances[index] - const delta0 = dist - head0 - const abs0 = delta0 < 0 ? -delta0 : delta0 - const crest0 = abs0 < WIDTH ? 0.5 + 0.5 * Math.cos((delta0 / WIDTH) * Math.PI) : 0 - const tail0 = delta0 < 0 && delta0 > -TAIL ? (1 + delta0 * TAIL_SCALE) ** 2.3 : 0 - - const delta1 = dist - head1 - const abs1 = delta1 < 0 ? -delta1 : delta1 - const crest1 = abs1 < WIDTH ? 0.5 + 0.5 * Math.cos((delta1 / WIDTH) * Math.PI) : 0 - const tail1 = delta1 < 0 && delta1 > -TAIL ? (1 + delta1 * TAIL_SCALE) ** 2.3 : 0 - - const delta2 = dist - head2 - const abs2 = delta2 < 0 ? -delta2 : delta2 - const crest2 = abs2 < WIDTH ? 0.5 + 0.5 * Math.cos((delta2 / WIDTH) * Math.PI) : 0 - const tail2 = delta2 < 0 && delta2 > -TAIL ? (1 + delta2 * TAIL_SCALE) ** 2.3 : 0 - - const level = - (crest0 * AMP + tail0 * TAIL_AMP) * eased0 + - (crest1 * AMP + tail1 * TAIL_AMP) * eased1 + - (crest2 * AMP + tail2 * TAIL_AMP) * eased2 - const rawStrength = (level * RING_SCALE + breath) * edgeFalloff[index] - const strength = (rawStrength > 1 ? 1 : rawStrength) * 0.7 - const offset = index * 4 - const r = Math.round(baseR + deltaR * strength) - const g = Math.round(baseG + deltaG * strength) - const b = Math.round(baseB + deltaB * strength) - bg[offset] = fg[offset] = r - bg[offset + 1] = fg[offset + 1] = g - bg[offset + 2] = fg[offset + 2] = b - bg[offset + 3] = fg[offset + 3] = 255 - } - } - - private setLogoPulse(dist: number, head0: number, eased0: number, head1: number, eased1: number) { - let peak = 0.04 - let primary = 0 - - const delta0 = dist - head0 - const core0 = Math.exp(-(Math.abs(delta0 / 1.2) ** 1.8)) - const soft0 = Math.exp(-(Math.abs(delta0 / 7) ** 1.6)) - const tail0 = delta0 < 0 && delta0 > -7 ? (1 + delta0 / 7) ** 2.6 : 0 - peak += core0 * 0.65 * eased0 - primary += (soft0 * 0.16 + tail0 * 0.22) * eased0 - - const delta1 = dist - head1 - const core1 = Math.exp(-(Math.abs(delta1 / 1.2) ** 1.8)) - const soft1 = Math.exp(-(Math.abs(delta1 / 7) ** 1.6)) - const tail1 = delta1 < 0 && delta1 > -7 ? (1 + delta1 / 7) ** 2.6 : 0 - peak += core1 * 0.65 * eased1 - primary += (soft1 * 0.16 + tail1 * 0.22) * eased1 - - this.pulsePeak = peak > 1 ? 1 : peak - this.pulsePrimary = primary > 1 ? 1 : primary - } - - private drawLogo(frameBuffer: OptimizedBuffer, t: number, rgb: boolean) { - if (this.logoIndexes.length === 0) return - - const buffers = frameBuffer.buffers - const fg = buffers.fg - const bg = buffers.bg - const shadow: Rgb = [ - mixChannel(this.panelRgb[0], this.logoBaseRgb[0], 0.25), - mixChannel(this.panelRgb[1], this.logoBaseRgb[1], 0.25), - mixChannel(this.panelRgb[2], this.logoBaseRgb[2], 0.25), - ] - const phase0 = (t / PERIOD) % 1 - const phase1 = (t / PERIOD + 0.5) % 1 - const envelope0 = Math.sin(phase0 * Math.PI) - const envelope1 = Math.sin(phase1 * Math.PI) - const eased0 = envelope0 * envelope0 * (3 - 2 * envelope0) - const eased1 = envelope1 * envelope1 * (3 - 2 * envelope1) - const head0 = phase0 * LOGO_REACH - const head1 = phase1 * LOGO_REACH - - for (let i = 0; i < LOGO_TEMPLATE.length; i++) { - const cell = LOGO_TEMPLATE[i]! - const index = this.logoIndexes[i]! - const offset = index * 4 - this.setLogoPulse(cell.topDist, head0, eased0, head1, eased1) - const topPeak = this.pulsePeak - const topPrimary = this.pulsePrimary - this.setLogoPulse(cell.bottomDist, head0, eased0, head1, eased1) - const bottomPeak = this.pulsePeak - const bottomPrimary = this.pulsePrimary - - if (cell.kind === LogoCellKind.Background) { - writeLogoTint(bg, offset, shadow, this.primaryRgb, 0, Math.max(topPeak, bottomPeak) * 0.18) - continue - } - - if (cell.kind === LogoCellKind.Top) { - writeLogoTint(fg, offset, this.logoBaseRgb, this.primaryRgb, topPrimary, topPeak) - writeLogoTint(bg, offset, shadow, this.primaryRgb, 0, bottomPeak * 0.18) - continue - } - - if (cell.kind === LogoCellKind.ShadowTop) { - writeLogoTint(fg, offset, shadow, this.primaryRgb, 0, topPeak * 0.18) - continue - } - - if (cell.kind === LogoCellKind.Solid && rgb) { - writeLogoTint(fg, offset, this.logoBaseRgb, this.primaryRgb, topPrimary, topPeak) - writeLogoTint(bg, offset, this.logoBaseRgb, this.primaryRgb, bottomPrimary, bottomPeak) - continue - } - - writeLogoTint( - fg, - offset, - this.logoBaseRgb, - this.primaryRgb, - (topPrimary + bottomPrimary) / 2, - (topPeak + bottomPeak) / 2, - ) - } - } -} diff --git a/packages/tui/src/component/bg-pulse.tsx b/packages/tui/src/component/bg-pulse.tsx deleted file mode 100644 index 01afa4c0e06..00000000000 --- a/packages/tui/src/component/bg-pulse.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { - FrameBufferRenderable, - RGBA, - type OptimizedBuffer, - type RenderContext, - type RenderableOptions, -} from "@opentui/core" -import { extend, useRenderer } from "@opentui/solid" -import { onCleanup, onMount } from "solid-js" -import { useTheme, useThemes } from "../context/theme" -import { tint } from "../theme/color" -import { GoUpsellArtPainter } from "./bg-pulse-render" - -type GoUpsellArtOptions = RenderableOptions & { - backgroundPanel?: RGBA - primary?: RGBA - logoBase?: RGBA -} - -class GoUpsellArtRenderable extends FrameBufferRenderable { - private painter = new GoUpsellArtPainter() - - constructor(ctx: RenderContext, options: GoUpsellArtOptions = {}) { - const width = typeof options.width === "number" ? options.width : 1 - const height = typeof options.height === "number" ? options.height : 1 - super(ctx, { - ...options, - width, - height, - live: options.live ?? true, - respectAlpha: false, - }) - - if (options.width !== undefined && typeof options.width !== "number") this.width = options.width - if (options.height !== undefined && typeof options.height !== "number") this.height = options.height - this.painter.setBackgroundPanel(options.backgroundPanel) - this.painter.setPrimary(options.primary) - this.painter.setLogoBase(options.logoBase) - } - - set backgroundPanel(value: RGBA | undefined) { - if (this.painter.setBackgroundPanel(value)) this.requestRender() - } - - set logoBase(value: RGBA | undefined) { - if (this.painter.setLogoBase(value)) this.requestRender() - } - - set primary(value: RGBA | undefined) { - if (this.painter.setPrimary(value)) this.requestRender() - } - - protected override renderSelf(buffer: OptimizedBuffer, deltaTime = 0): void { - if (!this.visible || this.isDestroyed) return - - this.painter.render(this.frameBuffer, { - deltaTime, - rgb: this._ctx.capabilities?.rgb === true, - }) - super.renderSelf(buffer) - } -} - -declare module "@opentui/solid" { - interface OpenTUIComponents { - go_upsell_art: typeof GoUpsellArtRenderable - } -} - -extend({ go_upsell_art: GoUpsellArtRenderable }) - -export function BgPulse() { - const themes = useThemes() - const theme = useTheme("elevated") - const mode = themes.mode - const renderer = useRenderer() - let targetFps = renderer.targetFps - let maxFps = renderer.maxFps - - onMount(() => { - targetFps = renderer.targetFps - maxFps = renderer.maxFps - renderer.targetFps = 30 - renderer.maxFps = 30 - }) - - onCleanup(() => { - renderer.targetFps = targetFps - renderer.maxFps = maxFps - }) - - return ( - - ) -} diff --git a/packages/tui/src/component/dialog-retry-action.tsx b/packages/tui/src/component/dialog-retry-action.tsx deleted file mode 100644 index 65d7730ac43..00000000000 --- a/packages/tui/src/component/dialog-retry-action.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { RGBA, TextAttributes } from "@opentui/core" -import open from "open" -import { createSignal } from "solid-js" -import { Keymap } from "../context/keymap" -import { useTheme } from "../context/theme" -import { useDialog, type DialogContext } from "../ui/dialog" -import { Link } from "../ui/link" -import { BgPulse } from "./bg-pulse" - -const GO_URL = "https://opencode.ai/go" -const PAD_X = 3 -const PAD_TOP_OUTER = 1 -const FOREGROUND_ALPHA = 186 - -export type DialogRetryActionProps = { - title: string - message: string - label: string - link?: string - onClose?: (dontShowAgain?: boolean) => void -} - -function runAction(props: DialogRetryActionProps, dialog: ReturnType) { - if (props.link) open(props.link).catch(() => {}) - props.onClose?.() - dialog.clear() -} - -function dismiss(props: DialogRetryActionProps, dialog: ReturnType) { - props.onClose?.(true) - dialog.clear() -} - -function panelOverlay(color: RGBA) { - const [r, g, b] = color.toInts() - return RGBA.fromInts(r, g, b, FOREGROUND_ALPHA) -} - -export function DialogRetryAction(props: DialogRetryActionProps) { - const dialog = useDialog() - const theme = useTheme("elevated") - const showGoTreatment = () => props.link === GO_URL - const textBg = () => (showGoTreatment() ? panelOverlay(theme.background.default) : undefined) - const [selected, setSelected] = createSignal<"dismiss" | "action">("action") - - Keymap.createLayer(() => ({ - mode: "modal", - commands: [ - { - bind: "left", - title: "Previous retry option", - group: "Dialog", - run: () => setSelected((value) => (value === "action" ? "dismiss" : "action")), - }, - { - bind: "right", - title: "Next retry option", - group: "Dialog", - run: () => setSelected((value) => (value === "action" ? "dismiss" : "action")), - }, - { - bind: "tab", - title: "Next retry option", - group: "Dialog", - run: () => setSelected((value) => (value === "action" ? "dismiss" : "action")), - }, - { - bind: "return", - title: "Confirm retry option", - group: "Dialog", - run: () => { - if (selected() === "action") runAction(props, dialog) - else dismiss(props, dialog) - }, - }, - ], - })) - - return ( - - {showGoTreatment() ? ( - - - - ) : null} - - - - {props.title} - - dialog.clear()}> - esc - - - - - {props.message} - - - {props.link ? ( - showGoTreatment() ? ( - - - - ) : ( - - - - ) - ) : ( - - )} - - setSelected("dismiss")} - onMouseUp={() => dismiss(props, dialog)} - > - - don't show again - - - setSelected("action")} - onMouseUp={() => runAction(props, dialog)} - > - - {props.label} - - - - - - ) -} - -DialogRetryAction.show = ( - dialog: DialogContext, - props: Pick, -) => { - return new Promise((resolve) => { - dialog.replace( - () => resolve(dontShow ?? false)} />, - () => resolve(false), - ) - }) -} diff --git a/packages/tui/src/component/dialog-session-delete-failed.tsx b/packages/tui/src/component/dialog-session-delete-failed.tsx deleted file mode 100644 index 2d44512d9cd..00000000000 --- a/packages/tui/src/component/dialog-session-delete-failed.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { TextAttributes } from "@opentui/core" -import { Keymap } from "../context/keymap" -import { useTheme } from "../context/theme" -import { useDialog } from "../ui/dialog" -import { createStore } from "solid-js/store" -import { For } from "solid-js" - -export function DialogSessionDeleteFailed(props: { - session: string - workspace: string - onDelete?: () => boolean | void | Promise - onRestore?: () => boolean | void | Promise - onDone?: () => void -}) { - const dialog = useDialog() - const theme = useTheme("elevated") - const [store, setStore] = createStore({ - active: "delete" as "delete" | "restore", - }) - - const options = [ - { - id: "delete" as const, - title: "Delete workspace", - description: "Delete the workspace and all sessions attached to it.", - run: props.onDelete, - }, - { - id: "restore" as const, - title: "Restore to new workspace", - description: "Try to restore this session into a new workspace.", - run: props.onRestore, - }, - ] - - async function confirm() { - const result = await options.find((item) => item.id === store.active)?.run?.() - if (result === false) return - props.onDone?.() - if (!props.onDone) dialog.clear() - } - - Keymap.createLayer(() => ({ - mode: "modal", - commands: [ - { bind: "return", title: "Confirm recovery option", group: "Dialog", run: () => void confirm() }, - { bind: "left", title: "Delete broken session", group: "Dialog", run: () => setStore("active", "delete") }, - { bind: "up", title: "Delete broken session", group: "Dialog", run: () => setStore("active", "delete") }, - { - bind: "right", - title: "Restore broken session", - group: "Dialog", - run: () => setStore("active", "restore"), - }, - { - bind: "down", - title: "Restore broken session", - group: "Dialog", - run: () => setStore("active", "restore"), - }, - ], - })) - - return ( - - - - Failed to Delete Session - - dialog.clear()}> - esc - - - - {`The session "${props.session}" could not be deleted because the workspace "${props.workspace}" is not available.`} - - - Choose how you want to recover this broken workspace session. - - - - {(item) => ( - { - setStore("active", item.id) - void confirm() - }} - > - - {item.title} - - - {item.description} - - - )} - - - - ) -} diff --git a/packages/tui/src/component/dialog-tag.tsx b/packages/tui/src/component/dialog-tag.tsx deleted file mode 100644 index 4c1fceb75b0..00000000000 --- a/packages/tui/src/component/dialog-tag.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { createMemo, createResource } from "solid-js" -import { DialogSelect } from "../ui/dialog-select" -import { useDialog } from "../ui/dialog" -import { useClient } from "../context/client" -import { useData } from "../context/data" -import { createStore } from "solid-js/store" - -export function DialogTag(props: { onSelect?: (value: string) => void }) { - const client = useClient() - const dialog = useDialog() - const data = useData() - - const [store] = createStore({ - filter: "", - }) - - const [files] = createResource( - () => [store.filter], - async () => { - const result = await client.api.file - .find({ - query: store.filter, - type: "file", - limit: 5, - location: { - directory: data.location.default().directory, - workspace: data.location.default().workspaceID, - }, - }) - .catch(() => undefined) - return result?.data.map((item) => item.path) ?? [] - }, - ) - - const options = createMemo(() => - (files() ?? []).map((file) => ({ - value: file, - title: file, - })), - ) - - return ( - { - props.onSelect?.(option.value) - dialog.clear() - }} - /> - ) -} diff --git a/packages/tui/src/component/prompt/cwd.ts b/packages/tui/src/component/prompt/cwd.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/tui/src/context/directory.ts b/packages/tui/src/context/directory.ts deleted file mode 100644 index 5c4152812b9..00000000000 --- a/packages/tui/src/context/directory.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createMemo } from "solid-js" -import { useData } from "./data" -import { abbreviateHome } from "../runtime" -import { useTuiPaths } from "./runtime" - -export function useDirectory() { - const data = useData() - const paths = useTuiPaths() - return createMemo(() => { - const directory = data.location.info()?.directory ?? data.location.default().directory ?? paths.cwd - return abbreviateHome(directory, paths.home) - }) -} diff --git a/packages/tui/src/routes/session/dialog-subagent.tsx b/packages/tui/src/routes/session/dialog-subagent.tsx deleted file mode 100644 index 3a92aa1d874..00000000000 --- a/packages/tui/src/routes/session/dialog-subagent.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { DialogSelect } from "../../ui/dialog-select" -import { useRoute } from "../../context/route" - -export function DialogSubagent(props: { sessionID: string }) { - const route = useRoute() - - return ( - { - route.navigate({ - type: "session", - sessionID: props.sessionID, - }) - dialog.clear() - }, - }, - ]} - /> - ) -} diff --git a/packages/tui/src/routes/session/footer.tsx b/packages/tui/src/routes/session/footer.tsx deleted file mode 100644 index e58e66f2a08..00000000000 --- a/packages/tui/src/routes/session/footer.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { createMemo, Match, onCleanup, onMount, Show, Switch } from "solid-js" -import { useTheme } from "../../context/theme" -import { useData } from "../../context/data" -import { useDirectory } from "../../context/directory" -import { useConnected } from "../../component/use-connected" -import { createStore } from "solid-js/store" -import { useRoute } from "../../context/route" -import { usePermission } from "../../context/permission" - -export function Footer() { - const theme = useTheme() - const data = useData() - const route = useRoute() - const permission = usePermission() - const mcp = createMemo( - () => (data.location.mcp.server.list() ?? []).filter((x) => x.status.status === "connected").length, - ) - const mcpError = createMemo(() => (data.location.mcp.server.list() ?? []).some((x) => x.status.status === "failed")) - const permissions = createMemo(() => { - if (route.data.type !== "session") return [] - return data.session.permission.list(route.data.sessionID) ?? [] - }) - const directory = useDirectory() - const connected = useConnected() - - const [store, setStore] = createStore({ - welcome: false, - }) - - onMount(() => { - // Track all timeouts to ensure proper cleanup - const timeouts: ReturnType[] = [] - - function tick() { - if (connected()) return - if (!store.welcome) { - setStore("welcome", true) - timeouts.push(setTimeout(() => tick(), 5000)) - return - } - - if (store.welcome) { - setStore("welcome", false) - timeouts.push(setTimeout(() => tick(), 10_000)) - return - } - } - timeouts.push(setTimeout(() => tick(), 10_000)) - - onCleanup(() => { - timeouts.forEach(clearTimeout) - }) - }) - - return ( - - {directory()} - - - - - Get started /connect - - - - 0}> - - {permissions().length} Permission - {permissions().length > 1 ? "s" : ""} - - - - - - - - - - - - - {mcp()} MCP - - - /status - - - - - ) -} diff --git a/packages/tui/src/routes/session/subagent-footer.tsx b/packages/tui/src/routes/session/subagent-footer.tsx deleted file mode 100644 index 9a9722db7f7..00000000000 --- a/packages/tui/src/routes/session/subagent-footer.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { createMemo, createSignal, Show } from "solid-js" -import { useRouteData } from "../../context/route" -import { useData } from "../../context/data" -import { useTheme } from "../../context/theme" -import { SplitBorder } from "../../ui/border" -import { Locale } from "../../util/locale" -import { useTerminalDimensions } from "@opentui/solid" -import { Keymap } from "../../context/keymap" -import { contextUsage, formatContextUsage } from "../../util/session" - -const money = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", -}) - -export function SubagentFooter() { - const route = useRouteData("session") - const data = useData() - const session = createMemo(() => data.session.get(route.sessionID)) - - const subagentInfo = createMemo(() => { - const s = session() - if (!s) return "Subagent" - const agentMatch = s.title.match(/@(\w+) subagent/) - return agentMatch ? Locale.titlecase(agentMatch[1]) : "Subagent" - }) - - const usage = createMemo(() => { - const current = session() - if (!current) return - const cost = current.cost - const formattedCost = cost > 0 ? money.format(cost) : undefined - const context = contextUsage( - data.session.message.list(route.sessionID), - data.location.model.list(current.location), - current.revert?.messageID, - ) - - return { - context: context ? formatContextUsage(context.tokens, context.percent) : undefined, - cost: formattedCost, - } - }) - - const theme = useTheme("elevated") - const keymap = Keymap.use() - const shortcuts = Keymap.useShortcuts() - const [hover, setHover] = createSignal<"parent" | "prev" | "next" | null>(null) - useTerminalDimensions() - - return ( - - - - - - {subagentInfo()} - - - {(item) => ( - - {[item().context, item().cost].filter(Boolean).join(" · ")} - - )} - - - - setHover("parent")} - onMouseOut={() => setHover(null)} - onMouseUp={() => keymap.dispatch("session.parent")} - backgroundColor={ - hover() === "parent" ? theme.background.action.primary.hovered : theme.background.default - } - > - - Parent {shortcuts.get("session.parent")} - - - setHover("prev")} - onMouseOut={() => setHover(null)} - onMouseUp={() => keymap.dispatch("session.child.previous")} - backgroundColor={hover() === "prev" ? theme.background.action.primary.hovered : theme.background.default} - > - - Prev {shortcuts.get("session.child.previous")} - - - setHover("next")} - onMouseOut={() => setHover(null)} - onMouseUp={() => keymap.dispatch("session.child.next")} - backgroundColor={hover() === "next" ? theme.background.action.primary.hovered : theme.background.default} - > - - Next {shortcuts.get("session.child.next")} - - - - - - - ) -}