Merge pull request #4426 from nesquena/release/stage-4417

Release v0.51.493 — RC (ignore malformed background-process wakeup events, #4417)
This commit is contained in:
nesquena-hermes
2026-06-18 11:12:06 -07:00
committed by GitHub
3 changed files with 107 additions and 5 deletions
+6
View File
@@ -3,6 +3,12 @@
## [Unreleased]
## [v0.51.493] — 2026-06-18 — Release RC (ignore malformed background-process wakeup events)
### Fixed
- **Malformed background-process wakeup events no longer appear as empty `unknown completed` prompts.** WebUI now ignores empty or unknown completion-queue payloads and renders watch-pattern overflow summaries as explicit watch notifications instead of fake process completions. Thanks @ai-ag2026.
## [v0.51.492] — 2026-06-18 — Release RB (large context window preserved on session reload)
### Fixed
+23 -5
View File
@@ -350,17 +350,28 @@ def _truncate(text: str, limit: int) -> str:
return s[:limit] + "\n…(truncated)"
def format_wakeup_prompt(evt: dict) -> str:
def format_wakeup_prompt(evt: object) -> str | None:
"""Build the synthetic [IMPORTANT: …] message the agent will see.
Mirrors ``cli._format_process_notification`` so wakeup payloads look the
same in CLI and WebUI sessions.
"""
if not isinstance(evt, dict) or not evt:
return None
evt_type = evt.get("type", "completion")
sid = evt.get("session_id", "unknown")
cmd = evt.get("command", "unknown")
sid = str(evt.get("session_id") or "").strip()
cmd = str(evt.get("command") or "").strip()
# The current server-side wakeup drain drops global watch-overflow events
# before this formatter because they intentionally carry no session_key.
# Keep this branch defensive so any future routable overflow summary is not
# mis-rendered as a fake process completion.
if evt_type in {"watch_overflow_tripped", "watch_overflow_released"}:
msg = str(evt.get("message") or "").strip()
return f"[IMPORTANT: {msg}]" if msg else None
if evt_type == "watch_disabled":
return f"[IMPORTANT: {evt.get('message', '')}]"
msg = str(evt.get("message") or "").strip()
return f"[IMPORTANT: {msg}]" if msg else None
if evt_type == "watch_match":
pat = evt.get("pattern", "?")
out = _truncate(evt.get("output", ""), 4000)
@@ -373,6 +384,12 @@ def format_wakeup_prompt(evt: dict) -> str:
if sup:
body += f"\n({sup} earlier matches were suppressed by rate limit)"
return body + "]"
if evt_type != "completion":
return None
if not (sid or cmd or "exit_code" in evt or evt.get("output")):
return None
# Default: completion event
exit_code = evt.get("exit_code", "?")
out = _truncate(evt.get("output", ""), 4000)
@@ -880,7 +897,8 @@ def _process_one(evt: dict) -> None:
# `{session_id, task_id, completed_at, summary?, event_id}`, so
# we derive the prompt directly from the evt here (same source the
# prior _build_payload used).
wakeup_prompt = format_wakeup_prompt(evt).strip()
wakeup_prompt_raw = format_wakeup_prompt(evt)
wakeup_prompt = wakeup_prompt_raw.strip() if wakeup_prompt_raw else ""
if wakeup_prompt:
if _session_has_active_turn(session_id):
# Defer-path fix: persist the prompt so a turn-teardown
@@ -0,0 +1,78 @@
from api.background_process import format_wakeup_prompt
def test_format_wakeup_prompt_skips_empty_event():
assert format_wakeup_prompt({}) is None
def test_format_wakeup_prompt_skips_non_dict_event():
assert format_wakeup_prompt(None) is None
assert format_wakeup_prompt(42) is None
def test_format_wakeup_prompt_skips_unknown_event_type():
evt = {"type": "unknown_event", "message": "do not inject me"}
assert format_wakeup_prompt(evt) is None
def test_format_wakeup_prompt_handles_watch_disabled():
evt = {
"type": "watch_disabled",
"session_id": "proc_abc",
"session_key": "webui-session",
"command": "tail -f log",
"message": "Watch patterns disabled for process proc_abc.",
}
assert (
format_wakeup_prompt(evt)
== "[IMPORTANT: Watch patterns disabled for process proc_abc.]"
)
def test_format_wakeup_prompt_skips_blank_watch_disabled():
assert format_wakeup_prompt({"type": "watch_disabled"}) is None
assert format_wakeup_prompt({"type": "watch_disabled", "message": ""}) is None
def test_format_wakeup_prompt_handles_watch_overflow_tripped():
evt = {
"type": "watch_overflow_tripped",
"session_id": "",
"session_key": "",
"command": "",
"message": "Watch-pattern overflow: suppressing further watch_match events.",
}
assert (
format_wakeup_prompt(evt)
== "[IMPORTANT: Watch-pattern overflow: suppressing further watch_match events.]"
)
def test_format_wakeup_prompt_handles_watch_overflow_released():
evt = {
"type": "watch_overflow_released",
"session_id": "",
"session_key": "",
"command": "",
"suppressed": 3,
"message": "Watch-pattern notifications resumed. 3 match event(s) were suppressed.",
}
assert (
format_wakeup_prompt(evt)
== "[IMPORTANT: Watch-pattern notifications resumed. 3 match event(s) were suppressed.]"
)
def test_format_wakeup_prompt_keeps_normal_completion():
evt = {
"type": "completion",
"session_id": "proc_abc",
"command": "sleep 1",
"exit_code": 0,
"output": "done",
}
result = format_wakeup_prompt(evt)
assert result is not None
assert "Background process proc_abc completed" in result
assert "Command: sleep 1" in result
assert "Output:\ndone" in result