kin: KIN-ARCH-004 Добавить подсказку в форму о требовании ~/.ssh/config для ProxyJump

This commit is contained in:
Gros Frumos 2026-03-16 09:43:26 +02:00
parent 4188384f1b
commit af554e15fa
7 changed files with 262 additions and 8 deletions

View file

@ -16,7 +16,7 @@ from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from pydantic import BaseModel, model_validator
from core.db import init_db
from core import models
@ -180,6 +180,12 @@ class ProjectCreate(BaseModel):
ssh_key_path: str | None = None
ssh_proxy_jump: str | None = None
@model_validator(mode="after")
def validate_operations_ssh_host(self) -> "ProjectCreate":
if self.project_type == "operations" and not self.ssh_host:
raise ValueError("ssh_host is required for operations projects")
return self
class ProjectPatch(BaseModel):
execution_mode: str | None = None
@ -365,6 +371,9 @@ def approve_phase(phase_id: int, body: PhaseApprove | None = None):
except ValueError as e:
conn.close()
raise HTTPException(400, str(e))
# Mark the phase's task as done for consistency
if phase.get("task_id"):
models.update_task(conn, phase["task_id"], status="done")
conn.close()
return result
@ -644,6 +653,21 @@ def approve_task(task_id: str, body: TaskApprove | None = None):
event="task_done", task_modules=task_modules)
except Exception:
pass
# Advance phase state machine if this task belongs to an active phase
phase_result = None
phase_row = conn.execute(
"SELECT * FROM project_phases WHERE task_id = ?", (task_id,)
).fetchone()
if phase_row:
phase = dict(phase_row)
if phase.get("status") == "active":
from core.phases import approve_phase as _approve_phase
try:
phase_result = _approve_phase(conn, phase["id"])
except ValueError:
pass
decision = None
if body and body.decision_title:
decision = models.add_decision(
@ -664,6 +688,7 @@ def approve_task(task_id: str, body: TaskApprove | None = None):
"followup_tasks": followup_tasks,
"needs_decision": len(pending_actions) > 0,
"pending_actions": pending_actions,
"phase": phase_result,
}
@ -954,9 +979,8 @@ def get_notifications(project_id: str | None = None):
"""Return tasks with status='blocked' as escalation notifications.
Each item includes task details, the agent role that blocked it,
the reason, and the pipeline step. Intended for GUI polling (5s interval).
TODO: Telegram send notification on new escalation (telegram_sent: false placeholder).
the reason, the pipeline step, and whether a Telegram alert was sent.
Intended for GUI polling (5s interval).
"""
conn = get_conn()
query = "SELECT * FROM tasks WHERE status = 'blocked'"
@ -979,8 +1003,7 @@ def get_notifications(project_id: str | None = None):
"reason": t.get("blocked_reason"),
"pipeline_step": t.get("blocked_pipeline_step"),
"blocked_at": t.get("blocked_at") or t.get("updated_at"),
# TODO: Telegram — set to True once notification is sent via Telegram bot
"telegram_sent": False,
"telegram_sent": bool(t.get("telegram_sent")),
})
return notifications