feat(KIN-012): auto followup generation and pending_actions auto-resolution
Auto mode now calls generate_followups() after task_auto_approved hook. Permission-blocked followup items are auto-resolved: rerun first, fallback to manual_task on failure. Recursion guard skips followup-sourced tasks. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
01b269e2b8
commit
3cb516193b
4 changed files with 256 additions and 25 deletions
|
|
@ -7,7 +7,7 @@ from unittest.mock import patch, MagicMock
|
|||
from core.db import init_db
|
||||
from core import models
|
||||
from core.followup import (
|
||||
generate_followups, resolve_pending_action,
|
||||
generate_followups, resolve_pending_action, auto_resolve_pending_actions,
|
||||
_collect_pipeline_output, _next_task_id, _is_permission_blocked,
|
||||
)
|
||||
|
||||
|
|
@ -222,3 +222,48 @@ class TestResolvePendingAction:
|
|||
def test_nonexistent_task(self, conn):
|
||||
action = {"type": "permission_fix", "original_item": {}}
|
||||
assert resolve_pending_action(conn, "NOPE", action, "skip") is None
|
||||
|
||||
|
||||
class TestAutoResolvePendingActions:
|
||||
@patch("agents.runner._run_claude")
|
||||
def test_rerun_success_resolves_as_rerun(self, mock_claude, conn):
|
||||
"""Успешный rerun должен резолвиться как 'rerun'."""
|
||||
mock_claude.return_value = {
|
||||
"output": json.dumps({"result": "fixed"}),
|
||||
"returncode": 0,
|
||||
}
|
||||
action = {
|
||||
"type": "permission_fix",
|
||||
"description": "Fix X",
|
||||
"original_item": {"title": "Fix X", "type": "frontend_dev", "brief": "Apply fix"},
|
||||
"options": ["rerun", "manual_task", "skip"],
|
||||
}
|
||||
results = auto_resolve_pending_actions(conn, "VDOL-001", [action])
|
||||
|
||||
assert len(results) == 1
|
||||
assert results[0]["resolved"] == "rerun"
|
||||
|
||||
@patch("agents.runner._run_claude")
|
||||
def test_rerun_failure_escalates_to_manual_task(self, mock_claude, conn):
|
||||
"""Провал rerun должен создавать manual_task для эскалации."""
|
||||
mock_claude.return_value = {"output": "", "returncode": 1}
|
||||
action = {
|
||||
"type": "permission_fix",
|
||||
"description": "Fix X",
|
||||
"original_item": {"title": "Fix X", "type": "frontend_dev", "brief": "Apply fix"},
|
||||
"options": ["rerun", "manual_task", "skip"],
|
||||
}
|
||||
results = auto_resolve_pending_actions(conn, "VDOL-001", [action])
|
||||
|
||||
assert len(results) == 1
|
||||
assert results[0]["resolved"] == "manual_task"
|
||||
# Manual task должна быть создана в DB
|
||||
tasks = models.list_tasks(conn, project_id="vdol")
|
||||
assert len(tasks) == 2 # VDOL-001 + новая manual task
|
||||
|
||||
@patch("agents.runner._run_claude")
|
||||
def test_empty_pending_actions(self, mock_claude, conn):
|
||||
"""Пустой список — пустой результат."""
|
||||
results = auto_resolve_pending_actions(conn, "VDOL-001", [])
|
||||
assert results == []
|
||||
mock_claude.assert_not_called()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue