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>
46 lines
1.8 KiB
Python
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))]]}
|