Skip to content

v0.12.2 — REPL Token Ceiling + TUI Scroll-Mode Fixes

Choose a tag to compare

@DusanCar-sudo DusanCar-sudo released this 27 Jul 04:20
· 110 commits to master since this release

Added

  • Cumulative token ceiling now covers the plain REPL session. Previously
    only the gazelle orchestrator's coder-conversation path held a
    SessionBudget; the REPL passed none, so recordTurn/recordCall were
    no-ops and net-of-cache spend was never tracked across messages. One budget
    now lives for the life of the REPL process.

    Turn-count enforcement is deliberately not extended here. The
    per-invocation maxTurns guard already holds correctly in the REPL, and a
    cumulative turn cap is the wrong instrument for interactive use where a
    human types every message and watches every response. Measured on a real
    96-minute session — 58 turns across 9 messages, 86% cache hit rate, $0.50
    total, peak 19 turns in any single message — a 50-turn session cap would
    have interrupted that for crossing a count that said nothing about its cost.

Fixed

  • TUI: SS3-encoded arrow keys corrupted the input and flipped the screen.
    Terminals in application-cursor mode send \x1bOA for Up. The input parser
    handled CSI (\x1b[A) but not SS3, so the sequence fell through to the
    bare-Escape branch: Escape entered scroll mode and the remaining O and
    final letter were typed into the input as literal text. Pressing an arrow
    key could flip the display to the scroll view and inject garbage.

    This is very likely the real cause of the "response stalled, then typing
    fixed it" reports — the live view was frozen in scroll mode, not the network
    connection. Typing a printable character exits scroll mode and redraws,
    which is exactly the observed "fix". Normalized the same way
    context-tuner.ts's splitKeys already did, including waiting for the
    final byte when the sequence is split across reads.

  • TUI: typing q to leave scroll mode silently dropped the character — a
    word beginning with "q" lost its first letter. q was excluded from the
    printable-exit path but advertised nowhere. i remains excluded on purpose:
    the scroll indicator documents "i/Enter/Esc insert", so it is a deliberate
    vim-style command.

  • TUI: terminal resizes during an overlay were dropped entirely. While a
    command palette, session switcher, context tuner, or confirmation prompt
    held the screen, handleResize returned early and the event was lost,
    leaving the scroll region set to the old geometry once the overlay closed.
    The resize is now recorded and applied when input resumes.

Known follow-ups (not in this release)

  • ESC timeout. A lone Escape stays buffered until the next byte arrives,
    so pressing Esc alone does nothing until another key is pressed. Fixing it
    needs a ~25–50 ms timer to disambiguate Escape from the start of a sequence;
    a fixed timeout can misfire on slow terminals and high-latency SSH, so it is
    deferred to its own pass rather than rushed into a patch release.
  • Archimedes alternator has no budget wired in at all. alternator.run()
    accepts no budget in its options interface, so neither ceiling applies to
    that path. Closing it needs a signature change.

Fixed (previously unreleased)

  • npm test sent a real Telegram voice message and overwrote a real API
    key.
    Two tests reached outside their sandbox on any machine with a
    configured bot:

    tests/telegram-voice-live.test.ts was gated skipIf(isCI || !hasTelegramConfig)
    — it skipped on CI and ran everywhere else, firing a live voice note at
    telegram.json:default_chat_id on every run. The sends left no trace in the
    bot's journal or session history (they came from the vitest process, not the
    bot service), which made them look like unexplained "the bot keeps sending me
    audio every few hours" behaviour. Now opt-in via AURA_LIVE_VOICE_TEST=1,
    and it requires an explicit AURA_TEST_CHAT_ID rather than falling back to a
    real person's chat.

    tests/provider-wizard.test.ts isolated XDG_CONFIG_HOME but not
    os.homedir(), which is what key-store.ts uses — so saveKey() wrote the
    fixture sk-test-key into the developer's real ~/.aura/keys.json,
    replacing their DeepSeek credential. It now mocks os.homedir() like the
    other filesystem-touching tests.

  • Streaming responses could hang forever on cloud providers. An SSE stream
    can go silent without the TCP connection closing — no error, no terminating
    chunk, the read simply blocks on data that never arrives. Aura waited
    indefinitely and showed the user nothing.

    The SDKs do not cover this, despite appearing to. Both openai and
    @anthropic-ai/sdk default to a 600s timeout, but implement it as
    fetch(...).finally(() => clearTimeout(timer)) — and the fetch promise
    settles when response headers arrive, which for a stream is immediate. The
    timer is cancelled before a single chunk of the body is read, so the
    documented timeout covers time-to-headers and nothing else.

    Streams are now guarded by an idle timeout measured between chunks
    (src/providers/stream-timeout.ts), default 60s, applied to both the
    OpenAI-compatible and Anthropic paths. Total-duration limits would be the
    wrong tool: a legitimate turn can run for minutes through tool calls, but a
    healthy stream never goes quiet for long once tokens flow. 60s was calibrated
    against 529 consecutive-turn intervals from this project's own token log
    (median 3.9s, p90 27s — and those measure whole turns including tool
    execution, so real inter-chunk gaps are far smaller).

    On a stall the underlying request is aborted, so the socket is released
    rather than leaked. The request is retried once, but only when nothing has
    reached the consumer yet
    — after text has been yielded the agent loop has
    already accumulated and displayed it, and re-running would append a second
    full response, corrupting both the transcript and the token accounting. In
    that case the stall surfaces as a clear provider error instead of hanging.
    This mirrors the existing rule in resilient.ts, which retries acquisition
    of the first chunk but never a mid-stream failure.

    Override with AURA_STREAM_IDLE_MS (values below 5000 are floored; 0
    disables the guard entirely).