Add permission-aware follow-up flow with interactive resolution

When follow-up agent detects permission-blocked items ("ручное
применение", "permission denied", etc.), they become pending_actions
instead of auto-created tasks. User chooses per item:
  1. Rerun with --dangerously-skip-permissions
  2. Create manual task
  3. Skip

core/followup.py:
  _is_permission_blocked() — regex detection of 9 permission patterns
  generate_followups() returns {created, pending_actions}
  resolve_pending_action() — handles rerun/manual_task/skip

agents/runner.py:
  _run_claude(allow_write=True) adds --dangerously-skip-permissions
  run_agent/run_pipeline pass allow_write through

CLI: kin approve --followup — interactive 1/2/3 prompt per blocked item
API: POST /approve returns {needs_decision, pending_actions}
     POST /resolve resolves individual actions
Frontend: pending actions shown as cards with 3 buttons in approve modal

136 tests, all passing. Frontend builds clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
johnfrum1234 2026-03-15 15:16:48 +02:00
parent 9264415776
commit ab693d3c4d
7 changed files with 356 additions and 73 deletions

View file

@ -23,6 +23,7 @@ def run_agent(
previous_output: str | None = None,
brief_override: str | None = None,
dry_run: bool = False,
allow_write: bool = False,
) -> dict:
"""Run a single Claude Code agent as a subprocess.
@ -62,7 +63,8 @@ def run_agent(
# Run claude subprocess
start = time.monotonic()
result = _run_claude(prompt, model=model, working_dir=working_dir)
result = _run_claude(prompt, model=model, working_dir=working_dir,
allow_write=allow_write)
duration = int(time.monotonic() - start)
# Parse output — ensure output_text is always a string for DB storage
@ -106,6 +108,7 @@ def _run_claude(
prompt: str,
model: str = "sonnet",
working_dir: str | None = None,
allow_write: bool = False,
) -> dict:
"""Execute claude CLI as subprocess. Returns dict with output, returncode, etc."""
cmd = [
@ -114,6 +117,8 @@ def _run_claude(
"--output-format", "json",
"--model", model,
]
if allow_write:
cmd.append("--dangerously-skip-permissions")
try:
proc = subprocess.run(
@ -207,6 +212,7 @@ def run_pipeline(
task_id: str,
steps: list[dict],
dry_run: bool = False,
allow_write: bool = False,
) -> dict:
"""Execute a multi-step pipeline of agents.
@ -253,6 +259,7 @@ def run_pipeline(
previous_output=previous_output,
brief_override=brief,
dry_run=dry_run,
allow_write=allow_write,
)
results.append(result)