From c767c6157ae6ca6044b706d7db8c92ed9d899797 Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Tue, 17 Mar 2026 18:23:04 +0200 Subject: [PATCH] kin: auto-commit after pipeline --- cli/watch.py | 6 +--- core/models.py | 9 ++++++ tests/test_models.py | 43 ++++++++++++++++++++++++++ web/frontend/src/api.ts | 4 +-- web/frontend/src/views/ProjectView.vue | 12 +++---- 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/cli/watch.py b/cli/watch.py index 2671d37..4a1d668 100644 --- a/cli/watch.py +++ b/cli/watch.py @@ -139,7 +139,6 @@ def _render_ps(rows: list[dict]) -> None: def cmd_watch(conn, task_id: str) -> None: """Polling monitor for a task. Updates every 5s. Exits on terminal pipeline state.""" - import sqlite3 task = models.get_task(conn, task_id) if not task: print(f"Task '{task_id}' not found.") @@ -152,10 +151,7 @@ def cmd_watch(conn, task_id: str) -> None: if pipeline: log = models.get_current_agent_log(conn, task_id, pipeline['created_at']) - step_num = conn.execute( - 'SELECT COUNT(*) FROM agent_logs WHERE task_id = ? AND created_at >= ?', - (task_id, pipeline['created_at']), - ).fetchone()[0] + step_num = models.count_agent_logs_since(conn, task_id, pipeline['created_at']) total = _parse_total_steps(pipeline.get('steps')) else: log, step_num, total = None, 0, '?' diff --git a/core/models.py b/core/models.py index baf6973..51a0fdc 100644 --- a/core/models.py +++ b/core/models.py @@ -543,6 +543,15 @@ def get_pipeline_for_watch(conn: sqlite3.Connection, task_id: str) -> dict | Non return _row_to_dict(row) +def count_agent_logs_since(conn: sqlite3.Connection, task_id: str, since_iso: str) -> int: + """Return number of agent_logs for a task created at or after since_iso.""" + row = conn.execute( + "SELECT COUNT(*) FROM agent_logs WHERE task_id = ? AND created_at >= ?", + (task_id, since_iso), + ).fetchone() + return row[0] + + def get_current_agent_log( conn: sqlite3.Connection, task_id: str, since_iso: str ) -> dict | None: diff --git a/tests/test_models.py b/tests/test_models.py index b7aeec4..715ce24 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -373,6 +373,49 @@ def test_log_agent_run(conn): assert log["success"] == 1 # SQLite boolean +def test_count_agent_logs_since_returns_correct_count(conn): + """count_agent_logs_since возвращает количество логов >= since_iso.""" + models.create_project(conn, "p1", "P1", "/p1") + models.create_task(conn, "P1-001", "p1", "Task") + models.log_agent_run(conn, "p1", "developer", "implement", task_id="P1-001") + models.log_agent_run(conn, "p1", "reviewer", "review", task_id="P1-001") + + count = models.count_agent_logs_since(conn, "P1-001", "2000-01-01T00:00:00") + assert count == 2 + + +def test_count_agent_logs_since_filters_by_task_id(conn): + """count_agent_logs_since не считает логи других задач.""" + models.create_project(conn, "p1", "P1", "/p1") + models.create_task(conn, "P1-001", "p1", "Task A") + models.create_task(conn, "P1-002", "p1", "Task B") + models.log_agent_run(conn, "p1", "developer", "implement", task_id="P1-001") + models.log_agent_run(conn, "p1", "developer", "implement", task_id="P1-002") + + assert models.count_agent_logs_since(conn, "P1-001", "2000-01-01T00:00:00") == 1 + assert models.count_agent_logs_since(conn, "P1-002", "2000-01-01T00:00:00") == 1 + + +def test_count_agent_logs_since_excludes_before_cutoff(conn): + """count_agent_logs_since не считает логи строго до since_iso.""" + models.create_project(conn, "p1", "P1", "/p1") + models.create_task(conn, "P1-001", "p1", "Task") + models.log_agent_run(conn, "p1", "developer", "implement", task_id="P1-001") + + # since_iso в далёком будущем — ни один лог не попадает + count = models.count_agent_logs_since(conn, "P1-001", "2099-01-01T00:00:00") + assert count == 0 + + +def test_count_agent_logs_since_empty_returns_zero(conn): + """count_agent_logs_since возвращает 0 при отсутствии логов.""" + models.create_project(conn, "p1", "P1", "/p1") + models.create_task(conn, "P1-001", "p1", "Task") + + count = models.count_agent_logs_since(conn, "P1-001", "2000-01-01T00:00:00") + assert count == 0 + + # -- Pipelines -- def test_create_and_update_pipeline(conn): diff --git a/web/frontend/src/api.ts b/web/frontend/src/api.ts index 5a55ffa..4b9fcb3 100644 --- a/web/frontend/src/api.ts +++ b/web/frontend/src/api.ts @@ -189,7 +189,7 @@ export interface ProjectLink { id: number from_project: string to_project: string - link_type: string + type: string description: string | null created_at: string } @@ -411,7 +411,7 @@ export const api = { get(`/pipelines/${pipelineId}/logs?since_id=${sinceId}`), projectLinks: (projectId: string) => get(`/projects/${projectId}/links`), - createProjectLink: (data: { from_project: string; to_project: string; link_type: string; description?: string }) => + createProjectLink: (data: { from_project: string; to_project: string; type: string; description?: string }) => post('/project-links', data), deleteProjectLink: (id: number) => del(`/project-links/${id}`), diff --git a/web/frontend/src/views/ProjectView.vue b/web/frontend/src/views/ProjectView.vue index 2c4b7be..01f5f0f 100644 --- a/web/frontend/src/views/ProjectView.vue +++ b/web/frontend/src/views/ProjectView.vue @@ -395,7 +395,7 @@ const links = ref([]) const linksLoading = ref(false) const linksError = ref('') const showAddLink = ref(false) -const linkForm = ref({ to_project: '', link_type: 'depends_on', description: '' }) +const linkForm = ref({ to_project: '', type: 'depends_on', description: '' }) const linkFormError = ref('') const linkSaving = ref(false) @@ -419,11 +419,11 @@ async function addLink() { await api.createProjectLink({ from_project: props.id, to_project: linkForm.value.to_project, - link_type: linkForm.value.link_type, + type: linkForm.value.type, description: linkForm.value.description || undefined, }) showAddLink.value = false - linkForm.value = { to_project: '', link_type: 'depends_on', description: '' } + linkForm.value = { to_project: '', type: 'depends_on', description: '' } await loadLinks() } catch (e: any) { linkFormError.value = e.message @@ -1368,7 +1368,7 @@ async function addDecision() { {{ link.from_project }} -> {{ link.to_project }} - {{ link.link_type }} + {{ link.type }} {{ link.description }}