Files
claudecodeui/server/shared/types.ts
Haileyesus bb86236520 refactor: modularize project services, and wizard create/clone flow
Restructure project creation, listing, GitHub clone progress, and TaskMaster
details behind a dedicated TypeScript module under server/modules/projects/,
and align the client wizard with a single path-based flow.

Server / routing
- Remove server/routes/projects.js and mount server/modules/projects/
  projects.routes.ts at /api/projects (still behind authenticateToken).
- Drop duplicate handlers from server/index.js for GET /api/projects and
  GET /api/projects/:projectId/taskmaster; those live on the new router.
- Import WORKSPACES_ROOT and validateWorkspacePath from shared utils in
  index.js instead of the deleted projects route module.

Projects router (projects.routes.ts)
- GET /: list projects with sessions (existing snapshot behavior).
- POST /create-project: validate body, reject legacy workspaceType and
  mixed clone fields, delegate to createProject service, return distinct
  success copy when an archived path is reactivated.
- GET /clone-progress: Server-Sent Events for clone progress/complete/error;
  requires authenticated user id for token resolution; wires startCloneProject.
- GET /:projectId/taskmaster: delegates to getProjectTaskMaster.

Services (new)
- project-management.service.ts: path validation, workspace directory
  creation, persistence via projectsDb.createProjectPath, mapping to API
  project shape; surfaces AppError for validation, conflict, and not-found
  cases; optional dependency injection for tests.
- project-clone.service.ts: validates workspace, resolves GitHub auth
  (stored token or inline token), runs git clone with progress callbacks,
  registers project via createProject on success; sanitizes errors and
  supports cancellation; injectable dependencies for tests.
- projects-has-taskmaster.service.ts: moves TaskMaster detection and
  normalization out of server/projects.js; resolve-by-id and public
  getProjectTaskMaster with structured AppError responses.

Persistence and shared types
- projectsDb.createProjectPath now returns CreateProjectPathResult
  (created | reactivated_archived | active_conflict) using INSERT … ON
  CONFLICT with selective update when the row is archived; normalizes
  display name from path or custom name; repository row typing moves to
  shared ProjectRepositoryRow.
- getProjectPaths() returns only non-archived rows (isArchived = 0).
- shared/types.ts: ProjectRepositoryRow, CreateProjectPathResult/outcome,
  WorkspacePathValidationResult.
- shared/utils.ts: WORKSPACES_ROOT, forbidden path lists, validateWorkspacePath,
  asyncHandler for Express async routes.

Legacy cleanup
- server/projects.js: remove detectTaskMasterFolder, normalizeTaskMasterInfo,
  and getProjectTaskMasterById (logic lives in the new service).
- server/routes/agent.js: register external API project paths with
  projectsDb.createProjectPath instead of addProjectManually try/catch;
  treat active_conflict as an existing registration and continue.

Tests
- Add Node test suites for project-management, project-clone, and
  projects-has-taskmaster services; update projects.service test import
  for renamed projects-with-sessions-fetch.service.ts.

Rename
- projects.service.ts → projects-with-sessions-fetch.service.ts;
  re-export from modules/projects/index.ts.

Client (project creation wizard)
- Remove StepTypeSelection and workspaceType from form state and types;
  wizard is two steps (configure path/GitHub auth, then review).
- createWorkspaceRequest → createProjectRequest; clone vs create-only
  inferred from githubUrl (pathUtils / isCloneWorkflow).
- Adjust step indices, WizardProgress, StepConfiguration/Review,
  WorkspacePathField, and src/utils/api.js as needed for the new API.

Docs
- Minor websocket README touch-up.

Net: ~1.6k insertions / ~0.9k deletions across 29 files; behavior is
centralized in typed services with explicit HTTP errors and test seams.
2026-04-25 19:36:47 +03:00

329 lines
8.9 KiB
TypeScript

import type { IncomingMessage } from 'node:http';
//----------------- HTTP RESPONSE SHAPES ------------
/**
* Canonical success envelope used by backend APIs that return a structured payload.
*
* Use this for route handlers that need a stable `success/data` shape so frontend
* consumers can parse responses consistently across endpoints.
*/
export type ApiSuccessShape<TData = unknown> = {
success: true;
data: TData;
};
/**
* Generic plain-object record used when parsing loosely typed JSON payloads.
*
* Use this only after runtime shape checks, not as a replacement for validated
* domain models.
*/
export type AnyRecord = Record<string, any>;
// ---------------------------
//----------------- WEBSOCKET TRANSPORT TYPES ------------
/**
* Minimal websocket client contract used by backend broadcaster services.
*
* Any transport object added to `connectedClients` must implement these two
* members so shared services can safely send JSON strings and check whether the
* socket is still open before broadcasting.
*/
export type RealtimeClientConnection = {
readyState: number;
send(data: string): void;
};
/**
* Authenticated user payload attached to websocket upgrade requests.
*
* Platform and OSS auth flows currently use either `id` or `userId`; both are
* represented here so websocket handlers can resolve a stable writer user id.
*/
export type AuthenticatedWebSocketUser = {
id?: string | number;
userId?: string | number;
username?: string;
[key: string]: unknown;
};
/**
* HTTP upgrade request shape after websocket authentication succeeds.
*
* `verifyClient` populates `request.user` with the authenticated payload, and
* downstream websocket handlers rely on this extended request type.
*/
export type AuthenticatedWebSocketRequest = IncomingMessage & {
user?: AuthenticatedWebSocketUser;
};
// ---------------------------
//----------------- PROVIDER MESSAGE MODEL ------------
/**
* Providers supported by the unified server runtime.
*
* Use this as the source of truth whenever a function or payload needs to identify
* a specific LLM integration.
*/
export type LLMProvider = 'claude' | 'codex' | 'gemini' | 'cursor';
/**
* Message/event variants emitted by provider adapters and normalized transports.
*
* Keep this union in sync with event kinds produced by provider session adapters.
*/
export type MessageKind =
| 'text'
| 'tool_use'
| 'tool_result'
| 'thinking'
| 'stream_delta'
| 'stream_end'
| 'error'
| 'complete'
| 'status'
| 'permission_request'
| 'permission_cancelled'
| 'session_created'
| 'interactive_prompt'
| 'task_notification';
/**
* Provider-neutral message envelope used in REST responses and realtime channels.
*
* Every provider-specific message must be converted into this shape before being
* emitted outside provider-specific modules.
*/
export type NormalizedMessage = {
id: string;
sessionId: string;
timestamp: string;
provider: LLMProvider;
kind: MessageKind;
role?: 'user' | 'assistant';
content?: string;
images?: unknown;
toolName?: string;
toolInput?: unknown;
toolId?: string;
toolResult?: {
content?: string;
isError?: boolean;
toolUseResult?: unknown;
};
isError?: boolean;
text?: string;
tokens?: number;
canInterrupt?: boolean;
requestId?: string;
input?: unknown;
context?: unknown;
reason?: string;
newSessionId?: string;
status?: string;
summary?: string;
tokenBudget?: unknown;
subagentTools?: unknown;
toolUseResult?: unknown;
sequence?: number;
rowid?: number;
[key: string]: unknown;
};
/**
* Shared options used to fetch historical provider messages.
*
* Consumers should pass provider-specific lookup hints (`projectName`, `projectPath`)
* only when the selected provider requires them.
*/
export type FetchHistoryOptions = {
projectName?: string;
projectPath?: string;
limit?: number | null;
offset?: number;
};
/**
* Standardized response payload returned from provider history readers.
*
* Use this as the contract for APIs that return paginated conversation history.
*/
export type FetchHistoryResult = {
messages: NormalizedMessage[];
total: number;
hasMore: boolean;
offset: number;
limit: number | null;
tokenUsage?: unknown;
};
// ---------------------------
//----------------- SHARED ERROR TYPES ------------
/**
* Optional metadata used when constructing application-level errors.
*
* `statusCode` should reflect the HTTP response status, while `code` identifies
* the stable machine-readable error category.
*/
export type AppErrorOptions = {
code?: string;
statusCode?: number;
details?: unknown;
};
// ---------------------------
//----------------- MCP TYPES ------------
/**
* Scope where an MCP server definition is stored and resolved.
*
* `user` is global for a user account, `local` is provider-local, and `project`
* is tied to a specific project path.
*/
export type McpScope = 'user' | 'local' | 'project';
/**
* Transport protocol used by an MCP server definition.
*/
export type McpTransport = 'stdio' | 'http' | 'sse';
/**
* Normalized MCP server model exposed to frontend and route handlers.
*
* Provider adapters should map provider-native config to this structure before
* returning results.
*/
export type ProviderMcpServer = {
provider: LLMProvider;
name: string;
scope: McpScope;
transport: McpTransport;
command?: string;
args?: string[];
env?: Record<string, string>;
cwd?: string;
url?: string;
headers?: Record<string, string>;
envVars?: string[];
bearerTokenEnvVar?: string;
envHttpHeaders?: Record<string, string>;
};
/**
* Payload for create/update MCP server operations.
*
* Routes and services should accept this type, validate it, and then persist it
* through provider-specific MCP repositories.
*/
export type UpsertProviderMcpServerInput = {
name: string;
scope?: McpScope;
transport: McpTransport;
workspacePath?: string;
command?: string;
args?: string[];
env?: Record<string, string>;
cwd?: string;
url?: string;
headers?: Record<string, string>;
envVars?: string[];
bearerTokenEnvVar?: string;
envHttpHeaders?: Record<string, string>;
};
// ---------------------------
//----------------- PROVIDER AUTH TYPES ------------
/**
* Authentication status result returned by provider health checks.
*
* This shape is consumed by settings/status endpoints to report installation and
* credential state for each provider.
*/
export type ProviderAuthStatus = {
installed: boolean;
provider: LLMProvider;
authenticated: boolean;
email: string | null;
method: string | null;
error?: string;
};
// ---------------------------
//----------------- SHARED DATABASE CREDENTIAL TYPES ------------
/**
* Safe credential view returned by credential listing APIs.
*
* This intentionally excludes the raw credential secret while still exposing
* metadata needed for UI rendering and management operations.
*/
export type CredentialPublicRow = {
id: number;
credential_name: string;
credential_type: string;
description: string | null;
created_at: string;
is_active: number;
};
/**
* Result returned after creating a credential record.
*
* Use this return shape when callers need the created id and display metadata,
* but must never receive the stored secret value.
*/
export type CreateCredentialResult = {
id: number | bigint;
credentialName: string;
credentialType: string;
};
// ---------------------------
//----------------- PROJECT PERSISTENCE TYPES ------------
/**
* Canonical project row shape returned by the projects repository.
*
* Use this type whenever backend services need to pass around one database
* project record without leaking raw SQL row typing across modules.
*/
export type ProjectRepositoryRow = {
project_id: string;
project_path: string;
custom_project_name: string | null;
isStarred: number;
isArchived: number;
};
/**
* Result category returned by `projectsDb.createProjectPath`.
*
* `created` means a fresh row was inserted, `reactivated_archived` means an
* existing archived path was accepted and updated, and `active_conflict` means
* an already-active path blocked project creation.
*/
export type CreateProjectPathOutcome =
| 'created'
| 'reactivated_archived'
| 'active_conflict';
/**
* Structured result returned by project-path upsert operations.
*
* Services should use this result to decide whether a request succeeded,
* should return a conflict, or needs follow-up retrieval of row metadata.
*/
export type CreateProjectPathResult = {
outcome: CreateProjectPathOutcome;
project: ProjectRepositoryRow | null;
};
/**
* Validation result for user-supplied workspace/project paths.
*
* `resolvedPath` is present only when validation succeeds. `error` is present
* only when validation fails and is suitable for user-facing diagnostics.
*/
export type WorkspacePathValidationResult = {
valid: boolean;
resolvedPath?: string;
error?: string;
};