diff --git a/backend/apps/agents/agents.py b/backend/apps/agents/agents.py
index 42ce5aa9..de5ec34d 100644
--- a/backend/apps/agents/agents.py
+++ b/backend/apps/agents/agents.py
@@ -153,16 +153,6 @@ async def update_session(session_id: str, body: dict):
await agent_manager.update_session(session_id, **body)
return {"ok": True}
-@agents.router.get("/sessions/{session_id}/branches")
-async def get_branches(session_id: str):
- session = agent_manager.get_session(session_id)
- if not session:
- raise HTTPException(status_code=404, detail="Session not found")
- return {
- "branches": {k: v.model_dump(mode="json") for k, v in session.branches.items()},
- "active_branch_id": session.active_branch_id,
- }
-
@agents.router.post("/sessions/{session_id}/duplicate")
async def duplicate_session(session_id: str, body: dict = {}):
try:
@@ -319,16 +309,6 @@ async def subscriptions_exchange(body: dict):
raise HTTPException(status_code=500, detail=str(e))
-@agents.router.get("/subscriptions/models")
-async def subscriptions_models():
- """List all models available through connected subscriptions."""
- from backend.apps.nine_router import is_running, get_models
- if not is_running():
- return {"models": []}
- models = await get_models()
- return {"models": models}
-
-
@agents.router.get("/models")
async def list_models():
"""Return the chat-picker model list grouped by provider.
diff --git a/backend/apps/outputs/models.py b/backend/apps/outputs/models.py
index 515ef187..25c7a385 100644
--- a/backend/apps/outputs/models.py
+++ b/backend/apps/outputs/models.py
@@ -202,10 +202,3 @@ class WorkspaceSeedRequest(BaseModel):
return data
-class VibeCodeRequest(BaseModel):
- prompt: str
- current_frontend_code: str = ""
- current_backend_code: str = ""
- current_schema: str = ""
- name: str = ""
- description: str = ""
diff --git a/backend/apps/outputs/outputs.py b/backend/apps/outputs/outputs.py
index ab01e5be..24876f77 100644
--- a/backend/apps/outputs/outputs.py
+++ b/backend/apps/outputs/outputs.py
@@ -13,7 +13,7 @@ from jsonschema import validate as schema_validate, ValidationError as SchemaVal
from backend.config.Apps import SubApp
from backend.apps.outputs.models import (
Output, OutputCreate, OutputUpdate, OutputExecute, OutputExecuteResult,
- VibeCodeRequest, AutoRunRequest, AutoRunConfig, AutoRunAgentRequest,
+ AutoRunRequest, AutoRunConfig, AutoRunAgentRequest,
WorkspaceSeedRequest,
)
from backend.apps.outputs.executor import execute_backend_code
@@ -398,111 +398,6 @@ async def delete_output(output_id: str):
return {"ok": True}
-VIBE_CODE_SYSTEM_PROMPT = """\
-You are an expert at building self-contained HTML/JS/CSS applications that run in an iframe.
-
-The user will describe what they want, and you will generate:
-1. **frontend_code**: A complete HTML document. React 18 is available via esm.sh CDN.
- - Use:
- - Input data is at window.OUTPUT_INPUT (object), backend result at window.OUTPUT_BACKEND_RESULT.
-2. **input_schema**: A JSON Schema object defining the structured input.
-3. **backend_code** (optional): Python code where input_data is a global dict and result is a global dict to assign to.
-4. **name**: A short name for the view.
-5. **description**: A one-sentence description.
-6. **message**: A brief explanation of what you did/changed.
-
-Return ONLY valid JSON with these keys. No markdown fences, no extra text.\
-"""
-
-
-@outputs.router.post("/vibe-code")
-async def vibe_code(body: VibeCodeRequest):
- """Use an LLM to generate or iterate on Output code from a natural language prompt."""
- try:
- import anthropic
- except ImportError:
- return {
- "message": "anthropic SDK not installed. Install with: pip install anthropic",
- "frontend_code": body.current_frontend_code,
- "backend_code": body.current_backend_code,
- "input_schema": body.current_schema,
- }
-
- context_parts = []
- if body.current_frontend_code:
- context_parts.append(f"Current frontend code:\n```html\n{body.current_frontend_code}\n```")
- if body.current_backend_code:
- context_parts.append(f"Current backend code:\n```python\n{body.current_backend_code}\n```")
- if body.current_schema:
- context_parts.append(f"Current input schema:\n```json\n{body.current_schema}\n```")
- if body.name:
- context_parts.append(f"Current name: {body.name}")
- if body.description:
- context_parts.append(f"Current description: {body.description}")
-
- user_message = body.prompt
- if context_parts:
- user_message = "\n\n".join(context_parts) + "\n\nUser request: " + body.prompt
-
- from backend.apps.agents.providers.registry import resolve_aux_model
- try:
- aux_model, _aux_base = await resolve_aux_model(load_settings(), preferred_tier="sonnet")
- except ValueError as e:
- return {
- "message": f"Error: {str(e)}",
- "frontend_code": body.current_frontend_code,
- "backend_code": body.current_backend_code,
- "input_schema": body.current_schema,
- }
- client = _get_anthropic_client(aux_model)
- try:
- resp = await client.messages.create(
- model=aux_model,
- max_tokens=8000,
- system=VIBE_CODE_SYSTEM_PROMPT,
- messages=[{"role": "user", "content": user_message}],
- )
- from backend.apps.agents.agent_manager import _safe_resp_text
- raw = _safe_resp_text(resp).strip()
- if not raw:
- return {
- "message": "Aux model returned no content. Please try again.",
- "frontend_code": body.current_frontend_code,
- "backend_code": body.current_backend_code,
- "input_schema": body.current_schema,
- }
- if raw.startswith("```"):
- raw = raw.split("\n", 1)[1] if "\n" in raw else raw[3:]
- if raw.endswith("```"):
- raw = raw[:-3]
-
- result = json.loads(raw)
- pass
- return {
- "message": result.get("message", "View updated."),
- "frontend_code": result.get("frontend_code", body.current_frontend_code),
- "backend_code": result.get("backend_code", body.current_backend_code),
- "input_schema": result.get("input_schema", body.current_schema),
- "name": result.get("name", body.name),
- "description": result.get("description", body.description),
- }
- except json.JSONDecodeError:
- return {
- "message": "I generated code but couldn't parse the response. Please try again.",
- "frontend_code": body.current_frontend_code,
- "backend_code": body.current_backend_code,
- "input_schema": body.current_schema,
- }
- except Exception as e:
- logger.exception("Vibe code generation failed")
- return {
- "message": f"Error: {str(e)}",
- "frontend_code": body.current_frontend_code,
- "backend_code": body.current_backend_code,
- "input_schema": body.current_schema,
- }
-
-
AUTO_RUN_SYSTEM_PROMPT = """\
You generate structured JSON data matching a given schema.
The user provides a prompt describing what data to generate and a JSON Schema.
diff --git a/backend/apps/service/service.py b/backend/apps/service/service.py
index 1ef21098..01e297ed 100644
--- a/backend/apps/service/service.py
+++ b/backend/apps/service/service.py
@@ -3,8 +3,7 @@
Replaces the former analytics SubApp with operationally-named endpoints
and lifecycle management. Responsibilities:
- - Usage-summary and cost-breakdown endpoints (user-facing, for the
- Settings / Usage page)
+ - Usage-summary endpoint (user-facing, for the Settings / Usage page)
- Background heartbeat that reports operational state to the cloud
- 9Router auto-start for OpenSwarm Pro users
- Frontend event endpoint (`POST /api/service/event`)
@@ -364,31 +363,6 @@ async def usage_summary():
}
-@service.router.get("/cost-breakdown")
-async def cost_breakdown(period: str = "7d"):
- from backend.apps.nine_router import get_usage_stats, is_running as _9r_running
- if not _9r_running():
- return {"available": False, "by_model": {}, "by_provider": {}}
- stats = await get_usage_stats(period)
- if not stats:
- return {"available": False, "by_model": {}, "by_provider": {}}
- return {
- "available": True,
- "period": period,
- "total_cost": stats.get("totalCost", 0),
- "total_requests": stats.get("totalRequests", 0),
- "total_prompt_tokens": stats.get("totalPromptTokens", 0),
- "total_completion_tokens": stats.get("totalCompletionTokens", 0),
- "by_model": stats.get("byModel", {}),
- "by_provider": stats.get("byProvider", {}),
- }
-
-
-@service.router.get("/status")
-async def service_status():
- return {"status": "ok", "enabled": True}
-
-
# ---------------------------------------------------------------------------
# Frontend event endpoints
# ---------------------------------------------------------------------------
@@ -422,9 +396,3 @@ async def post_event(body: dict):
"p": body.get("props") or body.get("properties") or {},
})
return {"ok": True}
-
-
-@service.router.get("/spool/count")
-async def spool_count():
- from backend.apps.service import buffer
- return {"pending": buffer.count(svc._spool_path())}
diff --git a/backend/apps/settings/settings.py b/backend/apps/settings/settings.py
index ce98b218..86ac01e5 100644
--- a/backend/apps/settings/settings.py
+++ b/backend/apps/settings/settings.py
@@ -200,11 +200,6 @@ async def update_settings(body: AppSettings):
return {"ok": True, "settings": body.model_dump()}
-@settings.router.get("/default-system-prompt")
-async def get_default_system_prompt():
- return {"default_system_prompt": DEFAULT_SYSTEM_PROMPT}
-
-
@settings.router.post("/reset-system-prompt")
async def reset_system_prompt():
current = load_settings()
diff --git a/backend/main.py b/backend/main.py
index 7d5b5186..7532374e 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -1,6 +1,5 @@
import logging
import os
-from uuid import uuid4
logger = logging.getLogger(__name__)
@@ -273,24 +272,6 @@ async def websocket_dashboard(websocket: WebSocket):
ws_manager.disconnect_global(websocket)
-@app.post("/api/browser/command")
-async def browser_command(request: Request):
- """HTTP endpoint called by the browser MCP server subprocess.
- Proxies commands to the frontend via WebSocket and waits for results."""
- body = await request.json()
- action = body.get("action", "")
- browser_id = body.get("browser_id", "")
- tab_id = body.get("tab_id", "")
- params = body.get("params", {})
-
- if not action or not browser_id:
- return JSONResponse({"error": "action and browser_id are required"}, status_code=400)
-
- request_id = uuid4().hex
- result = await ws_manager.send_browser_command(request_id, action, browser_id, params, tab_id=tab_id)
- return JSONResponse(result)
-
-
@app.get("/api/subscriptions/pending/{state}")
async def subscriptions_pending(state: str):
"""Return pending OAuth data for a state param. Called by 9Router's callback page."""
diff --git a/backend/tests/test_api_agents.py b/backend/tests/test_api_agents.py
index 87e04751..c606a26f 100644
--- a/backend/tests/test_api_agents.py
+++ b/backend/tests/test_api_agents.py
@@ -8,7 +8,6 @@ Tests:
succeeds; DELETE removes it.
- POST /sessions/{id}/edit_message validates input.
- PATCH /sessions/{id} updates allowed fields (name, system_prompt).
- - GET /sessions/{id}/branches returns the default 'main' branch.
- GET /history returns paginated results.
"""
@@ -127,20 +126,6 @@ def test_patch_session_updates_name(client, stub_agent_loop):
assert fetched["model"] == "sonnet" # not changed by the PATCH
-def test_get_branches_returns_main(client, stub_agent_loop):
- launch = client.post(
- "/api/agents/launch",
- json={"name": "B", "model": "sonnet", "mode": "agent"},
- )
- session_id = launch.json()["session_id"]
-
- resp = client.get(f"/api/agents/sessions/{session_id}/branches")
- assert resp.status_code == 200
- body = resp.json()
- assert body["active_branch_id"] == "main"
- assert "main" in body["branches"]
-
-
def test_history_endpoint_returns_paginated_shape(client):
"""`/history` is the search-and-resume endpoint. Even with no
saved sessions it should return the paginated wrapper without
diff --git a/backend/tests/test_api_agents_subscriptions.py b/backend/tests/test_api_agents_subscriptions.py
index cf42e037..929d74d2 100644
--- a/backend/tests/test_api_agents_subscriptions.py
+++ b/backend/tests/test_api_agents_subscriptions.py
@@ -15,7 +15,6 @@ chatter from these tests. Two fixtures here:
Coverage:
- GET /subscriptions/status — both down + up shapes.
- - GET /subscriptions/models — both down + up shapes.
- POST /subscriptions/connect — missing provider → 400; 503 when
9Router is unavailable; happy path
round-trips the device_code flow.
@@ -126,25 +125,6 @@ def test_status_when_9router_up(client, nine_router_up):
assert isinstance(body["models"], list)
-# ---------------------------------------------------------------------------
-# GET /subscriptions/models
-# ---------------------------------------------------------------------------
-
-
-def test_models_when_9router_down(client, nine_router_down):
- resp = client.get("/api/agents/subscriptions/models")
- assert resp.status_code == 200
- assert resp.json() == {"models": []}
-
-
-def test_models_when_9router_up(client, nine_router_up):
- resp = client.get("/api/agents/subscriptions/models")
- assert resp.status_code == 200
- body = resp.json()
- assert isinstance(body["models"], list)
- assert len(body["models"]) == 1
-
-
# ---------------------------------------------------------------------------
# POST /subscriptions/connect
# ---------------------------------------------------------------------------
diff --git a/backend/tests/test_api_dashboards.py b/backend/tests/test_api_dashboards.py
index 07888783..74c0d1cd 100644
--- a/backend/tests/test_api_dashboards.py
+++ b/backend/tests/test_api_dashboards.py
@@ -57,8 +57,14 @@ def test_create_get_update_delete_dashboard(client):
assert gone.status_code == 404
-def test_dashboard_layout_round_trip(client):
- """PUT layout with a sticky note and assert it survives a re-fetch."""
+def test_dashboard_layout_field_round_trip(client):
+ """PUT the `layout` field on a dashboard with a sticky note and
+ assert it survives a re-fetch via /api/dashboards/{id}.
+
+ Named `_field_` (not `_dashboard_layout_`) to make clear this exercises
+ the `layout` attribute of the live `Dashboard` model — not the legacy
+ `backend.apps.dashboard_layout` package, which was removed.
+ """
create = client.post("/api/dashboards/create", json={"name": "L"})
dashboard_id = create.json()["id"]
diff --git a/backend/tests/test_api_outputs.py b/backend/tests/test_api_outputs.py
index 0f866dad..3eefce82 100644
--- a/backend/tests/test_api_outputs.py
+++ b/backend/tests/test_api_outputs.py
@@ -13,7 +13,7 @@ Layout mirrors `outputs.py`:
- File serve (workspace + saved output, with token rewrite + _d
payload injection)
- Backend execute
- - vibe-code + auto-run (LLM-mocked)
+ - auto-run (LLM-mocked)
- auto-run-agent (stub_agent_loop + AgentConfig spy)
- Auth control mirroring test_api_agents.test_protected_route_requires_auth
"""
@@ -24,7 +24,6 @@ import base64
import json
import os
import sys
-from unittest.mock import AsyncMock, MagicMock
import pytest
@@ -52,43 +51,6 @@ def _create_output(client, **overrides) -> dict:
return resp.json()["output"]
-def _make_mock_anthropic(text_response: str) -> MagicMock:
- """Build a mock Anthropic client.
-
- The route does `await client.messages.create(...)` then reads
- `resp.content[0].text`. Mirror that shape.
- """
- fake_resp = MagicMock()
- fake_resp.content = [MagicMock(text=text_response)]
- client_mock = MagicMock()
- client_mock.messages.create = AsyncMock(return_value=fake_resp)
- return client_mock
-
-
-def _patch_anthropic(monkeypatch, text_response: str) -> MagicMock:
- """Hook `_get_anthropic_client` in outputs.py to return a mock that
- responds with `text_response` on every messages.create call.
-
- Returns the inner mock client so tests can assert call args.
- """
- from backend.apps.outputs import outputs as outputs_mod
-
- mock_client = _make_mock_anthropic(text_response)
- monkeypatch.setattr(outputs_mod, "_get_anthropic_client", lambda: mock_client)
- return mock_client
-
-
-def _patch_aux_model(monkeypatch, model_id: str = "claude-haiku-fake") -> None:
- """Stub `resolve_aux_model` on the registry so vibe-code/auto-run never
- try to inspect the user's actual model connections."""
- from backend.apps.agents.providers import registry
-
- async def _fake(_settings, preferred_tier="haiku"):
- return (model_id, None)
-
- monkeypatch.setattr(registry, "resolve_aux_model", _fake)
-
-
# ---------------------------------------------------------------------------
# CRUD + legacy migration
# ---------------------------------------------------------------------------
@@ -554,45 +516,6 @@ def test_execute_no_backend_code_returns_none_result(client):
assert body["error"] is None
-# ---------------------------------------------------------------------------
-# /vibe-code (Anthropic mocked)
-# ---------------------------------------------------------------------------
-
-
-def test_vibe_code_resolver_raises_value_error_returns_graceful(client, monkeypatch):
- """resolve_aux_model raises ValueError when no model is connected.
- The route catches and returns a graceful error response without
- touching the Anthropic client."""
- from backend.apps.agents.providers import registry
-
- async def _raise(_settings, preferred_tier="haiku"):
- raise ValueError("no aux model")
-
- monkeypatch.setattr(registry, "resolve_aux_model", _raise)
-
- resp = client.post(
- "/api/outputs/vibe-code",
- json={"prompt": "x", "current_frontend_code": ""},
- )
- body = resp.json()
- assert "no aux model" in body["message"]
- assert body["frontend_code"] == ""
-
-
-def test_vibe_code_anthropic_import_error(client, monkeypatch):
- """Forcing `import anthropic` to fail returns the install hint without
- touching the model registry at all."""
- monkeypatch.setitem(sys.modules, "anthropic", None)
-
- resp = client.post(
- "/api/outputs/vibe-code",
- json={"prompt": "x", "current_frontend_code": ""},
- )
- body = resp.json()
- assert "anthropic SDK not installed" in body["message"]
- assert body["frontend_code"] == ""
-
-
# ---------------------------------------------------------------------------
# /auto-run (Anthropic mocked)
# ---------------------------------------------------------------------------
diff --git a/backend/tests/test_api_settings.py b/backend/tests/test_api_settings.py
index c9f39f6b..890fbdee 100644
--- a/backend/tests/test_api_settings.py
+++ b/backend/tests/test_api_settings.py
@@ -8,7 +8,7 @@ which we exercise here.
Tests:
- GET /api/settings returns defaults including the canned system prompt
- PUT /api/settings round-trips a value and survives `load_settings()` reload
- - default-system-prompt + reset-system-prompt
+ - reset-system-prompt
- browse-directories on a tmp dir
"""
@@ -46,14 +46,6 @@ def test_put_round_trips_value(client):
assert load_settings().theme == "light"
-def test_default_system_prompt_returns_constant(client):
- from backend.apps.settings.models import DEFAULT_SYSTEM_PROMPT
-
- resp = client.get("/api/settings/default-system-prompt")
- assert resp.status_code == 200
- assert resp.json()["default_system_prompt"] == DEFAULT_SYSTEM_PROMPT
-
-
def test_reset_system_prompt(client):
"""Mutate then reset; the prompt must snap back to DEFAULT_SYSTEM_PROMPT."""
from backend.apps.settings.models import DEFAULT_SYSTEM_PROMPT
diff --git a/backend/tests/test_phase1_stress.py b/backend/tests/test_phase1_stress.py
index 26c468eb..efbb341c 100644
--- a/backend/tests/test_phase1_stress.py
+++ b/backend/tests/test_phase1_stress.py
@@ -200,7 +200,10 @@ def test_reconcile_idempotent():
# ---------------------------------------------------------------------------
-def test_dashboard_layout_notes_round_trip():
+def test_dashboard_layout_model_notes_round_trip():
+ """`DashboardLayout` (Pydantic) with notes round-trips through model_dump
+ and model_validate. Named `_model_` to disambiguate from the long-removed
+ `backend.apps.dashboard_layout` package."""
from backend.apps.dashboards.models import DashboardLayout, NotePosition
n = NotePosition(note_id="n1", x=100, y=200, content="todo: ship",
@@ -215,8 +218,9 @@ def test_dashboard_layout_notes_round_trip():
assert rehydrated.notes["n1"].color == "yellow"
-def test_dashboard_layout_legacy_no_notes():
- """Older dashboard JSON without 'notes' must still load cleanly."""
+def test_dashboard_layout_model_legacy_no_notes():
+ """Older dashboard JSON without 'notes' must still load cleanly into the
+ live `DashboardLayout` Pydantic model from `backend.apps.dashboards.models`."""
from backend.apps.dashboards.models import DashboardLayout
legacy = {
diff --git a/backend/tests/test_service.py b/backend/tests/test_service.py
index 9b98bce8..acc0f2c7 100644
--- a/backend/tests/test_service.py
+++ b/backend/tests/test_service.py
@@ -342,12 +342,3 @@ async def test_endpoint_event_missing_surface(sink):
assert res["ok"] is False
-@pytest.mark.asyncio
-async def test_endpoint_spool_count(tmp_path):
- from backend.apps.service import client as svc, buffer
- from backend.apps.service.service import spool_count
- spool = str(tmp_path / "spool.db")
- with patch.object(svc, "_spool_path", lambda: spool):
- buffer.enqueue(spool, "s:/x", {}, now=time.time())
- result = await spool_count()
- assert result == {"pending": 1}