Files
fusion/packages/dashboard/app/api/projects/workspace-files.ts
gsxdsm cb57093d03 refactor: domain folder layout (types, API, core, engine) (#2398)
## Summary

Wave 17 organizes Fusion into **domain folders** (stacks on #2397).

### Layout
- **core/types/** — board, task, agents, settings, merge, workflow,
mesh, …
- **core/src/** — agents, ai, async-stores, workflows, tasks, config,
db, …
- **dashboard/app/api/** — client, tasks, agents, git, missions,
planning, …
- **engine/src/** — agents, auth, execution, merge, missions, overseer,
worktree, …

Root keepers retained for large entrypoints (`store.ts`, `executor.ts`,
`merger.ts`, …).

Public barrels (`@fusion/core`, `@fusion/engine`, `app/api.ts` → legacy)
stay stable.

## Test plan
- [x] `@fusion/core` typecheck
- [x] `@fusion/engine` typecheck (pre-existing playwright-core noise
only)
- [ ] CI merge gate

**Stack:** #2394 → #2397 → **this PR**
2026-08-03 00:20:53 -07:00

251 lines
8.4 KiB
TypeScript

/**
* FNXC:CodeOrganization 2026-07-20-10:00:
* Workspace file browser and file operations client API peeled from legacy.ts.
*/
import { api } from "../client/client.js";
import { withProjectId } from "../client/health.js";
// --- File Browser API ---
/** File node in directory listing */
export interface FileNode {
name: string;
type: "file" | "directory";
size?: number;
mtime?: string;
}
/** File listing response */
export interface FileListResponse {
path: string;
entries: FileNode[];
}
/** File content response */
export interface FileContentResponse {
content: string;
mtime: string;
size: number;
}
/** Save file response */
export interface SaveFileResponse {
success: true;
mtime: string;
size: number;
}
/** List files in task directory */
export function fetchFileList(taskId: string, path?: string, projectId?: string): Promise<FileListResponse> {
const query = path ? `?path=${encodeURIComponent(path)}` : "";
return api<FileListResponse>(withProjectId(`/tasks/${taskId}/files${query}`, projectId));
}
/** Fetch file content */
export function fetchFileContent(taskId: string, filePath: string, projectId?: string): Promise<FileContentResponse> {
return api<FileContentResponse>(withProjectId(`/tasks/${taskId}/files/${encodeURIComponent(filePath)}`, projectId));
}
/** Save file content */
export function saveFileContent(taskId: string, filePath: string, content: string, projectId?: string): Promise<SaveFileResponse> {
return api<SaveFileResponse>(withProjectId(`/tasks/${taskId}/files/${encodeURIComponent(filePath)}`, projectId), {
method: "POST",
body: JSON.stringify({ content }),
});
}
// --- Workspace File Browser API ---
export interface WorkspaceTaskInfo {
id: string;
title?: string;
worktree: string;
}
export interface WorkspaceListResponse {
project: string;
tasks: WorkspaceTaskInfo[];
}
/** Fetch available file browser workspaces. */
export function fetchWorkspaces(projectId?: string): Promise<WorkspaceListResponse> {
return api<WorkspaceListResponse>(withProjectId("/workspaces", projectId));
}
/** List files in a workspace (project root or task worktree). */
export function fetchWorkspaceFileList(workspace: string, path?: string, projectId?: string): Promise<FileListResponse> {
const query = new URLSearchParams({ workspace });
if (path) {
query.set("path", path);
}
if (projectId) {
query.set("projectId", projectId);
}
return api<FileListResponse>(`/files?${query.toString()}`);
}
/** Fetch file content from a workspace. */
export function fetchWorkspaceFileContent(workspace: string, filePath: string, projectId?: string): Promise<FileContentResponse> {
const query = new URLSearchParams({ workspace });
if (projectId) {
query.set("projectId", projectId);
}
return api<FileContentResponse>(`/files/${encodeURIComponent(filePath)}?${query.toString()}`);
}
/** Save file content to a workspace. */
export function saveWorkspaceFileContent(workspace: string, filePath: string, content: string, projectId?: string): Promise<SaveFileResponse> {
const query = new URLSearchParams({ workspace });
if (projectId) {
query.set("projectId", projectId);
}
return api<SaveFileResponse>(`/files/${encodeURIComponent(filePath)}?${query.toString()}`, {
method: "POST",
body: JSON.stringify({ content }),
});
}
/** File search result. */
export interface FileSearchResult {
files: Array<{ path: string; name: string }>;
}
export interface IssueMentionItem {
number: number;
title: string;
state: "open" | "closed";
htmlUrl: string;
repository: string;
updatedAt?: string;
}
export function fetchRecentIssues(projectId?: string, query?: string): Promise<IssueMentionItem[]> {
const params = new URLSearchParams();
if (query && query.trim()) {
params.set("q", query.trim());
}
if (projectId) {
params.set("projectId", projectId);
}
const search = params.toString();
return api<IssueMentionItem[]>(`/github/issues/recent${search ? `?${search}` : ""}`);
}
/** Search for files matching a query in a workspace. */
export function searchFiles(query: string, workspace?: string, projectId?: string): Promise<FileSearchResult> {
const params = new URLSearchParams({ q: query });
if (workspace) {
params.set("workspace", workspace);
}
if (projectId) {
params.set("projectId", projectId);
}
return api<FileSearchResult>(`/files/search?${params.toString()}`);
}
// --- Workspace File Operations API (Create, Copy, Move, Delete, Rename, Download) ---
/** File operation response for create/copy/move/delete/rename operations */
export interface FileOperationResponse {
success: true;
message?: string;
path?: string;
}
/** Create a directory within a workspace. */
export function createWorkspaceDirectory(workspace: string, dirPath: string, projectId?: string): Promise<FileOperationResponse> {
const query = new URLSearchParams({ workspace });
if (projectId) {
query.set("projectId", projectId);
}
return api<FileOperationResponse>(`/files/mkdir?${query.toString()}`, {
method: "POST",
body: JSON.stringify({ path: dirPath }),
});
}
/** Create an empty file within a workspace. */
export function createWorkspaceFile(workspace: string, filePath: string, projectId?: string): Promise<FileOperationResponse> {
const query = new URLSearchParams({ workspace });
if (projectId) {
query.set("projectId", projectId);
}
return api<FileOperationResponse>(`/files/${encodeURIComponent(filePath)}?${query.toString()}`, {
method: "POST",
body: JSON.stringify({ content: "" }),
});
}
/** Copy a file or directory to a new location within a workspace. */
export function copyFile(workspace: string, filePath: string, destination: string, projectId?: string): Promise<FileOperationResponse> {
const query = new URLSearchParams({ workspace });
if (projectId) {
query.set("projectId", projectId);
}
return api<FileOperationResponse>(`/files/${encodeURIComponent(filePath)}/copy?${query.toString()}`, {
method: "POST",
body: JSON.stringify({ destination }),
});
}
/** Move a file or directory to a new location within a workspace. */
export function moveFile(workspace: string, filePath: string, destination: string, projectId?: string): Promise<FileOperationResponse> {
const query = new URLSearchParams({ workspace });
if (projectId) {
query.set("projectId", projectId);
}
return api<FileOperationResponse>(`/files/${encodeURIComponent(filePath)}/move?${query.toString()}`, {
method: "POST",
body: JSON.stringify({ destination }),
});
}
/** Delete a file or directory within a workspace. */
export function deleteFile(workspace: string, filePath: string, projectId?: string): Promise<FileOperationResponse> {
const query = new URLSearchParams({ workspace });
if (projectId) {
query.set("projectId", projectId);
}
return api<FileOperationResponse>(`/files/${encodeURIComponent(filePath)}/delete?${query.toString()}`, {
method: "POST",
});
}
/** Rename a file or directory within a workspace. */
export function renameFile(workspace: string, filePath: string, newName: string, projectId?: string): Promise<FileOperationResponse> {
const query = new URLSearchParams({ workspace });
if (projectId) {
query.set("projectId", projectId);
}
return api<FileOperationResponse>(`/files/${encodeURIComponent(filePath)}/rename?${query.toString()}`, {
method: "POST",
body: JSON.stringify({ newName }),
});
}
/** Get the download URL for a single file in a workspace. */
export function downloadFileUrl(workspace: string, filePath: string, projectId?: string, options?: { inline?: boolean }): string {
const query = new URLSearchParams({ workspace });
if (projectId) {
query.set("projectId", projectId);
}
/**
* FNXC:FileBrowser 2026-06-26-00:00:
* Browser-native preview consumers request `inline=1` so the shared download route serves renderable MIME types with inline disposition. The explicit Download action intentionally omits this option to preserve attachment downloads.
*/
if (options?.inline === true) {
query.set("inline", "1");
}
return `/api/files/${encodeURIComponent(filePath)}/download?${query.toString()}`;
}
/** Get the download URL for a folder as ZIP in a workspace. */
export function downloadZipUrl(workspace: string, filePath: string, projectId?: string): string {
const query = new URLSearchParams({ workspace });
if (projectId) {
query.set("projectId", projectId);
}
return `/api/files/${encodeURIComponent(filePath)}/download-zip?${query.toString()}`;
}