feat: implement repository clone mode with SSH key support

- Add clone_mode and branch fields to tool_instances
- Add ssh_key_id to git_repositories for per-repo SSH key assignment
- Implement host-side git cloning with branch selection (default: main)
- Mount SSH keys into containers for git operations in clone mode
- Add dirty state check on clone-mode instance deletion with confirmation
- Update SessionsPage with mount/clone selector, branch input, SSH key display
- Add SSH key selector to repository creation form
- Add dirty delete confirmation modal with changed files list
- Update API schemas and endpoints for new fields
- Sync delta specs to main specs (git-repo, tool-instances, repo-clone-mode)
- Archive completed OpenSpec change: repo-clone-mode-with-ssh
- Document git requirement for custom tool types

Quality gates: Frontend typecheck and build passed
OpenSpec: repo-clone-mode-with-ssh archived with all tasks complete
This commit is contained in:
Fusion
2026-05-22 22:56:35 +02:00
parent 952a9f3234
commit 063a839790
95 changed files with 2002 additions and 392 deletions
+14
View File
@@ -8,6 +8,7 @@ export interface GitRepository {
owner_id: string;
is_mirror: boolean;
remote_url: string | null;
ssh_key_id: string | null;
last_push: string | null;
created_at: string | null;
}
@@ -16,6 +17,7 @@ export interface GitRepositoryCreate {
name: string;
remote_url?: string;
force_original_url?: boolean;
ssh_key_id?: string;
}
export interface URLParseResult {
@@ -50,6 +52,18 @@ export async function deleteRepository(projectId: string, repoId: string): Promi
await apiClient.delete(`/projects/${projectId}/repositories/${repoId}`);
}
export async function updateRepositorySshKey(
projectId: string,
repoId: string,
sshKeyId: string | null
): Promise<GitRepository> {
const response = await apiClient.patch(
`/projects/${projectId}/repositories/${repoId}/ssh-key`,
{ ssh_key_id: sshKeyId }
);
return response.data;
}
export interface CommitHistoryEntry {
hash: string;
short_hash: string;
+11 -3
View File
@@ -27,6 +27,8 @@ export interface Session {
url: string | null;
container_status?: string;
probe_status?: string;
clone_mode?: string;
branch?: string | null;
}
export async function listInstances(
@@ -43,13 +45,17 @@ export async function createInstance(
projectId: string,
repoId: string,
toolTypeId: string,
displayName?: string
displayName?: string,
cloneMode?: string,
branch?: string
): Promise<ToolInstance> {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances`,
{
tool_type_id: toolTypeId,
display_name: displayName,
clone_mode: cloneMode || "mount",
branch: branch || undefined,
}
);
return response.data;
@@ -91,10 +97,12 @@ export async function restartInstance(
export async function deleteInstance(
projectId: string,
repoId: string,
instanceId: string
instanceId: string,
force?: boolean
): Promise<void> {
await apiClient.delete(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}`
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}`,
{ params: { force } }
);
}