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
|
|
@ -4,6 +4,7 @@ Each agent = separate process with isolated context.
|
|||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import time
|
||||
|
|
@ -24,6 +25,7 @@ def run_agent(
|
|||
brief_override: str | None = None,
|
||||
dry_run: bool = False,
|
||||
allow_write: bool = False,
|
||||
noninteractive: bool = False,
|
||||
) -> dict:
|
||||
"""Run a single Claude Code agent as a subprocess.
|
||||
|
||||
|
|
@ -64,7 +66,7 @@ def run_agent(
|
|||
# Run claude subprocess
|
||||
start = time.monotonic()
|
||||
result = _run_claude(prompt, model=model, working_dir=working_dir,
|
||||
allow_write=allow_write)
|
||||
allow_write=allow_write, noninteractive=noninteractive)
|
||||
duration = int(time.monotonic() - start)
|
||||
|
||||
# Parse output — ensure output_text is always a string for DB storage
|
||||
|
|
@ -109,6 +111,7 @@ def _run_claude(
|
|||
model: str = "sonnet",
|
||||
working_dir: str | None = None,
|
||||
allow_write: bool = False,
|
||||
noninteractive: bool = False,
|
||||
) -> dict:
|
||||
"""Execute claude CLI as subprocess. Returns dict with output, returncode, etc."""
|
||||
cmd = [
|
||||
|
|
@ -120,13 +123,17 @@ def _run_claude(
|
|||
if allow_write:
|
||||
cmd.append("--dangerously-skip-permissions")
|
||||
|
||||
is_noninteractive = noninteractive or os.environ.get("KIN_NONINTERACTIVE") == "1"
|
||||
timeout = 300 if is_noninteractive else 600
|
||||
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=600, # 10 min max
|
||||
timeout=timeout,
|
||||
cwd=working_dir,
|
||||
stdin=subprocess.DEVNULL if is_noninteractive else None,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
return {
|
||||
|
|
@ -137,7 +144,7 @@ def _run_claude(
|
|||
except subprocess.TimeoutExpired:
|
||||
return {
|
||||
"output": "",
|
||||
"error": "Agent timed out after 600s",
|
||||
"error": f"Agent timed out after {timeout}s",
|
||||
"returncode": 124,
|
||||
}
|
||||
|
||||
|
|
@ -213,6 +220,7 @@ def run_pipeline(
|
|||
steps: list[dict],
|
||||
dry_run: bool = False,
|
||||
allow_write: bool = False,
|
||||
noninteractive: bool = False,
|
||||
) -> dict:
|
||||
"""Execute a multi-step pipeline of agents.
|
||||
|
||||
|
|
@ -260,6 +268,7 @@ def run_pipeline(
|
|||
brief_override=brief,
|
||||
dry_run=dry_run,
|
||||
allow_write=allow_write,
|
||||
noninteractive=noninteractive,
|
||||
)
|
||||
results.append(result)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue