From e8e182f85f378b10f99b88fe68b7ad8677282477 Mon Sep 17 00:00:00 2001 From: Adamulek123 Date: Sat, 31 Oct 2026 17:41:36 +0100 Subject: [PATCH 1/8] fix(web): identify failed composer attachments --- .../orchestration-v2/AttachmentClaims.test.ts | 10 ++----- .../src/orchestration-v2/AttachmentClaims.ts | 6 ++++ apps/server/src/ws.ts | 6 ++++ .../chat/ComposerAttachmentStrip.tsx | 6 ++++ apps/web/src/components/chat/chatViewSend.ts | 2 ++ .../src/components/chat/useComposerSend.ts | 6 ++++ .../components/chat/useMultipleModelSend.ts | 11 ++++++- .../composerContextPresentation.tsx | 2 ++ .../web/src/lib/attachmentUploadQueue.test.ts | 24 +++++++++++++++ apps/web/src/lib/attachmentUploadQueue.ts | 30 +++++++++++++++++++ packages/contracts/src/orchestrationV2.ts | 2 ++ 11 files changed, 97 insertions(+), 8 deletions(-) diff --git a/apps/server/src/orchestration-v2/AttachmentClaims.test.ts b/apps/server/src/orchestration-v2/AttachmentClaims.test.ts index a67f7ee180db7..64d00d6949c84 100644 --- a/apps/server/src/orchestration-v2/AttachmentClaims.test.ts +++ b/apps/server/src/orchestration-v2/AttachmentClaims.test.ts @@ -149,18 +149,14 @@ describe("AttachmentClaims", () => { const mismatched = { ...staged, sizeBytes: 999 }; const config = yield* ServerConfig.ServerConfig; - const result = yield* Effect.exit( + const failure = yield* Effect.flip( claimPendingAttachments({ threadId: "thread-claims-4", attachments: [good, mismatched], }), ); - - expect(result._tag).toBe("Failure"); - if (result._tag === "Failure") { - const error = result.cause; - expect(String(error)).toContain("stored size does not match"); - } + expect(failure.message).toContain("stored size does not match"); + expect(failure.attachmentId).toBe(mismatched.id); const claimedFiles = NodeFS.readdirSync(config.attachmentsDir).filter((entry) => entry.startsWith("thread-claims-4-"), ); diff --git a/apps/server/src/orchestration-v2/AttachmentClaims.ts b/apps/server/src/orchestration-v2/AttachmentClaims.ts index be48ef68b5559..2bc018ea07bbe 100644 --- a/apps/server/src/orchestration-v2/AttachmentClaims.ts +++ b/apps/server/src/orchestration-v2/AttachmentClaims.ts @@ -21,6 +21,7 @@ export class AttachmentClaimError extends Schema.TaggedError new AttachmentClaimError({ + attachmentId: attachment.id, message: `Attachment '${attachment.name}' cannot be sent: attachment not found.`, cause, }), @@ -113,6 +116,7 @@ export const claimPendingAttachments = Effect.fn("AttachmentClaims.claimPendingA ); if (Number(info.size) !== attachment.sizeBytes) { return yield* new AttachmentClaimError({ + attachmentId: attachment.id, message: `Attachment '${attachment.name}' cannot be sent: stored size does not match.`, }); } @@ -133,6 +137,7 @@ export const claimPendingAttachments = Effect.fn("AttachmentClaims.claimPendingA }); if (expectedPath !== claim.finalPath) { return yield* new AttachmentClaimError({ + attachmentId: attachment.id, message: `Attachment '${attachment.name}' cannot be sent: attachment type does not match the upload.`, }); } @@ -142,6 +147,7 @@ export const claimPendingAttachments = Effect.fn("AttachmentClaims.claimPendingA Effect.mapError( (cause) => new AttachmentClaimError({ + attachmentId: attachment.id, message: `Failed to claim attachment '${attachment.name}' for this thread.`, cause, }), diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 4781fcf78dde6..74c3e1309accd 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -1911,6 +1911,9 @@ export const layerWsRpc = ( commandType: command.type, message: detail ?? "Failed to dispatch orchestration V2 command", ...(detail === undefined ? {} : { detail }), + ...(cause._tag === "AttachmentClaimError" && cause.attachmentId !== undefined + ? { attachmentId: cause.attachmentId } + : {}), cause, }); }), @@ -2101,6 +2104,9 @@ export const layerWsRpc = ( commandId: input.commandId, projectId: input.projectId, message: cause.message, + ...(cause.attachmentId !== undefined + ? { attachmentId: cause.attachmentId } + : {}), cause, }), ThreadLaunchError: (cause) => diff --git a/apps/web/src/components/chat/ComposerAttachmentStrip.tsx b/apps/web/src/components/chat/ComposerAttachmentStrip.tsx index e3560312925e5..fddef5d3db2fa 100644 --- a/apps/web/src/components/chat/ComposerAttachmentStrip.tsx +++ b/apps/web/src/components/chat/ComposerAttachmentStrip.tsx @@ -230,6 +230,12 @@ export function ComposerAttachmentMediaRow({ {image.name} )} + {upload?.status === "failed" && ( +