- Hang fix: 120s timeout on compression LLM call + raw_stream
channel-close handling (None => break instead of spinning)
- Supervisor effective active count: stop counting finished
JoinHandles as occupying capacity slots
- Universal tool result size cap: truncate_if_needed() on
ToolResult, applied in eval_tool_calls() after escalation block;
configurable via max_tool_result_chars in AppConfig/AgentConfig
- Windowed compression: compression_keep_last config param keeps
the N most recent messages visible after compression
- Fix pre-existing flaky test: add #[serial] to
handle_list_available_unrestricted_when_no_whitelist so it does
not race with TestConfigDirGuard-based tests that temporarily
populate the agents data dir
Two coordinated changes that make openai-compatible OAuth providers usable
with a non-localhost redirect_uri (browser shows the callback URL, user
copies it back into the terminal — the same UX Claude uses).
Fix: OpenAICompatibleOAuthProvider::fixed_redirect_uri() previously returned
Some(uri) for any redirect_uri including public HTTPS URLs, which trapped
run_pkce_flow into trying to bind a TCP listener on a public URL. It now
returns Some only for loopback URIs (127.0.0.1, localhost, ::1). Non-loopback
URIs return None, routing run_pkce_flow to the paste branch.
New tri-format paste parser (parse_paste_input):
- Full callback URL (starts with http:// or https://): parse code + state from
the query string. This is what most modern OAuth providers redirect to and
what a naive user copies from the browser bar.
- Anthropic-style code#state fragment: preserved for Claude compatibility.
- Bare code: accepted with a warning that CSRF state validation is skipped.
For providers whose callback page shows only the code with no state.
State validation moved from mandatory to conditional — if a paste didn't
carry state (bare-code path), we warn and skip the check instead of hard-
failing. The listener path (localhost + LAN redirects) still requires state
because the server sends it in the query.
Adds 9 unit tests covering both changes.
GitHub OAuth Apps issue tokens that never expire and omit expires_in from
the response (they only send access_token, token_type, scope). RFC 6749 §5.1
allows this — expires_in is only REQUIRED for tokens that actually expire.
When expires_in is missing, save the token with expires_at = i64::MAX so
prepare_oauth_access_token never tries to refresh. If the token is ever
revoked server-side, the eventual 401 on the API call is the user's cue
to re-authenticate.
No effect on providers that include expires_in (Moonshot etc. — unchanged).
GitHub's device flow endpoints (and likely other RFC 8628 servers) default
to responding in application/x-www-form-urlencoded unless the client asks
for JSON via the Accept header. Our device auth and polling paths both call
.json() on the response and were failing to decode form-urlencoded bodies
with 'expected value at line 1 column 1'.
Adds Accept: application/json to:
- The device authorization POST in run_device_code_flow
- The device_code polling POST (on the RequestBuilder returned by build_token_request)
RFC 6749 §5.1 already specifies JSON as the token response format, so this
is spec-compliant across providers. Servers that already default to JSON
(Moonshot, etc.) ignore the redundant header.
Adds a commented example under the openai-compatible client section showing
how to configure flow: device_code for RFC 8628 device flow. Uses Moonshot's
kimi-code endpoints as the illustrative reference (users supply their own
client_id — no bundled defaults per §5.8 of the design plan).
Adds 9 unit tests covering:
- yaml deserialization of flow: device_code
- merge preserves base device_authorization_url when user omits
- merge lets user device_authorization_url win
- merge lets user use_pkce_in_device_flow win
- OpenAICompatibleOAuthProvider exposes / defaults both new trait methods
- Full serde roundtrip of a realistic device_code yaml block
No network or polling — pure config/serde logic tests. Brings the test
count from 1134 to 1143.