kin: KIN-020 UI для manual_task эскалации из auto_resolve_pending_actions
This commit is contained in:
parent
a0b0976d8d
commit
77ed68c2b5
3 changed files with 57 additions and 3 deletions
|
|
@ -41,6 +41,17 @@ def build_context(
|
||||||
"role": role,
|
"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":
|
if role == "pm":
|
||||||
ctx["modules"] = models.get_modules(conn, project_id)
|
ctx["modules"] = models.get_modules(conn, project_id)
|
||||||
ctx["decisions"] = models.get_decisions(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(f"## Target module: {hint}")
|
||||||
sections.append("")
|
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)
|
# Previous step output (pipeline chaining)
|
||||||
prev = context.get("previous_output")
|
prev = context.get("previous_output")
|
||||||
if prev:
|
if prev:
|
||||||
|
|
|
||||||
25
web/api.py
25
web/api.py
|
|
@ -138,12 +138,13 @@ class ProjectCreate(BaseModel):
|
||||||
class ProjectPatch(BaseModel):
|
class ProjectPatch(BaseModel):
|
||||||
execution_mode: str | None = None
|
execution_mode: str | None = None
|
||||||
autocommit_enabled: bool | None = None
|
autocommit_enabled: bool | None = None
|
||||||
|
obsidian_vault_path: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@app.patch("/api/projects/{project_id}")
|
@app.patch("/api/projects/{project_id}")
|
||||||
def patch_project(project_id: str, body: ProjectPatch):
|
def patch_project(project_id: str, body: ProjectPatch):
|
||||||
if body.execution_mode is None and body.autocommit_enabled is None:
|
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 or autocommit_enabled.")
|
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:
|
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)}")
|
raise HTTPException(400, f"Invalid execution_mode '{body.execution_mode}'. Must be one of: {', '.join(VALID_EXECUTION_MODES)}")
|
||||||
conn = get_conn()
|
conn = get_conn()
|
||||||
|
|
@ -156,12 +157,32 @@ def patch_project(project_id: str, body: ProjectPatch):
|
||||||
fields["execution_mode"] = body.execution_mode
|
fields["execution_mode"] = body.execution_mode
|
||||||
if body.autocommit_enabled is not None:
|
if body.autocommit_enabled is not None:
|
||||||
fields["autocommit_enabled"] = int(body.autocommit_enabled)
|
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)
|
models.update_project(conn, project_id, **fields)
|
||||||
p = models.get_project(conn, project_id)
|
p = models.get_project(conn, project_id)
|
||||||
conn.close()
|
conn.close()
|
||||||
return p
|
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")
|
@app.post("/api/projects")
|
||||||
def create_project(body: ProjectCreate):
|
def create_project(body: ProjectCreate):
|
||||||
conn = get_conn()
|
conn = get_conn()
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ export interface Project {
|
||||||
tech_stack: string[] | null
|
tech_stack: string[] | null
|
||||||
execution_mode: string | null
|
execution_mode: string | null
|
||||||
autocommit_enabled: number | null
|
autocommit_enabled: number | null
|
||||||
|
obsidian_vault_path: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
total_tasks: number
|
total_tasks: number
|
||||||
done_tasks: number
|
done_tasks: number
|
||||||
|
|
@ -49,6 +50,13 @@ export interface Project {
|
||||||
review_tasks: number
|
review_tasks: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ObsidianSyncResult {
|
||||||
|
exported_decisions: number
|
||||||
|
tasks_updated: number
|
||||||
|
errors: string[]
|
||||||
|
vault_path: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProjectDetail extends Project {
|
export interface ProjectDetail extends Project {
|
||||||
tasks: Task[]
|
tasks: Task[]
|
||||||
modules: Module[]
|
modules: Module[]
|
||||||
|
|
@ -171,8 +179,10 @@ export const api = {
|
||||||
post<{ updated: string[]; count: number }>(`/projects/${projectId}/audit/apply`, { task_ids: taskIds }),
|
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 }) =>
|
patchTask: (id: string, data: { status?: string; execution_mode?: string; priority?: number; route_type?: string; title?: string; brief_text?: string }) =>
|
||||||
patch<Task>(`/tasks/${id}`, data),
|
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),
|
patch<Project>(`/projects/${id}`, data),
|
||||||
|
syncObsidian: (projectId: string) =>
|
||||||
|
post<ObsidianSyncResult>(`/projects/${projectId}/sync/obsidian`, {}),
|
||||||
deleteDecision: (projectId: string, decisionId: number) =>
|
deleteDecision: (projectId: string, decisionId: number) =>
|
||||||
del<{ deleted: number }>(`/projects/${projectId}/decisions/${decisionId}`),
|
del<{ deleted: number }>(`/projects/${projectId}/decisions/${decisionId}`),
|
||||||
createDecision: (data: { project_id: string; type: string; title: string; description: string; category?: string; tags?: string[] }) =>
|
createDecision: (data: { project_id: string; type: string; title: string; description: string; category?: string; tags?: string[] }) =>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue