feat: complete working-copies workspace-first cleanup

- Remove clone_mode/branch/new_branch from frontend create session flow.
- Add workspace picker to CreateSessionForm; auto-create default workspace when repo selected.
- Fix tool-starter.tsx and use-start-tool.ts createInstance signatures after API change.
- Remove clone mode badge from SessionCard.
- Delete stale backend unit tests referencing removed clone_mode schema fields.
- Update OpenSpec working-copies tasks and mark change completed.
- Regenerate project maps.

Quality gates: npm run typecheck, npm run lint, npm test -- --run (82 passed),
python3 -m py_compile on changed backend files.
This commit is contained in:
Developer
2026-06-12 15:47:26 +00:00
parent 3da7ea6408
commit 6b947b7593
32 changed files with 182 additions and 1386 deletions
@@ -13,6 +13,8 @@ import {
listConfigProfiles,
type ConfigProfile,
} from "../../../api/config-profiles";
import { listWorkspaces, createWorkspace } from "../../../api/workspaces";
import type { Workspace } from "../../../types/workspace";
interface CreateSessionFormProps {
projects: Project[];
@@ -54,6 +56,10 @@ export const CreateSessionForm = ({
const [selectedConfigProfile, setSelectedConfigProfile] = useState("");
const [selectedSshKeyIds, setSelectedSshKeyIds] = useState<string[]>([]);
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState("");
const [isLoadingWorkspaces, setIsLoadingWorkspaces] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -94,6 +100,50 @@ export const CreateSessionForm = ({
void loadProfiles();
}, [selectedToolType, selectedProject, fixedProjectId]);
// Load workspaces when repository is selected
useEffect(() => {
const projectId = fixedProjectId || selectedProject;
const repoId = fixedRepoId || selectedRepo;
if (!projectId || !repoId) {
setWorkspaces([]);
setSelectedWorkspaceId("");
return;
}
const loadWorkspaces = async () => {
setIsLoadingWorkspaces(true);
try {
const data = await listWorkspaces(projectId, repoId);
setWorkspaces(data);
if (data.length > 0) {
setSelectedWorkspaceId(data[0].id);
} else {
// Auto-create a default workspace so the user can start a tool
const workspace = await createWorkspace(projectId, repoId, {
name: "default",
branch: "main",
});
setWorkspaces([workspace]);
setSelectedWorkspaceId(workspace.id);
}
} catch (err) {
setError(
err instanceof Error ? err.message : "Failed to load workspaces",
);
setWorkspaces([]);
setSelectedWorkspaceId("");
} finally {
setIsLoadingWorkspaces(false);
}
};
void loadWorkspaces();
}, [
fixedProjectId,
selectedProject,
fixedRepoId,
selectedRepo,
repositories,
]);
// Filter repositories by selected project
const availableRepos = selectedProject
? repositories.filter((r) => r.project_id === selectedProject)
@@ -106,6 +156,8 @@ export const CreateSessionForm = ({
setDisplayName("");
setSelectedSshKeyIds([]);
setSelectedConfigProfile("");
setWorkspaces([]);
setSelectedWorkspaceId("");
};
const handleSubmit = async (event: React.FormEvent) => {
@@ -123,12 +175,19 @@ export const CreateSessionForm = ({
setIsSubmitting(true);
try {
const workspaceId = selectedWorkspaceId || undefined;
if (!workspaceId) {
setError("No workspace available for the selected repository");
setIsSubmitting(false);
return;
}
const instance = await createInstance(
projectId,
repoId,
selectedToolType,
displayName || undefined,
undefined,
workspaceId,
selectedConfigProfile || undefined,
selectedSshKeyIds.length > 0 ? selectedSshKeyIds : undefined,
);
@@ -214,6 +273,7 @@ export const CreateSessionForm = ({
onChange={(e) => {
setSelectedRepo(e.target.value);
setSelectedToolType("");
setSelectedWorkspaceId("");
}}
disabled={!hasProject || isSubmitting}
>
@@ -228,6 +288,30 @@ export const CreateSessionForm = ({
</div>
)}
{/* Workspace */}
{hasRepo && (
<div className="form-field">
<label>Workspace</label>
{isLoadingWorkspaces ? (
<span className="muted">Loading workspaces...</span>
) : workspaces.length === 0 ? (
<span className="muted">No workspace available</span>
) : (
<select
value={selectedWorkspaceId}
onChange={(e) => setSelectedWorkspaceId(e.target.value)}
disabled={!hasRepo || isSubmitting || isLoadingWorkspaces}
>
{workspaces.map((w) => (
<option key={w.id} value={w.id}>
{w.name} ({w.branch})
</option>
))}
</select>
)}
</div>
)}
{/* Tool Type */}
{hasRepo && (
<div className="form-field">