OSC 133;B latches a sticky `.input` cursor semantic in Ghostty (and
Ghostty-derived terminals such as cmux) that only a command-start marker
clears. UserMessageComponent emitted A/B and never C, so for the rest of
the session `cursorIsAtPrompt()` stayed true and every painted cell was
tagged `.input`. With `cursor-click-to-move = true` (Ghostty's default)
every left-click released inside the pane injects a burst of arrow keys
into omp's pty and the editor caret jumps to column 0.
Emit C immediately followed by D;0 at the end of the bubble. The zone is
opened and closed within the same render, so the sticky input state is
cleared while the grouping behaviour the original comment protected is
preserved: no later assistant/tool output can be grouped under this
prompt because the command zone never stays open.
Refs #8030, #6115
Kimi's usage provider built window ids from the raw duration/timeUnit ("300time_unit_minute") and left the aggregate quota on "default", so the status-line usage segment, which matches canonical "5h"/"7d" windows, never rendered for kimi-code sessions.
Derive window ids from the reported span instead (300 minutes -> "5h", 7 days -> "7d"), mirroring the minimax-code convention, and tag the aggregate quota as the weekly window. The payload carries only resetTime for the aggregate, but the observed reset horizon matches the plan's weekly cycle. The segment aggregator also falls back to the reported span (duration within a minute of 5h or 7d) when the id isn't canonical, so cache rows written by older builds still light up the 5h meter in mixed-version fleets.
Gate status-line monthly rendering to Cursor, prefer personal dashboard
rails over legacy /auth/usage request fractions, fall back from unusable
overall buckets to plan, ignore disabled plan percent fields, and keep
on-demand when the included bucket is empty. Also add changelog PR/author
attribution for the external contribution.
Cursor Pro/Pro+ usage-summary still uses individualUsage.plan, but the
web dashboard percent is autoPercentUsed / apiPercentUsed — not
plan.used/limit cents. Prefer those rails for Cursor Models / Other
Models, keep overall+cents fallbacks, floor monthly status-line %, and
repaint after async usage fetch so mo N% does not stay blank.
Cursor's /api/usage-summary now returns individualUsage.plan (and optional
onDemand) for Pro/Pro+/Ultra instead of the overall bucket that #7613
parsed. Fall back to plan when overall is absent, keep overall preferred
when both exist, and render monthly Cursor quotas in the status-line usage
segment.
Verified locally against a live Pro+ account and with focused unit tests.
Final review found `pendingRetryFallbackModel` unreachable. `servingModel`
returns `undefined` only when the session has no model at all, and the
pending getter required one, so the badge term guarding on it could never
fire. Its case — a fallback armed before anything has served — is already
answered by `servingModel`'s bootstrap, which names the current model and
flags it as fallback-routed. Removed, the same duplicate-surface cleanup
that removed `retryFallbackModel`.
Attribution now anchors on the session id rather than the session file. An
unpersisted session has no file, so two `undefined`s compared equal and
stale attribution survived `/new` and branch switches there; every real
switch mints a new id, persisted or not.
The cooldown-expiry restore keeps `#fallbackRouted` when the stored primary
selector cannot be parsed. Nothing is restored on that path, so the session
is still running on the fallback and its remaining turns are still fallback
work; clearing the flag reported them as the configured primary.
`executor-prewalk`'s fake session predates this work and never set
`servingModel`, so the prewalk hand-off stopped advancing the reported
model once the executor began reading attribution from the session. It now
mirrors the hand-off the way the other executor fixtures do.
An Agent Hub row reported a subagent as having run on a model that never
spoke. All 97 of its requests, 421K tokens and $6.19 of cost were served
by the primary; a transient stall then armed a fallback, that fallback
errored on its first request with an exhausted quota, and the run died.
Attribution followed the routing switch rather than the output.
Three surfaces lied independently, each re-deriving "the current model"
and calling it the run's model: the executor's progress snapshot, the
session's fallback selector that the hub row reads first, and the
transcript walk behind a settled row.
Sessions now own attribution. `AgentSession.servingModel` names the model
that produced this session's output, holding the last model that actually
served while a candidate is armed but unproven. A switch is a routing
decision, not evidence the target can produce anything, so the answer only
moves once a turn on the target settles.
Consumers read it instead of reconstructing it. The executor's observer
dropped its own event bookkeeping: that bus also carries advisor turns
running on a different model, and it was reading `retry_fallback_applied`
as proof of service. The hub row reads the same getter, so the main
session — which has no executor progress and no persisted history — stops
rendering an unproven candidate as its plain configured model. A fallback
armed before anything has served is still shown, marked as a fallback,
because there is no earlier work to miscredit there.
One predicate decides "this turn produced output", shared by the live
session and the offline replay so they cannot disagree. `error` and
`aborted` are both failures — a stalled stream is finalized as `aborted`
with its partial block still attached, so a stop reason alone proves
nothing — and a turn needs actionable content, which a `length` stop
burning its budget on unsigned thinking does not have. It tolerates
malformed content blocks: transcripts outlive the shapes that wrote them,
and one bad line previously blanked a whole row's history.
Ordering matters at two swap sites. Both the chain advance and the
cooldown-expiry restore move the model and fan `model_changed` out to
subscribers synchronously, so each now updates fallback state before the
swap rather than after; otherwise an observer reading attribution inside
that window sees the incoming candidate carrying the outgoing one's proof.
A startup-selected fallback owns the run from its first request only on a
fresh session. A resumed transcript already holds turns another model
produced, so there the candidate stays unproven until it answers.
`retryFallbackModel` is removed: every consumer reads `servingModel`, and
keeping a parallel derived getter alive for tests is the duplicate surface
this change set exists to remove.
- 28 symbols across discovery, mcp header policy, agent-hub projection and
rendering, the agent registry, shell tokenizing and changelog comparison
were exported but referenced only inside their own module; they are now
module-private, shrinking the deep-import surface.
- Kept AGENT_PLUGIN_MANIFEST_SCHEMA, AGENT_PLUGIN_MCP_SCHEMA,
parseAgentPluginManifest, clearAgentPluginRootCache and mergeMCPHeaders
exported: each is a seam for tests that defend real parsing or header
precedence behavior.
- Nothing reachable from an explicit exports entry or public barrel changed.
Published per-cwd discovery snapshots to existing task tools and refreshed them from TUI, ACP, and Agent Control Center reload paths.
Added regressions for existing and future task tools across TUI and ACP reloads.
Fixes#7940
Address review feedback: the segment previously read the setting from ctx.session.settings, a second source of truth that could disagree with the component's effectiveSettings.sessionAccent and crashed lightweight test fixtures lacking a settings manager. Resolve the value once in #buildSegmentContext from the effective settings and pass it through SegmentContext.sessionAccent so the name segment and the gap-fill divider consume the same value. Add regression assertions for the enabled and disabled branches and a changelog entry.
The session_name segment rendered the session name text with getSessionAccentHex unconditionally, ignoring the statusLine.sessionAccent setting. The adjacent gap-fill divider (component.ts) already gated on sessionAccent !== false, so disabling the setting only removed the accent-colored divider line while the session name itself stayed hash-colored - an inconsistent half-off state.
Read the setting via ctx.session.settings.get (same pattern already used by the goal segment in this file at line 206) and fall back to the theme accent color when disabled, matching the divider behavior.
app.tools.expand (Ctrl+O) was wired only through the editor's input path,
so when a tool-approval prompt or other selection dialog took keyboard
focus the key was delivered to that component and never reached the expand
handler — a large truncated edit could not be expanded while the user was
deciding whether to approve or deny it.
Promote the shortcut to a global TUI input listener (matching the existing
debug and branch/copy shortcuts) so it fires regardless of focus. It defers
when the main transcript is not the active surface (a fullscreen/anchored
overlay: agent hub, transcript viewer, log viewer, model picker) or when the
focused component rebinds Ctrl+O for its own use (the tree selector's filter
cycle). The editor-scoped handler is removed as a clean cutover.
Fixes#7837
Seven session entry types — title_change, credential_pin, mode_change,
service_tier_change, ttsr_injection, reset_boundary and session_init —
fell through #getEntryDisplayText's default branch to the empty string,
and none of them were in the default view's hidden set. Each one drew as
a bare bullet: a row you cannot read, cannot identify and cannot explain
the gap it leaves in the thread.
Hide them in the default and no-tools views, alongside the settings
entries they resemble, and give every one of them a label for `all`
mode. The default branch now falls back to the entry type rather than
the empty string, so a type added later degrades to a dull row instead
of an invisible one. Service tier renders its per-family map, and says
"(default)" when the tier was cleared instead of printing null.
- Routed session tool provenance through live and rebuilt transcript render paths.
- Kept same-named extension tools on the generic renderer while preserving native tool rendering.
- Added regression coverage for an external recall result collision.
Fixes#7770
- Implemented in-house, zero-dependency utility modules in `pi-utils` covering DOM manipulation, markdown parsing, templating, browser automation helpers, and terminal buffers.
- Migrated packages across the repository to consume the new internal utilities and `omptype` schema validators instead of external dependencies.
- Removed multiple external runtime and development dependencies including Zod, Marked, LRU cache, Turndown, and Puppeteer browser packages.
Shift-Tab entered the off state correctly, but both status-line render paths suppressed its label. Render off explicitly so users can distinguish disabled reasoning from a missing option.
Fixes#7668
Under the `nerd` symbol preset, `status.enabled` and `status.shadowed` are
Nerd Font private-use icons (U+F111 / U+F10C). Those glyphs are drawn two
cells wide, but `visibleWidth` counts them as one: `tui/utils.ts` pins
`ambiguousIsNarrow: true`, and the PUA block is East_Asian_Width=Ambiguous.
With no separator the icon overhangs into the next cell and swallows the
label's first character, so the role chips in the model browser and model
hub render as `efault` / `ision` / `lan` / `ask` / `dvisor` instead of
`default` / `vision` / `plan` / `task` / `advisor`.
The adjacent `status.success` check on the very same line already carries a
leading space and renders correctly, which isolates the missing separator
as the cause rather than the glyph itself.
Role-chip assertions in test/model-hub.test.ts are updated for the new
spacing, and the change is recorded under CHANGELOG `[Unreleased]`.
Constraint: lint
Confidence: high
Scope-risk: low
Agent Hub previously pre-rendered every registry row and resolved each
row's observer via getSessions().find, which copy-sorts the full observer
map. On large rosters that made open/selection/age-tick O(all rows) and
observer lookup near O(N^2 log N).
- Add SessionObserverRegistry.getSession(id) for O(1) Map lookup
- Lazy-render hub rows around the selection within the terminal line budget
- Keep ordering, selection, status counts, overflow, badges, and task lines
- Add 10k-row regression covering bounded getSession/render work
- Passed the authorized leaf through the branch executor and revalidated it before rewriting history.
- Refused promotion during active turns and bounded post-prompt drains.
- Consumed unavailable branch keys while showing pending and refusal state in the panel.
Fixes#7474
- The 'N tool calls elided' replay placeholder leaked tool activity while
display.hideToolActivity was on; it is now a visibility-aware component
wired into both the hotkey and /settings toggle paths.
- Added replay + live-reveal regression coverage.