Add Auto/Review mode toggle and non-interactive runner
- GUI: Auto/Review toggle on TaskDetail and ProjectView
persisted per-project in localStorage
- Runner: noninteractive param (stdin=DEVNULL, 300s timeout)
activated by KIN_NONINTERACTIVE=1 env or param
- CLI: --allow-write flag for kin run command
- API: POST /run accepts {allow_write: bool}, sets
KIN_NONINTERACTIVE=1 and stdin=DEVNULL for subprocess
- Fixes pipeline hanging on interactive claude input (VDOL-002)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
03961500e6
commit
e755a19633
8 changed files with 174 additions and 18 deletions
|
|
@ -1,6 +1,7 @@
|
|||
"""Tests for agents/runner.py — agent execution with mocked claude CLI."""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from core.db import init_db
|
||||
|
|
@ -274,3 +275,63 @@ class TestTryParseJson:
|
|||
|
||||
def test_json_array(self):
|
||||
assert _try_parse_json('[1, 2, 3]') == [1, 2, 3]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Non-interactive mode
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestNonInteractive:
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_noninteractive_sets_stdin_devnull(self, mock_run, conn):
|
||||
"""When noninteractive=True, subprocess.run should get stdin=subprocess.DEVNULL."""
|
||||
mock_run.return_value = _mock_claude_success({"result": "ok"})
|
||||
run_agent(conn, "debugger", "VDOL-001", "vdol", noninteractive=True)
|
||||
call_kwargs = mock_run.call_args[1]
|
||||
assert call_kwargs.get("stdin") == subprocess.DEVNULL
|
||||
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_noninteractive_uses_300s_timeout(self, mock_run, conn):
|
||||
mock_run.return_value = _mock_claude_success({"result": "ok"})
|
||||
run_agent(conn, "debugger", "VDOL-001", "vdol", noninteractive=True)
|
||||
call_kwargs = mock_run.call_args[1]
|
||||
assert call_kwargs.get("timeout") == 300
|
||||
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_interactive_uses_600s_timeout(self, mock_run, conn):
|
||||
mock_run.return_value = _mock_claude_success({"result": "ok"})
|
||||
run_agent(conn, "debugger", "VDOL-001", "vdol", noninteractive=False)
|
||||
call_kwargs = mock_run.call_args[1]
|
||||
assert call_kwargs.get("timeout") == 600
|
||||
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_interactive_no_stdin_override(self, mock_run, conn):
|
||||
"""In interactive mode, stdin should not be set to DEVNULL."""
|
||||
mock_run.return_value = _mock_claude_success({"result": "ok"})
|
||||
run_agent(conn, "debugger", "VDOL-001", "vdol", noninteractive=False)
|
||||
call_kwargs = mock_run.call_args[1]
|
||||
assert call_kwargs.get("stdin") is None
|
||||
|
||||
@patch.dict("os.environ", {"KIN_NONINTERACTIVE": "1"})
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_env_var_activates_noninteractive(self, mock_run, conn):
|
||||
"""KIN_NONINTERACTIVE=1 env var should activate non-interactive mode."""
|
||||
mock_run.return_value = _mock_claude_success({"result": "ok"})
|
||||
run_agent(conn, "debugger", "VDOL-001", "vdol", noninteractive=False)
|
||||
call_kwargs = mock_run.call_args[1]
|
||||
assert call_kwargs.get("stdin") == subprocess.DEVNULL
|
||||
assert call_kwargs.get("timeout") == 300
|
||||
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_allow_write_adds_skip_permissions(self, mock_run, conn):
|
||||
mock_run.return_value = _mock_claude_success({"result": "ok"})
|
||||
run_agent(conn, "debugger", "VDOL-001", "vdol", allow_write=True)
|
||||
cmd = mock_run.call_args[0][0]
|
||||
assert "--dangerously-skip-permissions" in cmd
|
||||
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_no_allow_write_no_skip_permissions(self, mock_run, conn):
|
||||
mock_run.return_value = _mock_claude_success({"result": "ok"})
|
||||
run_agent(conn, "debugger", "VDOL-001", "vdol", allow_write=False)
|
||||
cmd = mock_run.call_args[0][0]
|
||||
assert "--dangerously-skip-permissions" not in cmd
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue