v0.2.0: a cut stream stops passing for a finished answer
Added
editRetryHint, the first behavioural adapter (ADR-45, opt-in). Edits are
applied by exact match, and a model that returns the right content with the
wrong bytes (re-indented, tabs for spaces, trailing newline dropped) gets its
edit refused, then often resends the sameold_stringfor several turns. With
this quirk enabled on a profile, the turn right after a rejected edit carries
one extra system block naming the exact-match rule and telling the model to
copy the bytes rather than retype them. It repairs nothing on the model's
behalf: the proxy never rewritesold_string, because it has neither the file
nor a way to know which occurrence was meant, and a wrong guess would corrupt
a source file. Off by default, never applied tocount_tokens, and the report
now says on how many turns it fired, because an adapter nobody can see cannot
be judged. Enable it knowing that it is unproven: the first live
measurement (2026-08-07) confirmed it fires exactly when it should, and did
not show it helping, on a model that turned out not to have the defect it
targets. The numbers and what they do not license are in ROADMAP M5.
Changed
- The Rust sidecar shows the same request markers as
lupin top. It was
reading four of them and dropping three, soretryAfterMsanddialectwere
documented but invisible there, and the neweditHintwould have been too. All
three now print, in the wording the Node side has always used (waited:1500ms,
dialect:a+b,editHint), with a test on each side: one log line reads the
same whichever front end is watching it. An emptydialectlist prints
nothing rather than a marker with no name. lupin doctornow names the quirks active on the profile. The report
printed the dialect normalizations that fired but never the quirks configured
on the profile, which the--submitbody has always carried: the person
reading their own terminal knew less than a stranger reading the scoreboard.
The new line carries no warning glyph, because a configured quirk is true on
every run and an alarm that always fires stops being read. It is printed
because some request quirks (noParallelToolCalls,singleSystemMessage,
identityHint) change what the model was asked, so a score earned under them
is not comparable with a bare one.--jsoncarries the same information as a
quirksarray next todialects, always present, so a machine consumer of
the payload alone can tell the two apart without reading the config.
Fixed
-
lupin doctorno longer scores a session that died on the transport
(ADR-23). The headless result was still read starting fromsubtype, whose
name promises a verdict and only reports how Claude Code's own loop exited. A
run whoseterminal_reasonisapi_errororauth_erroris now voided on
that field alone, without waiting foris_errorto agree, so a provider that
never answered can no longer earn the "session completed" point and a score
that reads as a judgement on the model. -
A truncated stream is no longer reported as a finished turn (ADR-44, issue
#1). A provider that answered 200, streamed part of the answer and then
dropped reached Claude Code as a short but well formed turn with
stop_reason: end_turn. On the translated lanes the proxy now ends that
stream with an error event instead of a synthesized clean close, and a tool
call rescued from the cut text is no longer delivered. In passthrough the
bytes are still forwarded untouched, but the request log gains
streamError: truncatedand the provider no longer earns the success that
clears its failover cooldown. A stream that did send itsstop_reasonis
unaffected, even if the connection ends beforemessage_stop.