Commit Graph
2317 Commits
Author SHA1 Message Date
Frank Song 33859b2704 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 09:43:54 +08:00
Frank Song d8b7ac23d6 fix(wakeup): require pool recovery before clearing pause 2026-07-09 09:43:48 +08:00
sbe27 68d406f7ff fix(models): refresh OpenCode Go static catalog 2026-07-09 01:23:08 +00:00
ai-ag2026andClaude Opus 4.8 8dde1de4fa fix(bg): back off drain loop instead of silent tight-spin (#2476, #4633)
_drain_loop read process_registry.completion_queue directly under a bare
`except Exception: continue`. A registry missing that attribute raised
AttributeError that was swallowed and retried with no backoff — a
100%-CPU tight loop with no log line. The loop now reads the queue via
getattr(..., None) and backs off on the stop event when it is absent
(mirroring streaming.py), catches queue.Empty explicitly for the normal
idle path, and logs a warning + backs off (_DRAIN_STOP.wait(1.0)) on any
other queue error so a persistent fault can neither spin the CPU nor stay
invisible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 01:18:21 +00:00
Frank Song a5974d0ae9 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 09:15:09 +08:00
Frank Song 9d9d52958e fix(wakeup): restore pause on streaming stop race 2026-07-09 09:15:04 +08:00
Frank Song b21038f015 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 09:06:27 +08:00
Frank Song 59c868fe86 fix(wakeup): preserve pause on gateway stop race 2026-07-09 09:06:19 +08:00
ai-ag2026andClaude Opus 4.8 b10116bffc Frame passkey-login 200 response with Content-Length
The passkey-login success handler wrote its 200 JSON body without a
Content-Length header, unlike the password-login and logout handlers. Under
HTTP/1.1 keep-alive that response is unframed, so the browser's fetch().json()
waits for connection close and appears to hang until it times out.

Encode the body once and send Content-Length before end_headers(), mirroring
the /api/auth/login block. Header order is unchanged so set_auth_cookie's
Set-Cookie still precedes end_headers().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 01:05:28 +00:00
ai-ag2026andClaude Opus 4.8 4fc51072a2 fix(streaming): pair metering begin_session with end_session teardown (#4633, #2476)
The streaming worker called meter().begin_session(stream_id) but never a
paired end_session. A turn that produced zero output tokens (pre-flight
cancel, or a setup exception before the first token) left a _SessionMeter
in GlobalMeter._sessions forever: get_stats() only prunes sessions with
first_token_ts > 0, so a zero-token session is never reclaimed, and each
leaked entry inflates the `active` count sent over the SSE metering event.

begin_session() and the metering ticker's .start() are now registered as
the first statements inside the worker's outer `try`, and paired with an
idempotent meter().end_session(stream_id, 0) plus a deterministic
_metering_stop.set() in that try's outer `finally` (the same block that
pops STREAMS/CANCEL_FLAGS) — so every exit path tears the session down.
Deferring .start() until after put() is defined also closes a latent
start-before-put ordering window in the ticker closure.

The metering payload is unchanged; end_session only pops the session.

Verification: tests/test_metering_session_lifecycle.py (leak reproduction,
end_session reclaim, idempotency, begin->cancel-before-token->empty) plus a
plain-assert run on python3.11 (conftest gates pytest to 3.11-3.13; system
python is 3.14). py_compile clean.

WARNING streaming-contract: adjusts the streaming worker's begin/end
teardown ordering. Needs RFC/contract review against docs/rfcs/
(session-sse-contract-v1.md) before merge — draft PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 00:51:28 +00:00
Frank Song ff0e6347cb Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 08:22:26 +08:00
nesquena-hermes 236a2c8032 fix(updates): thread offered channel through apply/force to close the debounce race
Codex gate CORE: /api/updates/apply + /force ignored the request-body channel
and re-read the saved setting, so a channel switch whose debounced autosave
hadn't landed could apply the OLD channel. The banner now sends the channel the
CHECK reported for each target; the endpoints validate it against the enum and
thread it into apply_update/apply_force_update (None → saved-setting fallback,
preserving prior behavior). Agent stays channel-neutral server-side.
2026-07-08 22:42:54 +00:00
g 912c626195 Merge remote-tracking branch 'origin/master' into pr-5790-live 2026-07-08 22:26:22 +00:00
Frank Song 75409c511c fix(wakeup): probe empty-provider recovery changes 2026-07-09 06:07:23 +08:00
Frank Song 91141da397 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 05:34:24 +08:00
Nathan Esquenazi be004baaaf Merge branch 'master' into feat/update-channels 2026-07-08 14:20:48 -07:00
ai-ag2026andClaude Opus 4.8 c5b15850e5 Fix run-journal writer-lock cache leak on session delete
`_lock_for` caches one threading.Lock per (dir, file, pid) in the module-global
`_WRITER_LOCKS`, but nothing evicted those entries: `delete_run_journal` rmtree'd
the on-disk `_run_journal/{sid}/` directory yet left the cached lock objects
behind, so a long-lived gateway leaked one entry per deleted run forever.

Delete now evicts every cached lock whose parent directory matches the removed
session (pid-independent) under `_WRITER_LOCKS_GUARD`, leaving unrelated
sessions' locks intact.

Refs #4633, #2097.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 21:13:32 +00:00
Frank Song cb4248bc25 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 05:03:24 +08:00
ai-ag2026andClaude Opus 4.8 d505b45545 fix(bg): serialize daemon-thread start under a lifecycle lock
start_drain_thread and start_session_channel_reaper checked is_alive()
and then created + started the daemon thread without holding a lock, a
check-then-act race: two concurrent callers could both see "not alive"
and each spawn a thread. The loser's thread was never stored in the
module global and ran forever, un-joinable by the matching stop_*. Both
check-then-start sequences now run under a dedicated
_THREAD_LIFECYCLE_LOCK (kept separate from the purpose-bound
SESSION_CHANNELS_LOCK / _EMIT_COALESCE_LOCK), so exactly one thread is
ever created.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 20:58:18 +00:00
ai-ag2026 15470a9a35 fix(bg): sweep completion-dedup map by delivery lifecycle, not channel (#4633)
BG_TASK_COMPLETE_EVENTS_SEEN gained a session_id -> set[process_id] entry the
first time a bg task completed for a session and was never deleted anywhere, so
it grew unbounded for the server lifetime.

The entry is created in _process_one for EVERY completion, whether or not any
SSE channel/tab exists, so pruning it only when the SessionChannel is reaped
would miss the dominant headless case (task fires, tab closed or never opened —
no channel to collect). Instead the reaper now sweeps the map by DELIVERY: once
a completion is drained (its session_id removed from PENDING_BG_TASK_COMPLETIONS)
the short _move_to_finished dedup window is closed and the entry is swept, every
tick, regardless of any channel. The registry's per-process_id
_completion_consumed gate remains the primary idempotency backstop, so sweeping a
delivered session's set can never resurrect an already-delivered completion.

Session deletion also prunes the entry (new forget_bg_task_completion_dedup),
covering a session deleted while a completion is still pending (which the
delivery-gated sweep deliberately retains).

Refs #4633
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 20:41:59 +00:00
Frank Song 75b1571204 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 04:34:10 +08:00
ai-ag2026andClaude Opus 4.8 b967d89c3a fix(sessions): open state.db read-only for lineage reads + watcher projection
The session-listing path already opens the live agent state.db read-only
(file:...?mode=ro) so a write-capable handle doesn't add checkpoint/lock
surface while the agent streams into the same WAL DB (#5455). Three pure-read
projections were missed and still opened a read-WRITE connection:

  - read_session_lineage_report
  - read_session_lineage_metadata
  - the gateway-watcher fingerprint projection (a 5s poll)

Route all three through a shared open_state_db_readonly() helper that mirrors
the listing path: read-only file: URI with a writable fallback that warns. The
self-heal write path (missing idx_messages_session) is intentionally left
writable. No behavior change beyond open mode; reads perform zero writes.

Refs #5455

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 20:17:45 +00:00
t 0a56f44f5e fix(updates): Fable UX gate — channel re-check race + copy + mobile badge
MUST-FIX: /api/updates/check now accepts an explicit 'channel' in the POST body;
the Settings dropdown passes the just-picked value to checkUpdatesNow(channel) so
the immediate re-check can't race the debounced autosave PUT and answer for the
previous channel (verified live: body channel wins over stale saved setting).

SHOULD-FIX:
- copy: 'soaked' -> 'soak-tested'; append 'Applies to WebUI updates only.' to the
  channel helper text (all 15 locales, native translations)
- mobile: .settings-version-badge gets max-width/overflow/ellipsis in the <=768px
  query so the '· Experimental' suffix can't overflow a 320px viewport

(#5 badge-suffix i18n key deferred — non-blocking fast-follow per Fable, matches
the existing English-only version badge on master.)
2026-07-08 20:01:39 +00:00
t 0168bd1aed fix(updates): apply_clear_lock preserves the configured channel
Codex round-2 gate: apply_clear_lock re-entered _apply_update_inner(target)
without the channel, so an experimental WebUI lock-recovery retry silently fell
back to stable. Now passes _read_update_channel() through. + regression test.
2026-07-08 19:53:32 +00:00
t 162e145c30 fix(updates): channel is WebUI-only — never leak it to the Agent repo check/apply
Codex gate CORE fix: check_for_updates passed the user's WebUI channel into the
Agent repo resolution, so on 'experimental' the Agent ignored its v* tags and
fell back to origin/master. The Agent is a separate project (plain v* tags,
tracks master past tags) and must ALWAYS use DEFAULT_UPDATE_CHANNEL. Forced in
all 3 sites: check_for_updates agent leg + _apply_update_inner + apply_force_update
(target=='agent' -> channel=DEFAULT_UPDATE_CHANNEL). Added a real-git regression
proving Agent release/apply resolution is identical under both WebUI channels.
2026-07-08 19:49:36 +00:00
t 2cf43527a3 feat(updates): stable/experimental release channels
Add an update_channel setting (stable|experimental) that selects which git-tag
stream the self-updater tracks on the single linear master line:

  stable       -> 'v*'      promoted, soaked releases (default; unchanged glob)
  experimental -> 'exp-v*'  every release batch (opt-in testers)

A channel is only a tag glob — no branches, no divergence — so every ff-only
guarantee (#2653/#2846/#3140) is preserved. Channel governs the WebUI repo only;
the Agent repo keeps its historical branch fall-through.

Correctness (advisor-reviewed, Codex + Fable):
- describe uses --match <glob> so a commit tagged both v* and exp-v* resolves
  to the channel-correct tag
- stable never falls through to origin/master when HEAD contains the latest
  stable tag (the firehose-suppression that makes channels work)
- apply_force_update refuses to reset --hard onto an ancestor ref (rewind guard)
- update cache + in-progress guard keyed by (channel, include_agent)
- channel display badge is a SEPARATE field; WEBUI_VERSION stays channel-neutral
  (asset cache-busting / SW cache / stale-client skew all do exact-string equality)

Settings UX: channel dropdown with risk-forward copy + the ff-only asymmetry
helper ('switching back to Stable keeps your current version until Stable catches
up'), channel chip on the version badge, i18n keys, re-check on switch.

Tests: new tests/test_update_channels.py (12 real-git-fixture tests) + existing
update suite updated for the channel-aware signatures.
2026-07-08 19:25:27 +00:00
Frank Song 0419fb0640 fix(wakeup): prove credential recovery before clearing pause 2026-07-09 00:39:27 +08:00
Frank Song 9b352e3acf Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 00:28:30 +08:00
Frank Song 08e9c2898e fix(wakeup): revalidate paused credentials on canonical profile lane 2026-07-09 00:27:57 +08:00
Rod Boev f4172f73b0 fix(mcp): preserve config overrides for profile reads (#5619) 2026-07-08 16:18:52 +00:00
Rod Boev 3c8d9f851e fix(mcp): scope list config reads to active profile (#5619) 2026-07-08 16:18:52 +00:00
Frank Song 68e4770c2b fix(wakeup): revalidate recovered credential pauses 2026-07-08 08:56:05 +08:00
Frank Song ef85f5c5cb Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-08 04:27:16 +08:00
kosta 0560f2615b fix(state-sync): mirror WebUI API call counts 2026-07-07 19:48:19 +00:00
kosta 059a06b70d fix(state-sync): mirror WebUI cache counters 2026-07-07 19:48:19 +00:00
Frank Song 76478729b8 fix(wakeup): record pauses from turn route lane 2026-07-08 01:57:46 +08:00
Frank Song 5764df66f3 fix(wakeup): share process pause lane resolution 2026-07-07 17:00:36 +08:00
Frank Song 1ab9c47e9b fix(wakeup): canonicalize paused route model lanes 2026-07-07 15:09:30 +08:00
Frank Song 09862a3f9a fix(wakeup): stabilize credential pause revalidation 2026-07-07 14:08:25 +08:00
Frank Song 2c5035d6e7 fix(gateway): recheck cancel before wakeup pause clear 2026-07-07 13:21:36 +08:00
Frank Song 3acd588621 fix(gateway): preserve wakeup pause on late cancel 2026-07-07 13:15:54 +08:00
Frank Song a61381ee7e fix(wakeup): key stale pause to failed lane 2026-07-07 13:08:27 +08:00
Frank Song 46e788797e Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-07 13:01:42 +08:00
Swan-Sandnesquena-hermes ccd62f9b9d fix(streaming): drop empty tool_calls arrays before API send (#5737)
Strict providers (DeepSeek v4, newer OpenAI) reject an assistant message
carrying tool_calls: [] with HTTP 400. _sanitize_messages_for_api already
strips orphaned tool results, but not an assistant message that literally
stores an empty tool_calls list.

Drop the empty key on ALL THREE sanitized-dict sites that must agree:
_sanitize_messages_for_api, _api_safe_message_positions, and the
_safe_projection inside _restore_reasoning_metadata (so a tool_calls: [] row
projects identically on both sides of the metadata-carry-forward alignment and
doesn't lose its reasoning/id/timestamp every turn — Fable gate follow-up,
folded in).

Added mutation-checked regression tests: empty tool_calls dropped + populated
chain preserved (both sanitizer paths), and metadata carry-forward survives a
tool_calls: [] row.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-07-07 04:43:46 +00:00
Frank Song 1424917c1d fix(streaming): keep checkpoint guard comment adjacent 2026-07-07 12:42:22 +08:00
Frank Song 8e2a56e563 fix(streaming): keep checkpoint guard adjacent to try 2026-07-07 12:40:07 +08:00
Frank Song b25e99c1b4 fix(wakeup): revalidate credential pause on refresh 2026-07-07 12:34:41 +08:00
Frank Song 477f567a19 fix(wakeup): preserve pause for stale credential streams 2026-07-07 12:29:32 +08:00
Frank Song 524353b62d fix(wakeup): keep pause on late cancel 2026-07-07 12:09:16 +08:00
Frank Song cfd1169126 fix(wakeup): preserve pause through cancelled turns 2026-07-07 11:55:48 +08:00