From f8d67fdfd10500864a5393b727ff6fd3de0d0137 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Mon, 24 Aug 2026 01:04:59 -0700 Subject: [PATCH] [eric] release: 1.7.9 ships with a story, and two log assertions stop reading the root logger Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018foyDoK19jjbYdudfzQVkZ --- backend/apps/help/changelog.py | 22 ++++++++++++ backend/tests/test_fault_injection.py | 43 ++++++++++++++++++++---- backend/tests/test_tool_output_shaper.py | 7 ++-- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/backend/apps/help/changelog.py b/backend/apps/help/changelog.py index ca036ab4..993c1fca 100644 --- a/backend/apps/help/changelog.py +++ b/backend/apps/help/changelog.py @@ -21,6 +21,28 @@ P_RELEASES: List[ReleaseNote] = [ # Only lines that are true of the built app belong here. This one file feeds the in-app card, the # GitHub body AND the Help agent's context, so a line written for a planned feature becomes the # agent confidently describing something that does not exist. + ReleaseNote( + version="1.7.9", + headline="Agents keep going when the connection does not, and the fn key finally works.", + highlights=[ + "Press fn to dictate on any Mac. It asks for permission the first time you use it rather than at install, and it recovers without a relaunch if you grant it later.", + "Drop files straight onto a chat. Anything the app cannot take now says so instead of silently doing nothing.", + "The transcript follows a running agent until you scroll away, and re-pins itself the moment you scroll back.", + "Agents can move, tile, collapse and close their own cards, so a busy canvas tidies itself.", + ], + fixes=[ + "A dropped connection no longer costs you the conversation. The agent reconnects and carries on from where it was.", + "Pressing Stop stops it. A background watchdog can no longer restart a chat you just ended.", + "Chats stopped disconnecting mid task. A slow web page was being mistaken for a jammed tool and shot, roughly ten times a week per install.", + "An agent that runs out of things to say gets asked once for its answer instead of leaving you looking at a Done label with nothing under it.", + "Signing in on a fresh install works. The hand off back from your browser was landing on the wrong port.", + "An expired provider login renews itself mid run instead of stopping the turn and asking you to reconnect.", + "A browser agent can no longer report a task complete when the page never changed.", + "Turning a schedule on or off tells you the truth. A failed save used to leave the switch showing the wrong state.", + "Card resizing ends when you let go, even over an embedded browser.", + "Dictation goes to the chat you were typing in, not a brand new one.", + ], + ), ReleaseNote( version="1.7.8", headline="A stuck chat unsticks itself, and agents can finally upload files.", diff --git a/backend/tests/test_fault_injection.py b/backend/tests/test_fault_injection.py index 861bf366..fed6f95d 100644 --- a/backend/tests/test_fault_injection.py +++ b/backend/tests/test_fault_injection.py @@ -19,6 +19,38 @@ from backend.apps.agents.core.error_classify import ( ) TURN_RUNNER = "backend/apps/agents/manager/run/TurnRunner.py" + +class p_capture: + """Capture a specific logger directly. + + `caplog` reads the ROOT logger, so it goes blind the moment any other test in the run sets + propagate=False on ours: these two assertions passed alone and failed in the full suite while + the line was plainly there in captured stderr. A logging assertion must not depend on what + 3,000 other tests did to the logging tree. + """ + + def __init__(self, name): + import logging + self.logger = logging.getLogger(name) + self.records = [] + self.handler = logging.Handler() + self.handler.emit = lambda r: self.records.append(r) + + def __enter__(self): + import logging + self.prev = self.logger.level + self.logger.setLevel(logging.WARNING) + self.logger.addHandler(self.handler) + return self + + def __exit__(self, *a): + self.logger.removeHandler(self.handler) + self.logger.setLevel(self.prev) + + @property + def text(self): + return "\n".join(r.getMessage() for r in self.records) + # Where each fault is raised. A fault this build knows but wires nowhere is the exact shape of a # guard that never fires, so the map is the test, not a convenience. WIRED_IN = { @@ -109,18 +141,17 @@ def test_every_known_fault_is_wired_somewhere(): assert p_block(kind).strip(), f"{kind} has an empty branch" -def test_arming_is_announced_and_a_typo_is_named(monkeypatch, caplog): +def test_arming_is_announced_and_a_typo_is_named(monkeypatch): # The harness built to kill row-6 silence had it: unknown_faults() existed and NOTHING called it, # so a mistyped name armed nothing while the drill exercised the untouched happy path. monkeypatch.setenv("OSW_FAULT", "policy_block,plicy_blok") - with caplog.at_level("WARNING"): + with p_capture("backend.apps.agents.core.fault_injection") as cap: announce() - assert "policy_block" in caplog.text and "plicy_blok" in caplog.text - caplog.clear() + assert "policy_block" in cap.text and "plicy_blok" in cap.text monkeypatch.delenv("OSW_FAULT") - with caplog.at_level("WARNING"): + with p_capture("backend.apps.agents.core.fault_injection") as cap2: announce() - assert caplog.text == "", "a shipped build must say nothing at all" + assert cap2.text == "", "a shipped build must say nothing at all" def test_the_boot_path_actually_announces(): diff --git a/backend/tests/test_tool_output_shaper.py b/backend/tests/test_tool_output_shaper.py index eb330c4c..bd2ee157 100644 --- a/backend/tests/test_tool_output_shaper.py +++ b/backend/tests/test_tool_output_shaper.py @@ -118,12 +118,13 @@ def test_the_hook_shapes_the_pristine_response_not_the_flattened_one(): assert i_capture < i_use -def test_the_off_switch_is_declared_and_announces_itself(monkeypatch, caplog): +def test_the_off_switch_is_declared_and_announces_itself(monkeypatch): from backend.apps.agents.manager.streaming import tool_output_shaper as mod + from backend.tests.test_fault_injection import p_capture class S: id = "s1" monkeypatch.setenv("OSW_TOOL_SHAPING", "off") - with caplog.at_level("WARNING"): + with p_capture("backend.apps.agents.manager.streaming.tool_output_shaper") as cap: assert mod.shape_for_model(S(), "s1", {"stdout": p_big()}, "m1", "Bash") is None - assert "OFF" in caplog.text, "a guard that stops guarding must say which sessions it stopped protecting" + assert "OFF" in cap.text, "a guard that stops guarding must say which sessions it stopped protecting"