feat: Enhance local command handling and introduce local intents
- Refactor `run_local_command` to manage subprocesses more effectively, ensuring child processes are terminated on timeout. - Introduce `_terminate` function to handle process group termination and capture output. - Implement `_command_output` to format command results with a character limit. - Add local intent recognition in `intents.py` to handle commands like "stop", "go to sleep", and "come here" without server interaction. - Normalize user input to match local intents while stripping filler words. - Update tests to cover new local intent functionality and ensure proper command handling. - Enhance speech processing to handle abbreviations and improve spoken output clarity.
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@@ -142,3 +144,52 @@ def test_download_outbox_file_raises_server_error_on_http_failure():
|
||||
get.return_value = _mock_response({}, ok=False)
|
||||
with pytest.raises(server_client.ServerError, match="abc"):
|
||||
server_client.download_outbox_file("abc")
|
||||
|
||||
|
||||
def test_converse_reports_a_relay_that_never_produced_a_reply():
|
||||
"""Hitting the hop cap used to surface as "unknown server response", which
|
||||
sent everyone looking at the payload shape instead of at a model that kept
|
||||
calling tools and never answered."""
|
||||
command = {"type": "command", "command": "echo hi", "token": "t"}
|
||||
with patch.object(server_client.requests, "post") as post:
|
||||
post.return_value = _mock_response(command)
|
||||
with pytest.raises(server_client.ServerError, match="hop cap"):
|
||||
server_client.converse("hi", on_command=lambda cmd: "ok")
|
||||
|
||||
|
||||
# ── relayed shell commands ──────────────────────────────────────────────────
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_sudo_prompt(monkeypatch):
|
||||
monkeypatch.setattr(server_client.config, "SUDO_ASKPASS_PROMPT", False)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="POSIX shell and process groups")
|
||||
def test_a_successful_command_returns_its_output_and_exit_code():
|
||||
output = server_client.run_local_command("echo hello; exit 3")
|
||||
assert output.startswith("[exit 3]")
|
||||
assert "hello" in output
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="POSIX shell and process groups")
|
||||
def test_a_timed_out_command_still_reports_what_it_printed():
|
||||
"""A bare "timed out" tells the model nothing; the last line of output
|
||||
usually says exactly what it was stuck waiting for."""
|
||||
output = server_client.run_local_command("echo working on it; sleep 30", timeout=1)
|
||||
assert "timed out after 1s" in output
|
||||
assert "working on it" in output
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="POSIX shell and process groups")
|
||||
def test_a_timed_out_command_takes_its_children_with_it(tmp_path):
|
||||
"""subprocess.run() would only kill the `sh`, leaving whatever it spawned
|
||||
running for the rest of the session with no parent watching."""
|
||||
marker = tmp_path / "ticks"
|
||||
server_client.run_local_command(
|
||||
f"(while true; do echo tick >> {marker}; sleep 0.05; done) & sleep 30",
|
||||
timeout=1,
|
||||
)
|
||||
settled = marker.stat().st_size if marker.exists() else 0
|
||||
time.sleep(0.4)
|
||||
grew = (marker.stat().st_size if marker.exists() else 0) - settled
|
||||
assert grew == 0, "a grandchild survived the timeout and is still writing"
|
||||
|
||||
Reference in New Issue
Block a user