feat: issue-core task intake poll/claim/run (HARNESS-WP-0001-T03)
Add intake client mapping emissions to TaskSpec, CLI poll and run --from-issue-core, keep --task-file for local dev. Completes HARNESS-WP-0001 workplan.
This commit is contained in:
parent
154e535695
commit
66ccd4fa01
7 changed files with 705 additions and 34 deletions
204
tests/test_intake.py
Normal file
204
tests/test_intake.py
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from agent_harness.intake import (
|
||||
EmittedIssue,
|
||||
IntakeConfig,
|
||||
IssueCoreClient,
|
||||
infer_agent_and_event,
|
||||
issue_to_taskspec,
|
||||
poll_next,
|
||||
resolve_target_repo,
|
||||
)
|
||||
from agent_harness.taskspec import TaskSpecError
|
||||
|
||||
|
||||
def _make_repo(tmp_path: Path, name: str = "binky-control") -> Path:
|
||||
repo = tmp_path / name
|
||||
repo.mkdir()
|
||||
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
|
||||
(repo / "README.md").write_text("repo\n")
|
||||
subprocess.run(["git", "add", "."], cwd=repo, check=True)
|
||||
subprocess.run(
|
||||
["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "init"],
|
||||
cwd=repo,
|
||||
check=True,
|
||||
)
|
||||
return repo
|
||||
|
||||
|
||||
def test_resolve_target_repo_by_slug(tmp_path: Path) -> None:
|
||||
repo = _make_repo(tmp_path, "binky-control")
|
||||
found = resolve_target_repo(
|
||||
"binky-control",
|
||||
repo_roots=(str(tmp_path),),
|
||||
)
|
||||
assert found == repo.resolve()
|
||||
|
||||
|
||||
def test_resolve_target_repo_map(tmp_path: Path) -> None:
|
||||
repo = _make_repo(tmp_path, "sandbox")
|
||||
found = resolve_target_repo(
|
||||
"coulomb/other-name",
|
||||
repo_map={"other-name": str(repo)},
|
||||
)
|
||||
assert found == repo.resolve()
|
||||
|
||||
|
||||
def test_infer_agent_from_definition() -> None:
|
||||
issue = EmittedIssue(
|
||||
issue_id="1",
|
||||
title="x",
|
||||
description="y",
|
||||
labels=["binky", "automated"],
|
||||
activity_definition_id="binky-weekly-mail-intake",
|
||||
)
|
||||
agent, event = infer_agent_and_event(issue)
|
||||
assert agent == "mail-triage"
|
||||
assert event == "binky_mail_intake"
|
||||
|
||||
|
||||
def test_infer_agent_from_labels() -> None:
|
||||
issue = EmittedIssue(
|
||||
issue_id="1",
|
||||
title="x",
|
||||
description="y",
|
||||
labels=["rhythm", "automated"],
|
||||
)
|
||||
agent, event = infer_agent_and_event(issue)
|
||||
assert agent == "coach"
|
||||
assert event == "binky_daily_brief"
|
||||
|
||||
|
||||
def test_issue_to_taskspec(tmp_path: Path) -> None:
|
||||
repo = _make_repo(tmp_path)
|
||||
issue = EmittedIssue(
|
||||
issue_id="abc",
|
||||
title="Run daily",
|
||||
description="do the rhythm",
|
||||
labels=["binky", "rhythm", "automated"],
|
||||
target_repo="binky-control",
|
||||
activity_definition_id="binky-daily-rhythm",
|
||||
)
|
||||
cfg = IntakeConfig(repo_roots=(str(tmp_path),))
|
||||
spec = issue_to_taskspec(issue, cfg)
|
||||
assert spec.title == "Run daily"
|
||||
assert spec.agent == "coach"
|
||||
assert spec.completion_event_type == "binky_daily_brief"
|
||||
assert spec.target_repo == repo.resolve()
|
||||
|
||||
|
||||
def test_issue_to_taskspec_missing_repo_raises(tmp_path: Path) -> None:
|
||||
issue = EmittedIssue(
|
||||
issue_id="1",
|
||||
title="x",
|
||||
description="y",
|
||||
target_repo="no-such-repo",
|
||||
)
|
||||
with pytest.raises(TaskSpecError):
|
||||
issue_to_taskspec(issue, IntakeConfig(repo_roots=(str(tmp_path),)))
|
||||
|
||||
|
||||
def test_client_list_claim_close() -> None:
|
||||
cfg = IntakeConfig(
|
||||
base_url="http://issue-core.test",
|
||||
api_key="k",
|
||||
require_labels=("automated",),
|
||||
assignee="agent-harness",
|
||||
)
|
||||
client = IssueCoreClient(cfg)
|
||||
|
||||
list_body = [
|
||||
{
|
||||
"issue_id": "id-1",
|
||||
"number": 3,
|
||||
"title": "Run Binky daily rhythm",
|
||||
"description": "hygiene + brief",
|
||||
"state": "open",
|
||||
"labels": ["binky", "rhythm", "automated"],
|
||||
"target_repo": "binky-control",
|
||||
"activity_definition_id": "binky-daily-rhythm",
|
||||
}
|
||||
]
|
||||
claim_body = {**list_body[0], "state": "in_progress", "assignee": "agent-harness"}
|
||||
close_body = {**claim_body, "state": "closed"}
|
||||
|
||||
with patch("agent_harness.intake.httpx.get") as get_mock, patch(
|
||||
"agent_harness.intake.httpx.patch"
|
||||
) as patch_mock:
|
||||
get_resp = MagicMock()
|
||||
get_resp.raise_for_status = MagicMock()
|
||||
get_resp.json.return_value = list_body
|
||||
get_mock.return_value = get_resp
|
||||
|
||||
issues = client.list_open()
|
||||
assert len(issues) == 1
|
||||
assert issues[0].issue_id == "id-1"
|
||||
get_mock.assert_called_once()
|
||||
params = get_mock.call_args.kwargs.get("params") or get_mock.call_args[1].get(
|
||||
"params"
|
||||
)
|
||||
assert ("label", "automated") in params
|
||||
|
||||
patch_resp = MagicMock()
|
||||
patch_resp.raise_for_status = MagicMock()
|
||||
patch_resp.json.return_value = claim_body
|
||||
patch_mock.return_value = patch_resp
|
||||
claimed = client.claim("id-1")
|
||||
assert claimed.state == "in_progress"
|
||||
|
||||
patch_resp.json.return_value = close_body
|
||||
closed = client.close("id-1")
|
||||
assert closed.state == "closed"
|
||||
|
||||
|
||||
def test_poll_next_empty() -> None:
|
||||
client = IssueCoreClient(IntakeConfig(base_url="http://x", api_key="k"))
|
||||
with patch.object(client, "list_open", return_value=[]):
|
||||
assert poll_next(client) is None
|
||||
|
||||
|
||||
def test_poll_next_claims_and_maps(tmp_path: Path) -> None:
|
||||
_make_repo(tmp_path)
|
||||
client = IssueCoreClient(
|
||||
IntakeConfig(
|
||||
base_url="http://x",
|
||||
api_key="k",
|
||||
repo_roots=(str(tmp_path),),
|
||||
)
|
||||
)
|
||||
open_issue = EmittedIssue(
|
||||
issue_id="id-9",
|
||||
title="Mail intake",
|
||||
description="triage",
|
||||
labels=["mail-intake", "automated"],
|
||||
target_repo="binky-control",
|
||||
activity_definition_id="binky-weekly-mail-intake",
|
||||
number=9,
|
||||
)
|
||||
claimed = EmittedIssue(
|
||||
issue_id="id-9",
|
||||
title="Mail intake",
|
||||
description="triage",
|
||||
labels=["mail-intake", "automated"],
|
||||
target_repo="binky-control",
|
||||
activity_definition_id="binky-weekly-mail-intake",
|
||||
number=9,
|
||||
state="in_progress",
|
||||
)
|
||||
with patch.object(client, "list_open", return_value=[open_issue]), patch.object(
|
||||
client, "claim", return_value=claimed
|
||||
) as claim:
|
||||
result = poll_next(client, claim=True)
|
||||
assert result is not None
|
||||
issue, spec = result
|
||||
claim.assert_called_once_with("id-9")
|
||||
assert issue.state == "in_progress"
|
||||
assert spec.agent == "mail-triage"
|
||||
assert spec.completion_event_type == "binky_mail_intake"
|
||||
Loading…
Add table
Add a link
Reference in a new issue