diff --git a/tests/tools/test_process_heartbeat.py b/tests/tools/test_process_heartbeat.py index 0e997b9194..62fecb3303 100644 --- a/tests/tools/test_process_heartbeat.py +++ b/tests/tools/test_process_heartbeat.py @@ -63,6 +63,34 @@ def test_heartbeat_carries_only_new_output_and_stops_at_exit(tmp_path, monkeypat timeout=2.5) +def test_schema_minimum_heartbeat_is_disabled_for_foreground(monkeypatch): + from tools import terminal_tool as tt + + captured = {} + + def fake_terminal_tool(**kwargs): + captured.update(kwargs) + return json.dumps({"output": "Background process started", "session_id": "proc_x", "exit_code": 0}) + + monkeypatch.setattr(tt, "terminal_tool", fake_terminal_tool) + heartbeat_schema = tt.TERMINAL_SCHEMA["parameters"]["properties"]["heartbeat"] + generated = { + "command": "pwd", + "background": False, + "timeout": 20, + "pty": False, + "notify": False, + "heartbeat": heartbeat_schema["minimum"], + } + + result = json.loads(tt._handle_terminal(generated)) + + assert not result.get("error") + assert captured["background"] is False + assert captured["heartbeat"] == 0 + assert captured["notify_on_complete"] is False + + def test_terminal_dispatch_heartbeat_implies_notify_and_refuses_foreground(monkeypatch): from tools import terminal_tool as tt diff --git a/tools/terminal_tool.py b/tools/terminal_tool.py index dbf5fc604b..bd3029cd3d 100644 --- a/tools/terminal_tool.py +++ b/tools/terminal_tool.py @@ -1509,8 +1509,9 @@ TERMINAL_SCHEMA = { }, "heartbeat": { "type": "integer", - "minimum": 60, - "description": "With background=true: also notify every N seconds (min 60) with the output since the last notice. For long jobs you must react to mid-run (merge trains, full suites); implies notify=true." + "minimum": 0, + "default": 0, + "description": "0 disables. With background=true: also notify every N seconds (positive values are clamped to min 60) with the output produced since the last notice; a tick with no new output is skipped. For bounded jobs you must react to mid-run (merge trains, full suites, deploys) — never for servers or watchers; implies notify=true." }, "persist_on_release": { "type": "boolean",