80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
import { DATA_DIR } from './config.js';
|
|
import type { OutboundAttachment, PairedRoomRole } from './types.js';
|
|
|
|
function sanitizePathSegment(value: string, fallback: string): string {
|
|
const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, '-').slice(0, 96);
|
|
return sanitized.replace(/^[.-]+|[.-]+$/g, '') || fallback;
|
|
}
|
|
|
|
function storedAttachmentName(
|
|
attachment: OutboundAttachment,
|
|
sourcePath: string,
|
|
index: number,
|
|
): string {
|
|
const sourceBasename = path.basename(sourcePath);
|
|
const displayBasename = attachment.name
|
|
? path.basename(attachment.name)
|
|
: sourceBasename;
|
|
const ext = path.extname(displayBasename) || path.extname(sourceBasename);
|
|
const stem =
|
|
path.basename(displayBasename, path.extname(displayBasename)) ||
|
|
`attachment-${index + 1}`;
|
|
return `${String(index + 1).padStart(2, '0')}-${sanitizePathSegment(
|
|
stem,
|
|
`attachment-${index + 1}`,
|
|
)}${ext}`;
|
|
}
|
|
|
|
export function persistPairedTurnOutputAttachments(args: {
|
|
taskId: string;
|
|
turnNumber: number;
|
|
role: PairedRoomRole;
|
|
attachments: OutboundAttachment[];
|
|
}): OutboundAttachment[] {
|
|
if (args.attachments.length === 0) return [];
|
|
|
|
const safeTaskId = sanitizePathSegment(args.taskId, 'task');
|
|
const targetDir = path.join(
|
|
DATA_DIR,
|
|
'attachments',
|
|
'paired-turn-outputs',
|
|
safeTaskId,
|
|
String(args.turnNumber),
|
|
args.role,
|
|
);
|
|
|
|
return args.attachments.map((attachment, index) => {
|
|
try {
|
|
if (
|
|
!path.isAbsolute(attachment.path) ||
|
|
!fs.existsSync(attachment.path)
|
|
) {
|
|
return attachment;
|
|
}
|
|
const sourcePath = fs.realpathSync(attachment.path);
|
|
if (!fs.statSync(sourcePath).isFile()) return attachment;
|
|
|
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
const targetPath = path.join(
|
|
targetDir,
|
|
storedAttachmentName(attachment, sourcePath, index),
|
|
);
|
|
if (sourcePath !== path.resolve(targetPath)) {
|
|
fs.copyFileSync(sourcePath, targetPath);
|
|
}
|
|
return {
|
|
...attachment,
|
|
path: targetPath,
|
|
name: attachment.name
|
|
? path.basename(attachment.name)
|
|
: path.basename(targetPath),
|
|
};
|
|
} catch {
|
|
return attachment;
|
|
}
|
|
});
|
|
}
|