v2026.8.20
Breaking Changes
Fixed
/resumenow reuses exact, byte-bounded streaming session summaries for unchanged files and paints the visible transcript tail before progressively warming older messages, avoiding repeat parse/render stalls without weakening picker metadata or full-text search.- Assistant text that arrives after the last tool call now renders below the tool cards instead of updating the blob above the stack, so approval questions stay visible (#993 by @leeseunguk).
- Late Cursor
tool_execution_endevents now create a TUI tool card when none is pending, so a result is not rendered without a card (#1011). - Session title generation now uses the session model's summarization auth instead of remapped compaction auth, so an explicit compaction model no longer produces
unauthenticatedCursor title calls (#982 by @leeseunguk). - Cursor 0-token
resource_exhaustedretries the same model after remint/compact instead of falling back to another provider, and too-small overflow compact now drops to the last user turn (#1015 by @leeseunguk). - Cursor native
todo/updateTodoscalls now persist assenpi.todo-stateeven when the server resolves them without a localop, so the/todowidget no longer stays empty after a successful native todo update (#994 by @leeseunguk). - Native Cursor sessions no longer compact mid-turn while a Run is live:
compactBeforeNextAdmissionno-ops forcursor/cursor-cli-oauth, and blocking/generated compaction refuse those providers until the session is idle, so a mid-turn compact cannot poisonconversationIdand trigger 0-tokenresource_exhausted(#986 by @leeseunguk). - Native Cursor
write/editvia the exec bridge now emittool_resultaftertool_execution_end, so plan-touch trackers see.omo/plans/*.mdwrites and momus can unblock (#992 by @leeseunguk). - claude-sdk-oauth stream-start-timeout retries now fork the SDK conversation at the last assistant
boundary before the stalled turn instead of re-attaching and re-sending it, so each retry re-bills
only the turn's own message on a prefix cache read instead of re-writing the whole conversation
(fixes #723 retry-storm re-billing: cache writes grew ~8K per attempt, $25/6min, $1084/3days on
worker dispatch). A stalled first turn with no boundary to fork at re-seeds byte-identically, which
the provider serves from prefix cache after the first write. The retry watchdog cap semantics
(streamRetryTimeoutMscaps the retry continuation, reconciled to the granted stream-start guard)
are now documented on the setting itself. - The Cursor exec bridge fails closed when a session bridge has no captured owning run, and rechecks
run ownership after awaited preflight work so a run that ends during an approval prompt cannot start
a tool side effect afterward (#1002 by @HeiTuz). - Retry waits no longer animate decorative spinner frames at the default 80 ms cadence for sessions with at least 1,000 persisted entries, while the one-second countdown and small-session animation remain intact.
- Hot reload no longer stalls on filesystem watcher teardown: recursive config watchers now run in a worker thread on macOS as well as Linux, and the watch engine tears down its subscriptions off the reload critical path (measured 1.5-62s of
session_shutdownstall eliminated). MCP server reconnect during a hot reload no longer blocks the reload either (startup behavior unchanged). - Settings hot-reload no longer cascades across sessions that share an agent directory when another session saves a routine preference such as
defaultModelduring a reload. The replacement watcher now compares reload-window changes with the request-time settings snapshot, so routine-only writes remain suppressed while substantive configuration edits still reload (#1006 by @Indosaram). - Settings hot-reload now clears the reload handoff unconditionally after
requestReload()settles, preventing a stale plaintext settings snapshot from surviving when the reload successor omits the config-reload builtin (#1006 by @Indosaram). - Compaction no longer treats implausible Cursor billed usage as context size: when the local transcript estimate is at least 50k and billed usage is more than 8× that estimate, the threshold uses the estimate so a multi-million dashboard-cumulative cacheRead cannot force a useless compact (#985 by @leeseunguk).
- Goal continuations are no longer stripped down to the newest one on every provider request. Rewriting
already-sent history invalidated the provider's conversation cache prefix, so a long-running team-mode
session paid a full uncached re-read every turn and drove itself into 429 storms. Continuation history is
now append-only and bounded by normal compaction instead of per-request deletion. - Same-model 429 retries now floor every wait with the exponential schedule (
baseDelayMs * 2^(attempt-1)).
A provider that answers each rate-limit with the same tinyretry-afterhint can no longer pin the retry
cadence at a few milliseconds; longer provider hints still take precedence. - Goal footer tickers no longer freeze the session TUI after a session replacement or reload:
GoalWaitTicker
andGoalElapsedTickernow retire themselves when a tick hits a retired extension context instead of
swallowing the error and ticking against a context that can never render again, so the "Pursuing goal"
elapsed label and the continuation-wait countdown stop freezing and the session keeps self-painting without
an input event; a latersync()with a live context re-arms both tickers (#1028). - TUI mode switches no longer freeze the session UI: the renderer swap now detaches live components instead of
disposing them, so tool spinners, reveal animations, and extension widget intervals keep their periodic
repaint across the switch and the TUI does not stall until the next input event (#1028). - A vetoed or failed
/reloadno longer destroys live extension footers, task widgets, and hook statuses:
extension UI is reset only once the reload actually proceeds, so an idle session keeps its periodic repaint
source after a deferred or failed reload (#1028). - Config hot-reload no longer registers whole directory subtrees with the OS watcher: recursive watch targets
now open one non-recursive subscription per in-scope directory the scan already visits (skipping
node_modules,.git, symlinks, and filtered paths) and reconcile subscriptions after every rescan, so an
extensions or skills directory containingnode_modulesno longer drivesfseventsdto 123% CPU and
multi-GB RSS on macOS (#1041). - Native Cursor sessions with Claude-named models no longer reject parallel tool starts as invalid event order:
ANTML invoke recovery is skipped whenmodel.api === "cursor-agent"
(#1013 by @leeseunguk). - Cursor turns that end as
stopwhile the assistant message still contains toolCall blocks now continue so
pending tools run, and a turn whose tool calls were all already resolved on the Cursor exec channel no longer
re-enters the agent loop for an extra provider round-trip
(#1016 by @leeseunguk). - The
cursor-cli-oauthlane now spawns with the catalog suffix variant id for explicit thinking levels
(claude-fable-5-thinking-low,gpt-5.5-extra-high) instead of the bracket parameter form, matching the
native lane's switch away from bare capability ids that Cursor's Run RPC rejects with
Connect error not_found(#1020). - Cursor exec tool calls that lose their owning run during an awaited preflight (for example after an approval
prompt) now emit atool_execution_enderror event, so the TUI records the failed tool result instead of
leaving a dangling tool call with a start and no end
(#1002 by @HeiTuz). - A Cursor turn that goes quiet after all tools have completed now ends normally instead of hanging until the
stream idle timeout, both for native Cursor tool runs and buffered exec results
(#999 by @leeseunguk). - Cursor conversation-id rotation now persists under the agent directory (
CODING_AGENT_DIRor
~/.senpi/agent) instead of$HOME/cursor-conversation-ids.json, so a reminted wire id survives a TUI
restart (#998 by @leeseunguk). - A Cursor 0-token
resource_exhaustednow surfaces on the first failure of astream()call so the session
compacts before any conversation-id rotation, and a rotation skip at the 3-rotation cap remints a fresh wire
id on the next stream instead of failing every later request with a poisoned-conversation error
(#998 by @leeseunguk).
Added
- The notice-box primitives are now part of the public API:
buildNoticeBox,noticeMessageRenderer,
noticeEntryRenderer, and theNoticeSpec/NoticeLine/NoticeTonetypes are exported from the package
entry so extensions can render transcript notices in the shared visual family instead of re-implementing it.
Changed
-
Every remaining divergent transcript card now renders through the shared notice box (
customMessageBg
background block, bold tone title, dim body): loaded-resource conflict diagnostics, the update-available
and package-update notifications, the risky-main-model and high-reasoning warnings, the rules banner,
the prompt URL widget card, and the earendil announcement. Visible text is unchanged. -
The CLI no longer parses and evaluates the 1.2 MB Claude Agent SDK bundle or the jsdom/Readability/turndown
HTML stack while starting up. Both now load on first use behind the repository's documented lazy-boundary
pattern — the SDK when a claude-sdk-oauth stream actually opens, the HTML converters when webfetch actually
converts an HTML response — which removes 680 modules from the startup import graph (14,203 → 13,523).
Behavior is unchanged; only the moment the two dependencies are loaded moved. -
Every launch is one process lighter:
cli.tsnow loads the agent (cli-main) in its own process
instead of re-spawning Node, unless the run actually needs an isolated process. The child spawn is
kept byte-for-byte for the two cases that require it — an inherited Inspector option (--inspect*
in exec args orNODE_OPTIONS), whose debugger socket must be released and re-opened in the process
that runs the agent, and any custom exec arguments (for example--max-old-space-size), which only
apply at process start and so must be replayed onto a fresh process. Brand environment scrubbing is
unaffected:cli-mainscrubs the variable itself, so it is scrubbed in-process before anything the
agent spawns can inherit it. Measured onsenpi --help: 1.206 s -> 1.131 s (-75 ms, -6.2%). -
Startup is faster after the first run: the CLI now enables Node's on-disk module compile cache
(enableCompileCache()) in bothcli.tsandcli-main.tsand publishes the resolved cache directory
throughNODE_COMPILE_CACHEso the spawnedcli-mainchild process reuses it instead of re-compiling
the full engine module graph on every launch. An existingNODE_COMPILE_CACHEvalue is never overridden,
NODE_DISABLE_COMPILE_CACHE=1keeps the cache off, and runtimes without the API (the compiled binary)
degrade to plain compilation.