Files
ai-agent/backend/newproject/routes.py
Gabriel Vidal deb01427d5 feat(backend): New projects — owner and guests start a project from a template
newproject/: the policy (guestDefaults.newProjects — quota, templates, the
ones the owner approves first, visibility, level, domain — and a per-guest
createProjects + address bucket), the New project form built from it, and the
flow: POST /api/new-project/form (the run's guesses as defaults) → submit →
shape/policy and host checks (a problem sends a corrected form back) → an
owner ask for a backend template → sidecar /scaffold → grant → /relocate +
resume the same session inside the project with an app-authored turn.
SpawnBody.newProject starts a guest's run in ~/projects/.new-project/<uid>
(staging_scope: /tmp only); /api/me reports newProjects; the thread follows
ConversationDetail.continuedIn.

Fix: a guest's turn into an existing conversation resumed with no cwd, so the
sidecar ran it in the lab checkout (its CLAUDE.md loaded) — it now resumes
where the session lives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:42:07 +02:00

46 lines
1.8 KiB
Python

"""New projects routes (see service.py for the flow)."""
from fastapi import APIRouter
from pydantic import BaseModel
import schemas
from core.http import _r
from core.state import State
from newproject import service
from users.caller import OwnerDep
router = APIRouter()
class NewProjectFormBody(BaseModel):
sessionId: str
# The run's guesses — the form's defaults (all optional, all editable).
name: str | None = None
title: str | None = None
description: str | None = None
template: str | None = None
label: str | None = None
bucket: str | None = None
visibility: str | None = None
summary: str | None = None # what the run understood, shown on top
@router.post("/api/new-project/form", responses=_r(schemas.NewProjectFormResult))
def new_project_form(deps: State, _: OwnerDep, body: NewProjectFormBody):
"""Open the *New project* form in a conversation (``notify new-project``).
Called by the run, from the host (owner-trusted); whose project it makes
is the conversation's — its guest owner, else the owner — and their policy
shapes the form. Answers go through ``service.on_form_submitted``."""
guesses = body.model_dump(exclude={"sessionId"}, exclude_none=True)
return service.open_form(deps, body.sessionId, guesses)
@router.get("/api/new-project/requests", responses=_r(schemas.NewProjectRequests))
def new_project_requests(deps: State, _: OwnerDep, limit: int = 50):
"""Every New project form and what became of it, newest first."""
keep = ("id", "sessionId", "requester", "status", "template", "slug", "url",
"repo", "visibility", "error", "createdAt", "updatedAt")
return {"requests": [{k: r.get(k) for k in keep}
for r in deps.new_projects.all()[:max(1, min(limit, 500))]]}