from __future__ import annotations import uuid from datetime import UTC, datetime, timedelta def make_conversation(app, projects, owner): root = projects / "duck" root.mkdir() project_id = str(uuid.uuid4()) app.state.db.execute( "INSERT INTO projects(id,name,path,owner_id,created_at) VALUES(?,?,?,?,?)", (project_id, "Duck project", str(root), owner, "2026-01-01"), ) conversation = app.state.codex.create_conversation(project_id, "Long task", user_id=owner) return project_id, conversation def test_duck_repeated_failures_and_long_task_are_deduplicated(logged_in): client, app, projects, _, _ = logged_in owner = app.state.db.one("SELECT id FROM users LIMIT 1")["id"] _, conversation = make_conversation(app, projects, owner) for index in range(3): app.state.db.execute( "INSERT INTO prompt_queue(id,conversation_id,content,status,attachment_ids,created_at,finished_at,error,requested_by) VALUES(?,?,?,?,?,?,?,?,?)", (str(uuid.uuid4()), conversation["id"], "try", "failed", "[]", f"2026-01-0{index+1}", f"2026-01-0{index+1}", f"pytest failed in test_widget line {40+index}", owner), ) started = (datetime.now(UTC) - timedelta(minutes=45)).isoformat() running_id = str(uuid.uuid4()) app.state.db.execute( "INSERT INTO prompt_queue(id,conversation_id,content,status,attachment_ids,created_at,started_at,requested_by) VALUES(?,?,?,?,?,?,?,?)", (running_id, conversation["id"], "long", "running", "[]", started, started, owner), ) app.state.db.execute("UPDATE conversations SET status='running',updated_at=? WHERE id=?", (started, conversation["id"])) first = client.get("/api/activity") second = client.get("/api/activity") assert first.status_code == second.status_code == 200 signals = app.state.db.all("SELECT * FROM duck_signals WHERE conversation_id=?", (conversation["id"],)) assert {item["kind"] for item in signals} == {"repeated_failure", "long_running"} assert len(signals) == 2 card = next(item for item in second.json()["cards"] if item["id"] == conversation["id"]) assert card["state"] == "running" assert len(card["signals"]) == 2 def test_duck_signal_is_private_and_can_be_dismissed(logged_in): client, app, projects, _, headers = logged_in owner = app.state.db.one("SELECT id FROM users LIMIT 1")["id"] _, conversation = make_conversation(app, projects, owner) signal_id = str(uuid.uuid4()) now = datetime.now(UTC).isoformat() app.state.db.execute( "INSERT INTO duck_signals(id,conversation_id,kind,fingerprint,status,created_at,updated_at) VALUES(?,?,?,?,?,?,?)", (signal_id, conversation["id"], "long_running", "fixture", "open", now, now), ) response = client.post(f"/api/duck-signals/{signal_id}/action", headers=headers, json={"action": "dismiss"}) assert response.status_code == 200 assert app.state.db.one("SELECT status FROM duck_signals WHERE id=?", (signal_id,))["status"] == "dismissed" def test_project_duration_budget_warns_without_stopping_active_agent(logged_in): client, app, projects, _, _ = logged_in owner = app.state.db.one("SELECT id FROM users LIMIT 1")["id"] project_id, conversation = make_conversation(app, projects, owner) now = datetime.now(UTC).isoformat() app.state.db.execute( "INSERT INTO user_preferences(user_id,auto_compact_context,duck_enabled,updated_at) VALUES(?,?,?,?) " "ON CONFLICT(user_id) DO UPDATE SET duck_enabled=excluded.duck_enabled", (owner, 0, 0, now), ) app.state.db.execute( "INSERT INTO agent_project_policies(project_id,priority,max_runtime_minutes,updated_at) VALUES(?,?,?,?)", (project_id, "normal", 5, now), ) started = (datetime.now(UTC) - timedelta(minutes=10)).isoformat() queue_id = str(uuid.uuid4()) app.state.db.execute( "INSERT INTO prompt_queue(id,conversation_id,content,status,attachment_ids,created_at,started_at,requested_by) " "VALUES(?,?,?,?,?,?,?,?)", (queue_id, conversation["id"], "long", "running", "[]", started, started, owner), ) app.state.db.execute( "UPDATE conversations SET status='running',updated_at=? WHERE id=?", (started, conversation["id"]), ) assert client.get("/api/activity").status_code == 200 signal = app.state.db.one( "SELECT * FROM duck_signals WHERE conversation_id=? AND kind='long_running'", (conversation["id"],), ) assert signal and signal["queue_id"] == queue_id assert app.state.db.one("SELECT status FROM prompt_queue WHERE id=?", (queue_id,))["status"] == "running"