kin: KIN-020 UI для manual_task эскалации из auto_resolve_pending_actions

This commit is contained in:
Gros Frumos 2026-03-16 07:14:32 +02:00
parent a0b0976d8d
commit 77ed68c2b5
3 changed files with 57 additions and 3 deletions

View file

@ -41,6 +41,17 @@ def build_context(
"role": role,
}
# If task has a revise comment, fetch the last agent output for context
if task and task.get("revise_comment"):
row = conn.execute(
"""SELECT output_summary FROM agent_logs
WHERE task_id = ? AND success = 1
ORDER BY created_at DESC LIMIT 1""",
(task_id,),
).fetchone()
if row and row["output_summary"]:
ctx["last_agent_output"] = row["output_summary"]
if role == "pm":
ctx["modules"] = models.get_modules(conn, project_id)
ctx["decisions"] = models.get_decisions(conn, project_id)
@ -207,6 +218,18 @@ def format_prompt(context: dict, role: str, prompt_template: str | None = None)
sections.append(f"## Target module: {hint}")
sections.append("")
# Revision context: director's comment + agent's previous output
task = context.get("task")
if task and task.get("revise_comment"):
sections.append("## Director's revision request:")
sections.append(task["revise_comment"])
sections.append("")
last_output = context.get("last_agent_output")
if last_output:
sections.append("## Your previous output (before revision):")
sections.append(last_output)
sections.append("")
# Previous step output (pipeline chaining)
prev = context.get("previous_output")
if prev:

View file

@ -138,12 +138,13 @@ class ProjectCreate(BaseModel):
class ProjectPatch(BaseModel):
execution_mode: str | None = None
autocommit_enabled: bool | None = None
obsidian_vault_path: str | None = None
@app.patch("/api/projects/{project_id}")
def patch_project(project_id: str, body: ProjectPatch):
if body.execution_mode is None and body.autocommit_enabled is None:
raise HTTPException(400, "Nothing to update. Provide execution_mode or autocommit_enabled.")
if body.execution_mode is None and body.autocommit_enabled is None and body.obsidian_vault_path is None:
raise HTTPException(400, "Nothing to update. Provide execution_mode, autocommit_enabled, or obsidian_vault_path.")
if body.execution_mode is not None and body.execution_mode not in VALID_EXECUTION_MODES:
raise HTTPException(400, f"Invalid execution_mode '{body.execution_mode}'. Must be one of: {', '.join(VALID_EXECUTION_MODES)}")
conn = get_conn()
@ -156,12 +157,32 @@ def patch_project(project_id: str, body: ProjectPatch):
fields["execution_mode"] = body.execution_mode
if body.autocommit_enabled is not None:
fields["autocommit_enabled"] = int(body.autocommit_enabled)
if body.obsidian_vault_path is not None:
fields["obsidian_vault_path"] = body.obsidian_vault_path
models.update_project(conn, project_id, **fields)
p = models.get_project(conn, project_id)
conn.close()
return p
@app.post("/api/projects/{project_id}/sync/obsidian")
def sync_obsidian_endpoint(project_id: str):
"""Запускает двусторонний Obsidian sync для проекта."""
from core.obsidian_sync import sync_obsidian
conn = get_conn()
p = models.get_project(conn, project_id)
if not p:
conn.close()
raise HTTPException(404, f"Project '{project_id}' not found")
if not p.get("obsidian_vault_path"):
conn.close()
raise HTTPException(400, "obsidian_vault_path not set for this project")
result = sync_obsidian(conn, project_id)
conn.close()
return result
@app.post("/api/projects")
def create_project(body: ProjectCreate):
conn = get_conn()

View file

@ -41,6 +41,7 @@ export interface Project {
tech_stack: string[] | null
execution_mode: string | null
autocommit_enabled: number | null
obsidian_vault_path: string | null
created_at: string
total_tasks: number
done_tasks: number
@ -49,6 +50,13 @@ export interface Project {
review_tasks: number
}
export interface ObsidianSyncResult {
exported_decisions: number
tasks_updated: number
errors: string[]
vault_path: string
}
export interface ProjectDetail extends Project {
tasks: Task[]
modules: Module[]
@ -171,8 +179,10 @@ export const api = {
post<{ updated: string[]; count: number }>(`/projects/${projectId}/audit/apply`, { task_ids: taskIds }),
patchTask: (id: string, data: { status?: string; execution_mode?: string; priority?: number; route_type?: string; title?: string; brief_text?: string }) =>
patch<Task>(`/tasks/${id}`, data),
patchProject: (id: string, data: { execution_mode?: string; autocommit_enabled?: boolean }) =>
patchProject: (id: string, data: { execution_mode?: string; autocommit_enabled?: boolean; obsidian_vault_path?: string }) =>
patch<Project>(`/projects/${id}`, data),
syncObsidian: (projectId: string) =>
post<ObsidianSyncResult>(`/projects/${projectId}/sync/obsidian`, {}),
deleteDecision: (projectId: string, decisionId: number) =>
del<{ deleted: number }>(`/projects/${projectId}/decisions/${decisionId}`),
createDecision: (data: { project_id: string; type: string; title: string; description: string; category?: string; tags?: string[] }) =>