Quellcode durchsuchen

refactor: remove unused V2 code (#39699)

Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
opencode-agent[bot] vor 2 Wochen
Ursprung
Commit
0ffec67ca3

+ 0 - 2
packages/core/src/plugin/agent.ts

@@ -11,8 +11,6 @@ import { Permission } from "../permission"
 // Combined output files written by the Shell service, e.g. `<data>/shell/<projectID>/<shellID>.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.
 

+ 0 - 436
packages/tui/src/component/bg-pulse-render.ts

@@ -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,
-      )
-    }
-  }
-}

+ 0 - 102
packages/tui/src/component/bg-pulse.tsx

@@ -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<FrameBufferRenderable> & {
-  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 (
-    <go_upsell_art
-      width="100%"
-      height="100%"
-      backgroundPanel={theme.background.default}
-      primary={theme.hue.interactive[mode() === "light" ? 800 : 200]}
-      logoBase={tint(theme.background.default, theme.text.default, 0.62)}
-      live
-    />
-  )
-}

+ 0 - 164
packages/tui/src/component/dialog-retry-action.tsx

@@ -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<typeof useDialog>) {
-  if (props.link) open(props.link).catch(() => {})
-  props.onClose?.()
-  dialog.clear()
-}
-
-function dismiss(props: DialogRetryActionProps, dialog: ReturnType<typeof useDialog>) {
-  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 (
-    <box>
-      {showGoTreatment() ? (
-        <box position="absolute" top={-PAD_TOP_OUTER} left={0} right={0} bottom={0} zIndex={0}>
-          <BgPulse />
-        </box>
-      ) : null}
-      <box zIndex={1} paddingLeft={PAD_X} paddingRight={PAD_X} paddingBottom={1} gap={1}>
-        <box flexDirection="row" justifyContent="space-between">
-          <text attributes={TextAttributes.BOLD} fg={theme.text.default} bg={textBg()}>
-            {props.title}
-          </text>
-          <text fg={theme.text.subdued} bg={textBg()} onMouseUp={() => dialog.clear()}>
-            esc
-          </text>
-        </box>
-        <box gap={0}>
-          <text fg={theme.text.subdued} bg={textBg()}>
-            {props.message}
-          </text>
-        </box>
-        {props.link ? (
-          showGoTreatment() ? (
-            <box alignItems="center" justifyContent="flex-end" height={7} paddingBottom={1}>
-              <Link href={props.link} fg={theme.markdown.link} bg={textBg()} wrapMode="none" />
-            </box>
-          ) : (
-            <box width="100%" flexDirection="row" justifyContent="center" paddingBottom={1}>
-              <Link href={props.link} fg={theme.markdown.link} wrapMode="none" />
-            </box>
-          )
-        ) : (
-          <box paddingBottom={1} />
-        )}
-        <box flexDirection="row" justifyContent="space-between">
-          <box
-            paddingLeft={2}
-            paddingRight={2}
-            backgroundColor={
-              selected() === "dismiss" ? theme.background.action.primary.focused : RGBA.fromInts(0, 0, 0, 0)
-            }
-            onMouseOver={() => setSelected("dismiss")}
-            onMouseUp={() => dismiss(props, dialog)}
-          >
-            <text
-              fg={selected() === "dismiss" ? theme.text.action.primary.focused : theme.text.subdued}
-              bg={selected() === "dismiss" ? undefined : textBg()}
-              attributes={selected() === "dismiss" ? TextAttributes.BOLD : undefined}
-            >
-              don't show again
-            </text>
-          </box>
-          <box
-            paddingLeft={2}
-            paddingRight={2}
-            backgroundColor={
-              selected() === "action" ? theme.background.action.primary.focused : RGBA.fromInts(0, 0, 0, 0)
-            }
-            onMouseOver={() => setSelected("action")}
-            onMouseUp={() => runAction(props, dialog)}
-          >
-            <text
-              fg={selected() === "action" ? theme.text.action.primary.focused : theme.text.default}
-              bg={selected() === "action" ? undefined : textBg()}
-              attributes={selected() === "action" ? TextAttributes.BOLD : undefined}
-            >
-              {props.label}
-            </text>
-          </box>
-        </box>
-      </box>
-    </box>
-  )
-}
-
-DialogRetryAction.show = (
-  dialog: DialogContext,
-  props: Pick<DialogRetryActionProps, "title" | "message" | "label" | "link">,
-) => {
-  return new Promise<boolean>((resolve) => {
-    dialog.replace(
-      () => <DialogRetryAction {...props} onClose={(dontShow) => resolve(dontShow ?? false)} />,
-      () => resolve(false),
-    )
-  })
-}

+ 0 - 113
packages/tui/src/component/dialog-session-delete-failed.tsx

@@ -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<boolean | void>
-  onRestore?: () => boolean | void | Promise<boolean | void>
-  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 (
-    <box paddingLeft={2} paddingRight={2} gap={1}>
-      <box flexDirection="row" justifyContent="space-between">
-        <text attributes={TextAttributes.BOLD} fg={theme.text.default}>
-          Failed to Delete Session
-        </text>
-        <text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
-          esc
-        </text>
-      </box>
-      <text fg={theme.text.subdued} wrapMode="word">
-        {`The session "${props.session}" could not be deleted because the workspace "${props.workspace}" is not available.`}
-      </text>
-      <text fg={theme.text.subdued} wrapMode="word">
-        Choose how you want to recover this broken workspace session.
-      </text>
-      <box flexDirection="column" paddingBottom={1} gap={1}>
-        <For each={options}>
-          {(item) => (
-            <box
-              flexDirection="column"
-              paddingLeft={1}
-              paddingRight={1}
-              paddingTop={1}
-              paddingBottom={1}
-              backgroundColor={item.id === store.active ? theme.background.action.primary.focused : undefined}
-              onMouseUp={() => {
-                setStore("active", item.id)
-                void confirm()
-              }}
-            >
-              <text
-                attributes={TextAttributes.BOLD}
-                fg={item.id === store.active ? theme.text.action.primary.focused : theme.text.default}
-              >
-                {item.title}
-              </text>
-              <text
-                fg={item.id === store.active ? theme.text.action.primary.focused : theme.text.subdued}
-                wrapMode="word"
-              >
-                {item.description}
-              </text>
-            </box>
-          )}
-        </For>
-      </box>
-    </box>
-  )
-}

+ 0 - 52
packages/tui/src/component/dialog-tag.tsx

@@ -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 (
-    <DialogSelect
-      title="Autocomplete"
-      options={options()}
-      onSelect={(option) => {
-        props.onSelect?.(option.value)
-        dialog.clear()
-      }}
-    />
-  )
-}

+ 0 - 0
packages/tui/src/component/prompt/cwd.ts


+ 0 - 13
packages/tui/src/context/directory.ts

@@ -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)
-  })
-}

+ 0 - 26
packages/tui/src/routes/session/dialog-subagent.tsx

@@ -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 (
-    <DialogSelect
-      title="Subagent Actions"
-      options={[
-        {
-          title: "Open",
-          value: "subagent.view",
-          description: "the subagent's session",
-          onSelect: (dialog) => {
-            route.navigate({
-              type: "session",
-              sessionID: props.sessionID,
-            })
-            dialog.clear()
-          },
-        },
-      ]}
-    />
-  )
-}

+ 0 - 91
packages/tui/src/routes/session/footer.tsx

@@ -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<typeof setTimeout>[] = []
-
-    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 (
-    <box flexDirection="row" justifyContent="space-between" gap={1} flexShrink={0}>
-      <text fg={theme.text.subdued}>{directory()}</text>
-      <box gap={2} flexDirection="row" flexShrink={0}>
-        <Switch>
-          <Match when={store.welcome}>
-            <text fg={theme.text.default}>
-              Get started <span style={{ fg: theme.text.subdued }}>/connect</span>
-            </text>
-          </Match>
-          <Match when={connected()}>
-            <Show when={permission.mode !== "auto" && permissions().length > 0}>
-              <text fg={theme.text.feedback.warning.default}>
-                <span style={{ fg: theme.text.feedback.warning.default }}>△</span> {permissions().length} Permission
-                {permissions().length > 1 ? "s" : ""}
-              </text>
-            </Show>
-            <Show when={mcp()}>
-              <text fg={theme.text.default}>
-                <Switch>
-                  <Match when={mcpError()}>
-                    <span style={{ fg: theme.text.feedback.error.default }}>⊙ </span>
-                  </Match>
-                  <Match when={true}>
-                    <span style={{ fg: theme.text.feedback.success.default }}>⊙ </span>
-                  </Match>
-                </Switch>
-                {mcp()} MCP
-              </text>
-            </Show>
-            <text fg={theme.text.subdued}>/status</text>
-          </Match>
-        </Switch>
-      </box>
-    </box>
-  )
-}

+ 0 - 115
packages/tui/src/routes/session/subagent-footer.tsx

@@ -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 (
-    <box flexShrink={0}>
-      <box
-        paddingTop={1}
-        paddingBottom={1}
-        paddingLeft={2}
-        paddingRight={1}
-        {...SplitBorder}
-        border={["left"]}
-        borderColor={theme.border.default}
-        flexShrink={0}
-        backgroundColor={theme.background.default}
-      >
-        <box flexDirection="row" justifyContent="space-between" gap={1}>
-          <box flexDirection="row" gap={1}>
-            <text fg={theme.text.default}>
-              <b>{subagentInfo()}</b>
-            </text>
-            <Show when={usage()}>
-              {(item) => (
-                <text fg={theme.text.subdued} wrapMode="none">
-                  {[item().context, item().cost].filter(Boolean).join(" · ")}
-                </text>
-              )}
-            </Show>
-          </box>
-          <box flexDirection="row" gap={2}>
-            <box
-              onMouseOver={() => setHover("parent")}
-              onMouseOut={() => setHover(null)}
-              onMouseUp={() => keymap.dispatch("session.parent")}
-              backgroundColor={
-                hover() === "parent" ? theme.background.action.primary.hovered : theme.background.default
-              }
-            >
-              <text fg={theme.text.default}>
-                Parent <span style={{ fg: theme.text.subdued }}>{shortcuts.get("session.parent")}</span>
-              </text>
-            </box>
-            <box
-              onMouseOver={() => setHover("prev")}
-              onMouseOut={() => setHover(null)}
-              onMouseUp={() => keymap.dispatch("session.child.previous")}
-              backgroundColor={hover() === "prev" ? theme.background.action.primary.hovered : theme.background.default}
-            >
-              <text fg={theme.text.default}>
-                Prev <span style={{ fg: theme.text.subdued }}>{shortcuts.get("session.child.previous")}</span>
-              </text>
-            </box>
-            <box
-              onMouseOver={() => setHover("next")}
-              onMouseOut={() => setHover(null)}
-              onMouseUp={() => keymap.dispatch("session.child.next")}
-              backgroundColor={hover() === "next" ? theme.background.action.primary.hovered : theme.background.default}
-            >
-              <text fg={theme.text.default}>
-                Next <span style={{ fg: theme.text.subdued }}>{shortcuts.get("session.child.next")}</span>
-              </text>
-            </box>
-          </box>
-        </box>
-      </box>
-    </box>
-  )
-}