(fileName: string, chatId: string)
| 36 | } |
| 37 | |
| 38 | async function executeSave(fileName: string, chatId: string): Promise<ToolCallResult> { |
| 39 | const row = await findMothershipUploadRowByChatAndName(chatId, fileName) |
| 40 | if (!row) { |
| 41 | return { |
| 42 | success: false, |
| 43 | error: `Upload not found: "${fileName}". Use glob("uploads/*") to list available uploads.`, |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | const [updated] = await db |
| 48 | .update(workspaceFiles) |
| 49 | .set({ context: 'workspace', chatId: null, originalName: row.displayName ?? row.originalName }) |
| 50 | .where(and(eq(workspaceFiles.id, row.id), isNull(workspaceFiles.deletedAt))) |
| 51 | .returning({ id: workspaceFiles.id, originalName: workspaceFiles.originalName }) |
| 52 | |
| 53 | if (!updated) { |
| 54 | return { |
| 55 | success: false, |
| 56 | error: `Upload not found: "${fileName}". Use glob("uploads/*") to list available uploads.`, |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | logger.info('Materialized file', { fileName, fileId: updated.id, chatId }) |
| 61 | |
| 62 | // Canonical, per-segment-encoded path — matches how the workspace VFS serves |
| 63 | // the file (files/<encoded>), rather than echoing the raw display name. |
| 64 | const canonicalPath = canonicalWorkspaceFilePath({ |
| 65 | folderPath: null, |
| 66 | name: updated.originalName, |
| 67 | }) |
| 68 | |
| 69 | return { |
| 70 | success: true, |
| 71 | output: { |
| 72 | message: `File "${updated.originalName}" materialized. It is now available at ${canonicalPath} and will persist independently of this chat.`, |
| 73 | fileId: updated.id, |
| 74 | path: canonicalPath, |
| 75 | }, |
| 76 | resources: [{ type: 'file', id: updated.id, title: updated.originalName }], |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | async function executeImport( |
| 81 | fileName: string, |
no test coverage detected