feat(files): delete a file from the Files panel

A delete button on each file row of the Files panel (tree and search
results) and a DELETE /api/sessions/:id/files?path= route that removes one
regular file from the session workspace, refused for remote (SSH)
sessions and behind the same sensitive-path, attachment-guard and .git
checks as the edit route.

Salvaged from #529.
This commit is contained in:
shenlvkang-collab authored and Codeman maintainer committed 2026-11-09 03:09:46 +01:00
1 parent f62c1f7e2f
commit dfe1af2e5f
3 files changed
+86

No files matched your search

+42
View File
@@ -2000,6 +2000,48 @@ export function registerFileRoutes(app: FastifyInstance, ctx: SessionPort & Even
};
});
// Delete a regular file from the current session workspace. The same
// confinement and sensitive-path checks used by previews apply here.
app.delete('/api/sessions/:id/files', async (req, reply) => {
const { id } = req.params as { id: string };
const { path: filePath } = req.query as { path?: string };
const session = findSessionOrFail(ctx, id, req);
if (session.remote) {
reply.code(400);
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Deleting is not supported for remote (SSH) sessions');
}
if (!filePath) {
reply.code(400);
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Missing path parameter');
}
const validated = validateSessionFilePath(session.workingDir, filePath);
if (!validated) {
reply.code(404);
return createErrorResponse(ApiErrorCode.NOT_FOUND, 'File not found');
}
const { resolvedPath, relativePath } = validated;
const guard = await loadAttachmentGuardConfig();
if (
isSensitivePath(resolvedPath) ||
isBlockedAttachmentPath(resolvedPath, guard.blockedTrees) ||
isDeniedEditRelativePath(relativePath)
) {
reply.code(403);
return createErrorResponse(ApiErrorCode.FORBIDDEN, 'Deleting this file is blocked');
}
const stat = await fs.lstat(resolvedPath).catch(() => undefined);
if (!stat) {
reply.code(404);
return createErrorResponse(ApiErrorCode.NOT_FOUND, 'File not found');
}
if (!stat.isFile()) {
reply.code(400);
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Only regular files can be deleted');
}
await fs.unlink(resolvedPath);
return { success: true, data: { path: filePath } };
});
// Get file content for preview (File Browser)
app.get('/api/sessions/:id/file-content', async (req) => {
const { id } = req.params as { id: string };