feat: multi-session terminal backend API + frontend client (PR 2)

- Add WebSocket route /ws/tool-instances/{instance_id}/terminal/{session_id}
- Preserve /terminal as default-session alias for backward compatibility
- Extract shared _handle_terminal_websocket handler for both routes
- Add REST endpoints: GET list, POST create, DELETE close, POST reset, POST rename
- Preserve legacy POST .../terminal/reset as default session alias
- Add frontend API client (apps/web/src/api/terminal.ts)
- Add useTerminalSessions React hook for session CRUD + state management
- Add integration tests for auth requirements on all new endpoints

Quality gates: pytest (8 new passed, 182 total passed, 51 pre-existing failures)
This commit is contained in:
2026-05-28 12:08:37 +02:00
parent b55300ff6f
commit 0b35ae3bf0
4 changed files with 822 additions and 70 deletions
+87
View File
@@ -0,0 +1,87 @@
import { apiClient } from "./client";
export interface TerminalSession {
id: string;
name: string;
status: string;
has_websockets: boolean;
created_at: string;
last_activity_at: string | null;
}
export interface TerminalSessionListResponse {
sessions: TerminalSession[];
}
export interface TerminalSessionCreateRequest {
name?: string;
}
export interface TerminalSessionCreateResponse {
id: string;
name: string;
status: string;
created_at: string;
}
export async function listTerminalSessions(
projectId: string,
repoId: string,
instanceId: string
): Promise<TerminalSession[]> {
const response = await apiClient.get(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/terminal/sessions`
);
return response.data.sessions;
}
export async function createTerminalSession(
projectId: string,
repoId: string,
instanceId: string,
name?: string
): Promise<TerminalSessionCreateResponse> {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/terminal/sessions`,
{ name }
);
return response.data;
}
export async function closeTerminalSession(
projectId: string,
repoId: string,
instanceId: string,
sessionId: string
): Promise<{ status: string; session_id: string }> {
const response = await apiClient.delete(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/terminal/sessions/${sessionId}`
);
return response.data;
}
export async function resetTerminalSession(
projectId: string,
repoId: string,
instanceId: string,
sessionId: string
): Promise<{ id: string; name: string; status: string }> {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/terminal/sessions/${sessionId}/reset`
);
return response.data;
}
export async function renameTerminalSession(
projectId: string,
repoId: string,
instanceId: string,
sessionId: string,
name: string
): Promise<{ id: string; name: string }> {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/terminal/sessions/${sessionId}/rename`,
{ name }
);
return response.data;
}