resolve task outputs from user-scoped storage

This commit is contained in:
4pmtong 2026-06-26 19:12:34 +08:00
parent d9fc758e0a
commit 1fa65f3cf3
4 changed files with 193 additions and 28 deletions

View file

@ -633,6 +633,51 @@ export class FileReader {
}
}
private sanitizeIdentity(identity: string): string {
return identity
.split('@')[0]
.replace(/[\\/*?:"<>|\s]/g, '_')
.replace(/^\.+|\.+$/g, '');
}
private getStorageIdentityCandidates(
email: string,
userId?: string | number | null
): string[] {
const candidates = [this.sanitizeIdentity(email)];
if (userId !== undefined && userId !== null && userId !== '') {
const rawUserId = String(userId);
candidates.push(
this.sanitizeIdentity(
rawUserId.startsWith('user_') ? rawUserId : `user_${rawUserId}`
)
);
}
return [...new Set(candidates.filter(Boolean))];
}
private findProjectTaskPath(
userHome: string,
rootDir: 'eigent' | '.eigent',
identities: string[],
projectId: string,
taskId: string
): string | null {
for (const identity of identities) {
const taskPath = path.join(
userHome,
rootDir,
identity,
`project_${projectId}`,
`task_${taskId}`
);
if (fs.existsSync(taskPath)) {
return taskPath;
}
}
return null;
}
private findTaskInProjects(userDir: string, taskId: string): string | null {
try {
if (!fs.existsSync(userDir)) {
@ -663,35 +708,50 @@ export class FileReader {
private resolveTaskPaths(
email: string,
taskId: string,
projectId?: string
projectId?: string,
userId?: string | number | null
): {
dirPath: string;
logPath: string;
} {
const safeEmail = email
.split('@')[0]
.replace(/[\\/*?:"<>|\s]/g, '_')
.replace(/^\.+|\.+$/g, '');
const identities = this.getStorageIdentityCandidates(email, userId);
const safeEmail = identities[0] || this.sanitizeIdentity(email);
const userHome = app.getPath('home');
let dirPath: string;
let logPath: string;
if (projectId) {
dirPath = path.join(
userHome,
'eigent',
safeEmail,
`project_${projectId}`,
`task_${taskId}`
);
logPath = path.join(
userHome,
'.eigent',
safeEmail,
`project_${projectId}`,
`task_${taskId}`
);
dirPath =
this.findProjectTaskPath(
userHome,
'eigent',
identities,
projectId,
taskId
) ||
path.join(
userHome,
'eigent',
safeEmail,
`project_${projectId}`,
`task_${taskId}`
);
logPath =
this.findProjectTaskPath(
userHome,
'.eigent',
identities,
projectId,
taskId
) ||
path.join(
userHome,
'.eigent',
safeEmail,
`project_${projectId}`,
`task_${taskId}`
);
return { dirPath, logPath };
}
@ -723,12 +783,14 @@ export class FileReader {
public getFileList(
email: string,
taskId: string,
projectId?: string
projectId?: string,
userId?: string | number | null
): FileInfo[] {
const { dirPath, logPath } = this.resolveTaskPaths(
email,
taskId,
projectId
projectId,
userId
);
const camelLogPath = path.join(logPath, 'camel_logs');

View file

@ -1934,9 +1934,15 @@ function registerIpcHandlers() {
ipcMain.handle(
'get-file-list',
async (_, email: string, taskId: string, projectId?: string) => {
async (
_,
email: string,
taskId: string,
projectId?: string,
userId?: string | number | null
) => {
const manager = checkManagerInstance(fileReader, 'FileReader');
return manager.getFileList(email, taskId, projectId);
return manager.getFileList(email, taskId, projectId, userId);
}
);

View file

@ -321,6 +321,7 @@ interface UploadOutcome {
success: boolean;
fileName: string;
source: UploadFileSource;
response?: unknown;
error?: unknown;
}
@ -484,7 +485,8 @@ export function collectTaskUploadFiles(
generatedFiles: GeneratedUploadFile[],
messages: Message[],
pendingAttaches: File[] = [],
taskId = 'unknown_task'
taskId = 'unknown_task',
taskOutputFiles: FileInfo[] = []
): UploadCandidate[] {
const uploadCandidates: Array<
Omit<UploadCandidate, 'uploadName'> & { relativePath?: string }
@ -500,6 +502,28 @@ export function collectTaskUploadFiles(
});
}
for (const file of taskOutputFiles) {
if (!file?.path || !file?.name || file.isFolder) continue;
if (!isReadableLocalPath(file.path)) continue;
uploadCandidates.push({
path: file.path,
name: file.name,
relativePath: file.relativePath,
source: 'project_output',
});
}
for (const file of messages.flatMap((message) => message.fileList || [])) {
if (!file?.path || !file?.name || file.isFolder) continue;
if (!isReadableLocalPath(file.path)) continue;
uploadCandidates.push({
path: file.path,
name: file.name,
relativePath: file.relativePath,
source: 'project_output',
});
}
const attachmentFiles = [
...messages.flatMap((message) => message.attaches || []),
...pendingAttaches,
@ -573,12 +597,21 @@ async function uploadTaskFiles(
// TODO(file): rename endpoint to use project_id
formData.append('task_id', uploadTargetId);
await uploadFile('/api/v1/chat/files/upload', formData);
console.log('File uploaded successfully:', file.uploadName, file.source);
const uploadResponse = await uploadFile(
'/api/v1/chat/files/upload',
formData
);
console.log('File uploaded successfully:', {
fileName: file.uploadName,
source: file.source,
uploadTargetId,
response: uploadResponse,
});
results.push({
success: true,
fileName: file.uploadName,
source: file.source,
response: uploadResponse,
});
} catch (error) {
console.error('File upload failed:', file.uploadName, file.source, error);
@ -1414,6 +1447,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
cloud_model_type,
codex_model_type,
email,
user_id,
} = getAuthStore();
const workerList = getWorkerList();
const { getLastUserMessage: _getLastUserMessage } = get();
@ -3510,14 +3544,28 @@ const chatStore = (initial?: Partial<ChatStore>) =>
'get-file-list',
email,
currentTaskId,
uploadTargetId
uploadTargetId,
user_id
)) as GeneratedUploadFile[]) || [];
const taskOutputFiles = tasks[
currentTaskId
].taskAssigning.flatMap((agent) =>
agent.tasks.flatMap((task) => task.fileList || [])
);
const filesToUpload = collectTaskUploadFiles(
generatedFiles,
tasks[currentTaskId].messages,
tasks[currentTaskId].attaches,
currentTaskId
currentTaskId,
taskOutputFiles
);
console.log('Task upload files collected:', {
generatedFileCount: generatedFiles.length,
taskOutputFileCount: taskOutputFiles.length,
uploadCandidateCount: filesToUpload.length,
uploadTargetId,
taskId: currentTaskId,
});
if (filesToUpload.length > 0) {
const uploadResults = await uploadTaskFiles(

View file

@ -332,6 +332,55 @@ describe('ChatStore - Core Functionality', () => {
},
]);
});
it('collects generated files from task output file lists', () => {
const uploadFiles = collectTaskUploadFiles([], [], [], 'task-789', [
{
path: '/Users/test/.eigent/user_1/space_x/index.html',
name: 'index.html',
type: 'html',
},
{
path: 'https://example.com/files/remote.html',
name: 'remote.html',
type: 'html',
},
] as any);
expect(uploadFiles).toEqual([
{
path: '/Users/test/.eigent/user_1/space_x/index.html',
name: 'index.html',
uploadName: 'project_output/index.html',
source: 'project_output',
},
]);
});
it('keeps camel log upload names nested under camel_log', () => {
const uploadFiles = collectTaskUploadFiles(
[
{
path: '/Users/test/.eigent/user_1/project_p/task_t/camel_logs/agent/conv.json',
name: 'conv.json',
relativePath: 'agent',
source: 'camel_log',
},
],
[],
[],
'task-123'
);
expect(uploadFiles).toEqual([
{
path: '/Users/test/.eigent/user_1/project_p/task_t/camel_logs/agent/conv.json',
name: 'conv.json',
uploadName: 'camel_log/agent/conv.json',
source: 'camel_log',
},
]);
});
});
describe('Cloud Model Platform Mapping', () => {