fix: allow multiline commands

This commit is contained in:
2026-07-19 23:37:45 +02:00
parent 5b2b09b000
commit 14737db853
3 changed files with 61 additions and 10 deletions
+1 -2
View File
@@ -4,7 +4,7 @@ import re
from agentci.domain.models import CommandName, JobKind, ParsedCommand
COMMAND_RE = re.compile(r"^/agent[ \t]+([a-z]+)(?:[ \t]+([\s\S]*))?$")
COMMAND_RE = re.compile(r"^/agent[ \t]+([a-z]+)(?:[ \t\r\n]+([\s\S]*))?$")
class CommandError(ValueError):
@@ -46,4 +46,3 @@ def resolve_job_kind(command: ParsedCommand, *, is_pull_request: bool) -> JobKin
if command.name not in mapping:
raise CommandError(f"`/agent {command.name}` can only be used on a pull request.")
return mapping[command.name]
+39 -8
View File
@@ -8,12 +8,31 @@ def test_ignores_non_commands() -> None:
assert parse_command("please run /agent plan") is None
def test_parses_multiline_message() -> None:
command = parse_command("/agent implement focus on API\nand add tests")
@pytest.mark.parametrize("name", list(CommandName))
@pytest.mark.parametrize("line_breaks", [1, 2, 5])
def test_all_commands_accept_messages_after_any_number_of_lines(
name: CommandName, line_breaks: int
) -> None:
command = parse_command(
f"/agent {name.value}{'\n' * line_breaks}"
"focus on the API\nand add tests"
)
assert command is not None
assert command.name is CommandName.IMPLEMENT
assert command.message == "focus on API\nand add tests"
assert resolve_job_kind(command, is_pull_request=False) is JobKind.IMPLEMENT
assert command.name is name
assert command.message == "focus on the API\nand add tests"
@pytest.mark.parametrize("name", list(CommandName))
def test_all_commands_accept_crlf_separated_multiline_messages(
name: CommandName,
) -> None:
command = parse_command(
f"/agent {name.value}\r\n\r\n\r\n"
"focus on the API\r\nand add tests"
)
assert command is not None
assert command.name is name
assert command.message == "focus on the API\r\nand add tests"
def test_discuss_requires_message() -> None:
@@ -28,9 +47,21 @@ def test_rejects_wrong_location() -> None:
resolve_job_kind(command, is_pull_request=False)
def test_resolves_iterate_by_location() -> None:
command = parse_command("/agent iterate refine tests")
@pytest.mark.parametrize(
("body", "message"),
[
("/agent iterate", ""),
("/agent iterate refine tests", "refine tests"),
(
"/agent iterate\n\nkeep the API stable\nlimit changes to the parser",
"keep the API stable\nlimit changes to the parser",
),
],
)
def test_iterate_accepts_optional_message(body: str, message: str) -> None:
command = parse_command(body)
assert command is not None
assert command.name is CommandName.ITERATE
assert command.message == message
assert resolve_job_kind(command, is_pull_request=False) is JobKind.ITERATE_PLAN
assert resolve_job_kind(command, is_pull_request=True) is JobKind.ITERATE_IMPLEMENT
+21
View File
@@ -72,6 +72,27 @@ async def test_authorized_command_is_queued() -> None:
assert "queued" in gitea.comments[0]
async def test_iterate_message_is_preserved_on_queued_job() -> None:
storage = FakeStorage()
container = SimpleNamespace(storage=storage, gitea=FakeGitea())
event = _event_from_payload(
"delivery",
payload(
"/agent iterate\n\nkeep the API stable\nlimit changes to the parser",
is_pull=True,
),
)
assert event is not None
response = await _handle_command(container, event)
assert response.status_code == 202
assert len(storage.jobs) == 1
assert storage.jobs[0].message == (
"keep the API stable\nlimit changes to the parser"
)
async def test_non_command_is_ignored() -> None:
container = SimpleNamespace(storage=FakeStorage(), gitea=FakeGitea())
event = _event_from_payload("delivery", payload("ordinary discussion"))