day 1: Kin from zero to production - agents, GUI, autopilot, 352 tests
This commit is contained in:
parent
8d9facda4f
commit
8a6f280cbd
22 changed files with 1907 additions and 103 deletions
|
|
@ -41,6 +41,7 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|||
security_result JSON,
|
||||
forgejo_issue_id INTEGER,
|
||||
execution_mode TEXT,
|
||||
blocked_reason TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
|
@ -211,6 +212,9 @@ def _migrate(conn: sqlite3.Connection):
|
|||
if "execution_mode" not in task_cols:
|
||||
conn.execute("ALTER TABLE tasks ADD COLUMN execution_mode TEXT")
|
||||
conn.commit()
|
||||
if "blocked_reason" not in task_cols:
|
||||
conn.execute("ALTER TABLE tasks ADD COLUMN blocked_reason TEXT")
|
||||
conn.commit()
|
||||
|
||||
|
||||
def init_db(db_path: Path = DB_PATH) -> sqlite3.Connection:
|
||||
|
|
|
|||
|
|
@ -146,6 +146,17 @@ def _get_hook(conn: sqlite3.Connection, hook_id: int) -> dict:
|
|||
return dict(row) if row else {}
|
||||
|
||||
|
||||
def _substitute_vars(command: str, task_id: str | None, conn: sqlite3.Connection) -> str:
|
||||
"""Substitute {task_id} and {title} in hook command."""
|
||||
if task_id is None or "{task_id}" not in command and "{title}" not in command:
|
||||
return command
|
||||
row = conn.execute("SELECT title FROM tasks WHERE id = ?", (task_id,)).fetchone()
|
||||
title = row["title"] if row else ""
|
||||
# Sanitize title for shell safety (strip quotes and newlines)
|
||||
safe_title = title.replace('"', "'").replace("\n", " ").replace("\r", "")
|
||||
return command.replace("{task_id}", task_id).replace("{title}", safe_title)
|
||||
|
||||
|
||||
def _execute_hook(
|
||||
conn: sqlite3.Connection,
|
||||
hook: dict,
|
||||
|
|
@ -159,9 +170,11 @@ def _execute_hook(
|
|||
exit_code = -1
|
||||
success = False
|
||||
|
||||
command = _substitute_vars(hook["command"], task_id, conn)
|
||||
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
hook["command"],
|
||||
command,
|
||||
shell=True,
|
||||
cwd=hook.get("working_dir") or None,
|
||||
capture_output=True,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ from datetime import datetime
|
|||
from typing import Any
|
||||
|
||||
|
||||
VALID_TASK_STATUSES = [
|
||||
"pending", "in_progress", "review", "done",
|
||||
"blocked", "decomposed", "cancelled",
|
||||
]
|
||||
|
||||
|
||||
def _row_to_dict(row: sqlite3.Row | None) -> dict | None:
|
||||
"""Convert sqlite3.Row to dict with JSON fields decoded."""
|
||||
if row is None:
|
||||
|
|
@ -249,6 +255,19 @@ def get_decisions(
|
|||
return _rows_to_list(conn.execute(query, params).fetchall())
|
||||
|
||||
|
||||
def get_decision(conn: sqlite3.Connection, decision_id: int) -> dict | None:
|
||||
"""Get a single decision by id."""
|
||||
row = conn.execute("SELECT * FROM decisions WHERE id = ?", (decision_id,)).fetchone()
|
||||
return _row_to_dict(row) if row else None
|
||||
|
||||
|
||||
def delete_decision(conn: sqlite3.Connection, decision_id: int) -> bool:
|
||||
"""Delete a decision by id. Returns True if deleted, False if not found."""
|
||||
cur = conn.execute("DELETE FROM decisions WHERE id = ?", (decision_id,))
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Modules
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue