v0.8.7: status line renders on MobaXterm — gate on turn count not data presence
Root cause: render_status_line suppressed the OAuth line whenever ctx_used, 5h_util, and 7d_util were ALL empty. On a rate-limited session ctx is never recorded (the error path returns before _record_ctx_used) and pre-v0.8.5 the unified-* headers weren't captured on errors — so all three stayed empty turn after turn and the line never appeared on Bryan's work-box. NOT a positioning bug: the line is a plain printf'd dim line (no scroll-region/cursor escapes) and is not coupled to streaming or mouse mode. Fix: suppress only before the first turn (_LARRY_TURNS==0); thereafter always render — empty fields show "—" placeholders, reset date fills in once headers populate. /status now renders on demand even pre-first-turn. CR-taint sweep: coerce_int the reset-epoch arithmetic comparisons + strip_cr the oauth-status color case (MobaXterm CRLF would otherwise crash/blank the line). Verify: bash -n clean; 7/7 unit tests (turn-0 suppressed, turn>=1 placeholders, reset date when populated, renders with LARRY_NO_STREAM=1 + mouse off, survives CR-tainted epoch, LARRY_NO_STATUS=1 still disables). Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
This commit is contained in:
parent
578cefcc35
commit
4a992d9668
53
CHANGELOG.md
53
CHANGELOG.md
@ -4,6 +4,59 @@ All notable changes to `cloverleaf-larry` / `larry-anywhere` are recorded here.
|
||||
Versioning is loose-semver; bumps trigger the in-process self-update on every
|
||||
running client via `LARRY_BASE_URL` + `MANIFEST`.
|
||||
|
||||
## v0.8.7 — 2026-05-27
|
||||
|
||||
Status-line render fix for MobaXterm/Cygwin (Clover). Symptom: the dim
|
||||
between-turn status line (session context + rate-limit reset date) never
|
||||
appeared on Bryan's MobaXterm work-box — the v0.7.1 status-line feature that
|
||||
MEMORY.md flagged as a never-verified passive item.
|
||||
|
||||
**Root cause = suppress-when-empty gate, NOT terminal positioning.**
|
||||
`render_status_line` (larry.sh) gated the OAuth arm on `ctx_used_tokens`,
|
||||
`oauth_5h_utilization`, AND `oauth_7d_utilization` ALL being empty — returning
|
||||
silently (rendering nothing) when so. Two facts made all three stay empty turn
|
||||
after turn on Bryan's box: (1) `STATUS_ctx_used_tokens` is populated by
|
||||
`_record_ctx_used`, which runs only AFTER a successful `agent_turn`; on a
|
||||
`rate_limit_error` the turn returns early (larry.sh ~L3929), so ctx was never
|
||||
recorded; (2) pre-v0.8.5 the `anthropic-ratelimit-unified-*` utilization
|
||||
headers weren't captured on error responses. With every turn erroring, all
|
||||
three gate fields were empty every turn, so the line was suppressed for the
|
||||
whole session and never rendered. This was NOT a positioning bug: the status
|
||||
line is a single plain `printf`'d dim line printed between turns — there is no
|
||||
DECSTBM scroll-region reservation, no cursor save/restore, no absolute-row
|
||||
positioning anywhere in the codebase, so MobaXterm's terminal emulation had
|
||||
nothing to mis-honor. It was also NOT coupled to streaming or mouse mode.
|
||||
|
||||
- **Gate on turn count, not data presence.** `render_status_line` now suppresses
|
||||
ONLY before the first turn has run (`_LARRY_TURNS == 0`, coerce_int-guarded);
|
||||
thereafter it ALWAYS renders. Both auth-mode helpers already self-render a
|
||||
`—` placeholder for any unpopulated field, so the line always shows session
|
||||
context (model context window, turns, session cost) and the rate-limit reset
|
||||
time fills in once a successful call — or, since v0.8.5, a captured error
|
||||
response — populates the headers.
|
||||
- **`/status` always renders on demand**, even before the first turn — an
|
||||
explicit request bypasses the turn-0 gate. Lets Bryan verify the line renders
|
||||
on MobaXterm without first completing a (possibly rate-limited) turn.
|
||||
- **CR-taint hardening (same-pattern sweep).** The OAuth segment's reset-epoch
|
||||
comparisons (`[ <epoch> -le <now> ]`) read `STATUS_oauth_{5h,7d}_reset_epoch`,
|
||||
which come from `_header_value` (strips only the TRAILING CR). A CRLF response
|
||||
on MobaXterm or a non-numeric token would have crashed the arithmetic test and
|
||||
aborted the entire line. Both comparisons now `coerce_int` the epoch first;
|
||||
the `STATUS_oauth_status` color-override `case` is now `strip_cr`-guarded so a
|
||||
`rate_limited\r` value still matches its literal-glob arm.
|
||||
- **Same-pattern sweep results:** audited every escape sequence in larry.sh +
|
||||
lib/ — only color SGR, clear-screen (`\033[2J\033[H`), erase-line
|
||||
(`\r\033[K`), and the opt-in mouse/bracketed-paste modes (off by default since
|
||||
v0.7.5) are used; ZERO scroll-region / cursor-save-restore / absolute-cursor
|
||||
sequences, so no other UI element is at risk of MobaXterm mis-rendering.
|
||||
Confirmed no user-visible element is gated behind streaming (`used_stream`
|
||||
only guards re-printing already-streamed text) or mouse mode.
|
||||
- **Verification:** `bash -n` clean; 7/7 unit tests pass against the shipped
|
||||
render functions — turn-0 suppressed; turn≥1 with empty data renders with
|
||||
placeholders; reset date shown when populated; renders with `LARRY_NO_STREAM=1`
|
||||
+ mouse off (Bryan's exact config); survives a CR-tainted reset epoch without
|
||||
crashing; `LARRY_NO_STATUS=1` still fully disables.
|
||||
|
||||
## v0.8.6 — 2026-05-27
|
||||
|
||||
Work-box → Mac `headers.log` sync (tsk-2026-05-27-023, Clover headers-sync).
|
||||
|
||||
119
larry.sh
119
larry.sh
@ -65,7 +65,7 @@ set -o pipefail
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Config
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
LARRY_VERSION="0.8.6"
|
||||
LARRY_VERSION="0.8.7"
|
||||
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
@ -2626,32 +2626,47 @@ _parse_response_headers() {
|
||||
# agent_turn begins (was: above the prompt, v0.6.9–v0.7.0). It now reads
|
||||
# as a "between turns" marker summarising the just-completed turn's cost
|
||||
# heading into the new request.
|
||||
# Honors LARRY_NO_STATUS=1. Prints nothing if we have no data yet (first
|
||||
# turn of a session). Always ends with a trailing newline so the next
|
||||
#
|
||||
# Honors LARRY_NO_STATUS=1. Always ends with a trailing newline so the next
|
||||
# stream lands cleanly below.
|
||||
#
|
||||
# v0.8.7 — suppress ONLY on the true first turn (no turn has run yet,
|
||||
# _LARRY_TURNS==0). After that, ALWAYS render — even if the rate-limit /
|
||||
# context fields are still empty (the segments self-render "—" placeholders).
|
||||
# Earlier (v0.6.9–v0.8.6) the OAuth arm gated on ctx/5h/7d ALL being empty,
|
||||
# which silently hid the entire line for a whole session whenever every turn
|
||||
# erred (e.g. rate_limit): _record_ctx_used (which populates ctx) runs only
|
||||
# AFTER a successful agent_turn, and pre-v0.8.5 the unified-* utilization
|
||||
# headers weren't captured on error responses — so all three gate fields
|
||||
# stayed empty turn after turn and the line never appeared on MobaXterm.
|
||||
# Root cause of the "status line missing on the work-box" report.
|
||||
# This is NOT a positioning bug: the line is a plain printf'd dim line printed
|
||||
# between turns (no DECSTBM scroll-region / cursor-save / absolute-row escape),
|
||||
# so MobaXterm's terminal emulation has nothing to mis-honor. It is also NOT
|
||||
# coupled to streaming or mouse mode — the between-turn call site (main_loop)
|
||||
# invokes it unconditionally regardless of LARRY_NO_STREAM / LARRY_MOUSE.
|
||||
# Gate on turn count, NOT data presence, so session context (model, turns,
|
||||
# cost, ctx window) always shows and the reset date fills in once a call
|
||||
# populates the headers.
|
||||
render_status_line() {
|
||||
[ "${LARRY_NO_STATUS:-0}" = "1" ] && return 0
|
||||
|
||||
# Pick template by auth mode.
|
||||
# Suppress ONLY before the first turn has run. coerce_int defends against a
|
||||
# CR-tainted counter on Cygwin/MobaXterm (v0.7.5 lesson — never feed a raw
|
||||
# value to `-eq`).
|
||||
local _turns; _turns=$(coerce_int "$_LARRY_TURNS" 0)
|
||||
[ "$_turns" -eq 0 ] && return 0
|
||||
|
||||
# Pick template by auth mode. Both arms self-render "—" for any field that
|
||||
# has no data yet, so the line is always informative even on a fresh or
|
||||
# error-only session.
|
||||
case "$LARRY_AUTH_MODE" in
|
||||
oauth)
|
||||
# Suppress if we have NO context data AND no OAuth data — first turn.
|
||||
if [ -z "$STATUS_ctx_used_tokens" ] \
|
||||
&& [ -z "$STATUS_oauth_5h_utilization" ] \
|
||||
&& [ -z "$STATUS_oauth_7d_utilization" ]; then
|
||||
return 0
|
||||
fi
|
||||
_render_status_line_oauth
|
||||
;;
|
||||
apikey)
|
||||
# Suppress only when context AND cost both absent (first turn).
|
||||
if [ -z "$STATUS_ctx_used_tokens" ] && [ "$_LARRY_TURNS" -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
_render_status_line_apikey
|
||||
;;
|
||||
oauth) _render_status_line_oauth ;;
|
||||
apikey) _render_status_line_apikey ;;
|
||||
*)
|
||||
return 0 ;;
|
||||
# Unknown auth mode: still show the universal context segment + turns so
|
||||
# the operator gets feedback rather than a blank line.
|
||||
_render_status_line_apikey ;;
|
||||
esac
|
||||
}
|
||||
|
||||
@ -2714,11 +2729,16 @@ _render_status_line_oauth() {
|
||||
else
|
||||
five_pct="—"
|
||||
fi
|
||||
if [ -n "$STATUS_oauth_5h_reset_epoch" ]; then
|
||||
if [ "$STATUS_oauth_5h_reset_epoch" -le "$now" ]; then
|
||||
# v0.8.7: coerce_int the reset epoch before the `-le` test. These values come
|
||||
# from _header_value, which strips only the TRAILING CR; an embedded CR (CRLF
|
||||
# response on MobaXterm) or a non-numeric token would crash `[ X -le N ]` with
|
||||
# an arithmetic error and abort the whole line — same defense as `now` above.
|
||||
local _5h_epoch; _5h_epoch=$(coerce_int "$STATUS_oauth_5h_reset_epoch" 0)
|
||||
if [ -n "$STATUS_oauth_5h_reset_epoch" ] && [ "$_5h_epoch" -gt 0 ]; then
|
||||
if [ "$_5h_epoch" -le "$now" ]; then
|
||||
five_reset="— reset"
|
||||
else
|
||||
five_reset="reset $(_epoch_to_hhmm "$STATUS_oauth_5h_reset_epoch")"
|
||||
five_reset="reset $(_epoch_to_hhmm "$_5h_epoch")"
|
||||
fi
|
||||
else
|
||||
five_reset="reset —"
|
||||
@ -2735,19 +2755,25 @@ _render_status_line_oauth() {
|
||||
else
|
||||
seven_pct="—"
|
||||
fi
|
||||
if [ -n "$STATUS_oauth_7d_reset_epoch" ]; then
|
||||
if [ "$STATUS_oauth_7d_reset_epoch" -le "$now" ]; then
|
||||
# v0.8.7: coerce_int the 7d reset epoch — same CR-taint / arithmetic-crash
|
||||
# defense as the 5h segment above.
|
||||
local _7d_epoch; _7d_epoch=$(coerce_int "$STATUS_oauth_7d_reset_epoch" 0)
|
||||
if [ -n "$STATUS_oauth_7d_reset_epoch" ] && [ "$_7d_epoch" -gt 0 ]; then
|
||||
if [ "$_7d_epoch" -le "$now" ]; then
|
||||
seven_reset="— reset"
|
||||
else
|
||||
seven_reset="reset $(_epoch_to_ddd_mmm_d "$STATUS_oauth_7d_reset_epoch")"
|
||||
seven_reset="reset $(_epoch_to_ddd_mmm_d "$_7d_epoch")"
|
||||
fi
|
||||
else
|
||||
seven_reset="reset —"
|
||||
fi
|
||||
|
||||
# Status-level color override (warning → yellow, rate_limited → red wins).
|
||||
# v0.8.7: strip_cr before the case — STATUS_oauth_status comes from an API
|
||||
# header and a CRLF response (MobaXterm) would leave "rate_limited\r", which
|
||||
# the literal-glob case arm would never match (silent loss of the red cue).
|
||||
local overall_pre=""
|
||||
case "$STATUS_oauth_status" in
|
||||
case "$(strip_cr "$STATUS_oauth_status")" in
|
||||
rate_limited) overall_pre="$C_RED" ;;
|
||||
warning) overall_pre="$C_YELLOW" ;;
|
||||
esac
|
||||
@ -4318,13 +4344,19 @@ Multi-line input:
|
||||
are not matched. Binary files and files >250 KB are skipped/truncated with
|
||||
a warning. TAB after @ autocompletes against files in cwd (fzf if installed).
|
||||
|
||||
Status line (v0.6.9, repositioned v0.7.1):
|
||||
Status line (v0.6.9, repositioned v0.7.1, render fix v0.8.7):
|
||||
A dim 1-line summary prints between turns — after you submit input and
|
||||
before larry's response begins — summarising the just-completed turn:
|
||||
OAuth: ─ ctx 12% (24K/200K) ─ 5h 1.8% reset 19:45 ─ 7d 73.7% reset Mon Jun 2 ─
|
||||
API key: ─ ctx 12% (24K/200K) ─ $0.213 session ─ 14 turns ─
|
||||
Disable entirely with LARRY_NO_STATUS=1. Force re-display with /status.
|
||||
Suppressed automatically on the first turn (no data yet).
|
||||
Disable entirely with LARRY_NO_STATUS=1. Force re-display anytime with
|
||||
/status (renders even before the first turn). Suppressed automatically
|
||||
ONLY before the first turn has run; thereafter it always renders — fields
|
||||
with no data yet show a "—" placeholder, and the rate-limit reset time
|
||||
fills in once a successful call (or a captured error response) populates
|
||||
the API headers. It is a plain printed line (no terminal-positioning escape
|
||||
sequences) and is not coupled to streaming or mouse mode, so it renders the
|
||||
same on MobaXterm/Cygwin as on a native Linux terminal.
|
||||
|
||||
TAB completion (v0.6.6/v0.6.7/v0.7.0):
|
||||
Type '/' followed by any prefix and press TAB.
|
||||
@ -5218,19 +5250,26 @@ main_loop() {
|
||||
/cost) print_cost_summary; continue ;;
|
||||
/status) # v0.6.9: force-render the persistent status line on demand,
|
||||
# e.g. when it has scrolled off-screen mid-conversation.
|
||||
# v0.8.7: ALWAYS render on explicit /status, even before the
|
||||
# first turn. The turn-0 suppression in render_status_line is
|
||||
# only for the AUTOMATIC between-turn render (nothing to report
|
||||
# yet); an explicit /status is a deliberate request, so honor
|
||||
# it and show the context/placeholder segments. This also lets
|
||||
# Bryan verify the line renders on MobaXterm without first
|
||||
# completing a (possibly rate-limited) turn.
|
||||
if [ "${LARRY_NO_STATUS:-0}" = "1" ]; then
|
||||
larry_say "status line disabled (LARRY_NO_STATUS=1)"
|
||||
else
|
||||
# Temporarily override the "first turn suppression" by
|
||||
# making sure ctx_used has a value even if unknown.
|
||||
# Lazy-init the context window so the ctx segment shows the
|
||||
# right denominator even with zero turns / no API call yet.
|
||||
[ -z "$STATUS_ctx_window" ] && STATUS_ctx_window=$(_model_context_window "$LARRY_MODEL")
|
||||
if [ -z "$STATUS_ctx_used_tokens" ] \
|
||||
&& [ -z "$STATUS_oauth_5h_utilization" ] \
|
||||
&& [ "$_LARRY_TURNS" -eq 0 ]; then
|
||||
larry_say "no data yet — make a turn first"
|
||||
else
|
||||
render_status_line
|
||||
fi
|
||||
# Render directly via the auth-mode helper to bypass the
|
||||
# turn-0 gate (which only applies to the automatic call).
|
||||
case "$LARRY_AUTH_MODE" in
|
||||
oauth) _render_status_line_oauth ;;
|
||||
apikey) _render_status_line_apikey ;;
|
||||
*) _render_status_line_apikey ;;
|
||||
esac
|
||||
fi
|
||||
continue ;;
|
||||
# v0.7.0: HL7 schema lookup commands.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user