From 77ed68c2b57d31c926ca8c485c2448ab506a1d05 Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Mon, 16 Mar 2026 07:14:32 +0200 Subject: [PATCH] =?UTF-8?q?kin:=20KIN-020=20UI=20=D0=B4=D0=BB=D1=8F=20manu?= =?UTF-8?q?al=5Ftask=20=D1=8D=D1=81=D0=BA=D0=B0=D0=BB=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B8=D0=B7=20auto=5Fresolve=5Fpending=5Factions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/context_builder.py | 23 +++++++++++++++++++++++ web/api.py | 25 +++++++++++++++++++++++-- web/frontend/src/api.ts | 12 +++++++++++- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/core/context_builder.py b/core/context_builder.py index 0aba5bb..ac8ca33 100644 --- a/core/context_builder.py +++ b/core/context_builder.py @@ -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: diff --git a/web/api.py b/web/api.py index 40cb3a4..4c9fa49 100644 --- a/web/api.py +++ b/web/api.py @@ -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() diff --git a/web/frontend/src/api.ts b/web/frontend/src/api.ts index 37195da..ef07115 100644 --- a/web/frontend/src/api.ts +++ b/web/frontend/src/api.ts @@ -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(`/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(`/projects/${id}`, data), + syncObsidian: (projectId: string) => + post(`/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[] }) =>