feat(drone): fix pr_handler: scope commit to branch_dir pathspec — prevents concurrent-PR index contamination (FPLAN-0190 Task A) (#351)
* feat(devpulse): watchdog: breadcrumb on exit + default timeout 1800s to 600s (FPLAN-0189) Co-Authored-By: @devpulse <devpulse@aipass> * feat(drone): fix pr_handler: scope commit to branch_dir pathspec — prevents concurrent-PR index contamination (FPLAN-0190 Task A) Co-Authored-By: @drone <drone@aipass> --------- Co-authored-by: @devpulse <devpulse@aipass> Co-authored-by: @drone <drone@aipass>
This commit is contained in:
co-authored by
@devpulse
@drone
parent
c941c3d6ac
commit
905f2a45fb
@@ -100,6 +100,11 @@
|
||||
"standard": "json_structure",
|
||||
"file": "apps/handlers/watchdog/registry.py",
|
||||
"reason": "Devpulse is a manager branch with no json_handler. Registry logs through prax system_logger. Same situation as watchdog/agent.py, timer.py, schedule.py."
|
||||
},
|
||||
{
|
||||
"standard": "encapsulation",
|
||||
"file": "tests/test_watchdog_module.py",
|
||||
"reason": "Test file imports aipass.devpulse.apps.modules.watchdog for direct router testing (same-branch import). Encapsulation check pattern-matches on 'from aipass.*.apps.*' shape regardless of same- vs cross-branch. Inherits branch-level gap (manager branch, no apps/handlers/__init__.py inspect.stack guard)."
|
||||
}
|
||||
],
|
||||
"notes": {
|
||||
|
||||
@@ -178,14 +178,15 @@ def _classify_exit(branch_path: Path, lock_existed: bool) -> tuple[str, str, int
|
||||
|
||||
def watch_agent(
|
||||
agent_id: str,
|
||||
timeout_seconds: int = 1800,
|
||||
timeout_seconds: int = 600,
|
||||
poll_interval: float = 2.0,
|
||||
) -> dict:
|
||||
"""Block until the dispatched agent at `agent_id` exits.
|
||||
|
||||
Args:
|
||||
agent_id: Branch token like ``@drone`` (or bare ``drone``).
|
||||
timeout_seconds: Maximum wait. Default 30 min.
|
||||
timeout_seconds: Maximum wait. Default 10 min — catches crashes + silent-finishes
|
||||
fast; long agent watches should pass an explicit ``--timeout``.
|
||||
poll_interval: Seconds between checks. Default 2.0.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -32,7 +32,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger
|
||||
from aipass.cli.apps.modules import console, error, warning
|
||||
|
||||
_VALID_SUBCOMMANDS = ["agent", "timer", "schedule", "status", "cancel", "list"]
|
||||
_DEFAULT_AGENT_TIMEOUT = 1800
|
||||
_DEFAULT_AGENT_TIMEOUT = 600
|
||||
_NOT_IMPLEMENTED_MSG = "{sub} is not yet implemented in this phase — see FPLAN-0186 (Phase {phase})"
|
||||
# Phase 4 wired cancel + list for real. Left the map so future deferrals can reuse the shape.
|
||||
_PHASE_BY_SUB: dict[str, int] = {}
|
||||
@@ -353,6 +353,9 @@ def _handle_agent(sub_args: List[str]) -> bool:
|
||||
reason = result.get("reason", "")
|
||||
elapsed = result.get("elapsed", 0)
|
||||
console.print(f"[bold]watchdog agent[/bold] {agent_id} -> state={state} elapsed={elapsed}s reason={reason}")
|
||||
console.print(
|
||||
f'watchdog: {agent_id} stopped (state={state}). Next: drone @ai_mail dispatch {agent_id} "check in" "..."'
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -279,3 +279,50 @@ def test_agent_subcommand_invalid_timeout(capsys):
|
||||
captured = capsys.readouterr()
|
||||
combined = captured.out + captured.err
|
||||
assert "invalid" in combined.lower() or "--timeout" in combined.lower()
|
||||
|
||||
|
||||
def test_agent_subcommand_default_timeout_is_600():
|
||||
"""Without an explicit --timeout, the module passes 600s (FPLAN-0189)."""
|
||||
captured_args = {}
|
||||
|
||||
def fake_watch_agent(agent_id, timeout_seconds=9999):
|
||||
"""Fake watcher — records the timeout the module passed in."""
|
||||
captured_args["timeout"] = timeout_seconds
|
||||
return {
|
||||
"woke": True,
|
||||
"reason": "fake",
|
||||
"elapsed": 1,
|
||||
"agent_state": "completed",
|
||||
"exit_code": 0,
|
||||
"agent_id": agent_id,
|
||||
}
|
||||
|
||||
fake_module = type(sys)("fake_agent_mod")
|
||||
fake_module.watch_agent = fake_watch_agent
|
||||
|
||||
with patch("importlib.import_module", return_value=fake_module):
|
||||
wd_mod.handle_command("watchdog", ["agent", "@flow"])
|
||||
|
||||
assert captured_args["timeout"] == 600
|
||||
|
||||
|
||||
def test_agent_subcommand_emits_next_action_breadcrumb(capsys):
|
||||
"""On exit, the CLI prints a 'Next: drone @ai_mail dispatch' breadcrumb (FPLAN-0189)."""
|
||||
fake_result = {
|
||||
"woke": True,
|
||||
"reason": "fake clean exit",
|
||||
"elapsed": 5,
|
||||
"agent_state": "completed",
|
||||
"exit_code": 0,
|
||||
"agent_id": "@drone",
|
||||
}
|
||||
fake_module = type(sys)("fake_agent_mod")
|
||||
fake_module.watch_agent = lambda agent_id, timeout_seconds=600: fake_result
|
||||
|
||||
with patch("importlib.import_module", return_value=fake_module):
|
||||
wd_mod.handle_command("watchdog", ["agent", "@drone"])
|
||||
|
||||
captured = capsys.readouterr()
|
||||
combined = captured.out + captured.err
|
||||
assert "Next: drone @ai_mail dispatch @drone" in combined
|
||||
assert "state=completed" in combined
|
||||
|
||||
Reference in New Issue
Block a user