master
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8686528b1b |
Release batch A: 6 low-risk gate-passed fixes (experimental) (#7089)
* fix(goal): suppress reserved SILENT sentinel at /api/goal ingress A wake relay POSTing the exact [SILENT] suppression sentinel to /api/goal lets it reach _start_chat_stream_for_session, which persists it as pending_user_message. If 8701 restarts while the turn is pending, session recovery materializes it as a visible _recovered user turn — the same phantom-recovered-turn exposure #7018 closed for chat ingress. Apply the shared _is_silent_control_message() guard immediately after /api/goal session-ID validation and before session lookup or goal-state mutation, returning the same 200 no-op. Matching stays exact and case-sensitive; ordinary kickoff text is unaffected. Add tests mirroring test_silent_control_suppression.py for the goal path (args and text fields, before-lookup suppression, exact-match semantics). Closes #7019 * fix(sessions): keep evicted subagent parents in the import window The sidebar nests a subagent row under its parent only when the parent row is present in the same payload. The visible-window limit was applied as a flat per-row recency slice, so a frozen orchestrator (which stops writing while its leaves keep streaming) lost the recency race against its own leaves and fell outside the window -- promoting those leaves to top-level sidebar rows. Re-add subagent parents that the oversampled candidate set already projected, after the slice. No extra queries, no change to CLI_VISIBLE_SESSION_LIMIT. webui ancestors are deliberately not imported because that sidebar bucket already owns them. Supersedes #7031. * fix(sessions): document and pin the parent-recovery bound (greptile review) Greptile flagged (P1) that a selected subagent child whose parent ranks below the limit * 8 oversample is still promoted to a top-level row. That is real and measured (the parent drops out at candidate #25 of 24), but it is the bound of the design, not a regression: the walk reuses rows the projection already fetched and never issues an extra query. Resolving arbitrarily old ancestors needs an unbounded per-row lookup on the hot sidebar path -- the approach rejected in #7031 -- so the bound is documented and pinned instead. - Docstring: state that `limit` bounds the recency slice, not the row count, so callers must iterate rather than assume len(rows) <= limit; state that recovery is bounded by the oversampled candidate set. - Inline comment: mark the bound at the exact lines Greptile flagged and point at `candidate_limit` as the knob if the window proves too tight. - Tests: cover the candidate-window exhaustion Greptile said was untested -- parent inside the oversample is recovered, parent beyond it stays unresolved -- plus the over-limit return contract and a parent-cycle guard. No behaviour change; 7 tests pass. * fix(share): add _hadAppearance guard to prevent fabricated appearance choice Summary: The inline appearance bootstrap in static/share.html writes hermes-theme and hermes-skin to localStorage unconditionally on every page load, even when the browser had no prior appearance state. This fabricates an explicit user choice on first access via a shared link, making the server-side SETTINGS_DEFAULTS unreachable for deployments that customise the default theme or skin. Root Cause: share.html:9 — the boot IIFE resolves a theme+skin and calls localStorage.setItem() without guarding on whether the user had previously chosen an appearance. The same bug was fixed in index.html by PR #6808 (commit tomtong2015) but share.html was left unchanged. Change: 1. Added _hadAppearance guard before the two localStorage.setItem() calls: var _hadAppearance = localStorage.getItem('hermes-theme') !== null || localStorage.getItem('hermes-skin') !== null; if (_hadAppearance) { setItem('hermes-theme', t); setItem('hermes-skin', s); } 2. The first-paint DOM mutations (classList.add('dark'), dataset.skin) remain outside the guard — only persistence is protected. 3. Synced the skin allowlist with index.html: added neon-soft and neon-paint (zeus and verdigris were already present). Verification: - test_6808_appearance_bootstrap_no_fabricated_choice.py: 11/11 passed covering fresh-browser (no writes), pre-paint fallback, explicit state normalisation, and legacy migration survival. Closes #7030 * fix(tests): pin rebuild budget in issue2513 custom-provider catalog test test (3.13, 4) failed on this PR with: assert "@custom:alpha-proxy:alpha/remote" in alpha_ids E AssertionError: assert '@custom:alpha-proxy:alpha/remote' in {'alpha/sticky'} WARNING api.config:config.py:8355 live provider-catalog rebuild exceeded 4.0s budget - serving fallback, refreshing catalog out-of-band Pre-existing wall-clock flake, not a regression from this PR: this branch touches only api/agent_sessions.py and tests/test_subagent_parent_in_import_ window.py, and both api/config.py and this test file are byte-identical to origin/master. The same shard passed on 3.11 and 3.12. The test never pinned _LIVE_REBUILD_BUDGET_SECONDS, so it raced the global 4s budget in get_available_models(). On a starved runner the cold rebuild overruns, the degraded fallback catalog is served, and the monkeypatched- urlopen model alpha/remote is dropped - leaving only the config-declared sticky model, exactly as CI observed. Force the synchronous (unbounded) rebuild path, matching the existing precedent in tests/test_issue2540_models_endpoint_error.py:20-24. Verified: with the budget forced to 0.001s the unpatched test reproduces the CI assertion verbatim; patched it passes at 0.001s, 0, and default. * fix(#7013): preserve media deny coverage across platforms * fix(docker): keep repository agents out of runtime context * fix(docker): gate image runtime proof behind integration job * test(models): stabilize custom provider catalog regression * Release batch A: 6 low-risk gate-passed fixes (experimental) Batched contributor fixes, each individually Codex-gated during the overnight certifier cycles and re-verified clean-to-ship as a combined stage (Codex SAFE TO SHIP on the combined diff; full suite green except 8 pre-existing approval tests that fail identically on clean origin/master — CI green on same commit). - #7019 (@webtecnica) suppress reserved [SILENT] sentinel at /api/goal - #7031 (@carlotestor) keep evicted subagent parents in the sidebar import window - #7030 (@webtecnica) share.html first-visit appearance guard + skin-id sync - #6853 (@rodboev) exclude repo-root AGENTS.md from the Docker runtime image - #7013 (@webtecnica) test-only: platform-neutral test portability (playwright import guard + media tests served from allowed roots) - test-only (@carlotestor) pin custom-provider catalog rebuild budget (#7054) Co-authored-by: webtecnica <webtecnica@users.noreply.github.com> Co-authored-by: carlotestor <carlotestor@users.noreply.github.com> Co-authored-by: rodboev <rodboev@users.noreply.github.com> --------- Co-authored-by: webtecnica <webtecnica@gmail.com> Co-authored-by: carlotestor <carlotestor@users.noreply.github.com> Co-authored-by: Rod Boev <rod.boev@gmail.com> Co-authored-by: n <a@n> Co-authored-by: webtecnica <webtecnica@users.noreply.github.com> Co-authored-by: rodboev <rodboev@users.noreply.github.com> |
||
|
|
49437e20ab |
fix: phantom 'Compressing context' barrier when compressed SSE is lost/delayed (#6572)
* fix: clear stale compression barrier on turn-done when compressed SSE is lost When a turn completes (done SSE event) but the "compressed" SSE event was lost or delayed, the compression UI state remained in phase="running". The done handler unconditionally rebound the stale state sessionId to the continuation session, surfacing a phantom "Compressing context" barrier on a session that never actually compressed. Fix: when the done event session_id differs from the active session AND the compression UI is still in "running" phase, clear it via clearCompressionUi() instead of rebinding. Non-running states (e.g. "done") are still rebound as before. Added regression tests verifying: - stale running state is cleared (not rebound) on session change - non-running states are still rebound - the old unconditional rebind is gone * fix: also clear phantom compression on same-session done (A->A case) Reviewer feedback on PR #6572: the previous fix only cleared stale running compression when the session ID changed (A->B). But the backend also supports compression without session rotation (A->A), where the compressed SSE being lost leaves the same phantom barrier. Fix: in the done handler, clear any owned automatic compression state that is still in "running" phase, regardless of whether the session ID changed. The condition is now phase==='running' alone, not session_id change AND running. Added behavioral state-transition tests for all four cases: - running A->B (rotation, compressed lost): cleared - running A->A (no rotation, compressed lost): cleared - non-running A->B (rotation, compressed seen): rebound - non-running A->A (no rotation, compressed seen): rebound * test: real SSE regression for compression phantom barrier Rewrite test_compression_phantom_barrier.py to use real browser + SSE instead of handwritten Python simulation. The new tests: - Boot real WebUI server with isolated state - Use deterministic mock gateway (Runs API mode) - Verify done handler clears stale running compression state - Cover both A→B (rotation) and A→A (no rotation) cases Key insight: /v1/capabilities must return run_approval_response=true to enable the Runs API path in gateway_chat.py. Fixes reviewer feedback: simulation never called attachLiveStream, clearCompressionUi, or registered done listeners. * test: replace Playwright test with hermetic source-grep suite for compression phantom barrier The previous Playwright-based test failed CI in no-egress sandboxes due to external resource errors (net::ERR_INTERNET_DISCONNECTED) and was structurally unsound: the mock window.S shadowed the lexical S, so the compressing handler short-circuited before the barrier could be established, producing a false- positive assertion. New suite uses static source analysis (same convention as test_issue840, test_issue3040, test_bg_task_complete_focus_drop): - No browser, no network, no playwright dependency - Proves the done handler clears phase:'running' without gating on session ID change (A->A case coverage) - Asserts gating conditions: automatic && owner && done session exists - Negative controls: non-owner / manual compression are ignored - Source-level regression proof: reverting the production hunk breaks tests 13 tests. All pass in <3s on bare Python. * test: exercise compression cleanup through real SSE handlers Replace source-grep assertions with a hermetic Playwright harness that loads the shipping classic scripts and dispatches MockEventSource events through attachLiveStream. Cover A-to-A/A-to-B cleanup, non-running rebind, ownership controls, canonical cleanup side effects, and a production-branch mutation proof. --------- Co-authored-by: happy5318 <happy5318@users.noreply.github.com> Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com> |