From 1350753f5f6e180f7ccf5ec489bcd256e400a05e Mon Sep 17 00:00:00 2001 From: plind-dm <59729252+plind-dm@users.noreply.github.com> Date: Fri, 3 Apr 2026 01:53:57 +0900 Subject: [PATCH] fix(api): include attachment metadata in heartbeat-context response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agents receiving issue context via GET /issues/:id/heartbeat-context had no way to discover file attachments — the endpoint returned issue metadata, ancestors, project, goal, and comment cursor but omitted attachments entirely. Users attaching files through the UI would then see agents ask for documents that were already uploaded. Fetch attachments in parallel with the existing queries and append a lightweight summary (id, filename, contentType, byteSize, contentPath) to the response so agents can detect and retrieve attached files on their first heartbeat without an extra round-trip. Closes #2536 --- .../src/__tests__/issues-goal-context-routes.test.ts | 2 ++ server/src/routes/issues.ts | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/server/src/__tests__/issues-goal-context-routes.test.ts b/server/src/__tests__/issues-goal-context-routes.test.ts index 25ce2042..d8036797 100644 --- a/server/src/__tests__/issues-goal-context-routes.test.ts +++ b/server/src/__tests__/issues-goal-context-routes.test.ts @@ -10,6 +10,7 @@ const mockIssueService = vi.hoisted(() => ({ findMentionedProjectIds: vi.fn(), getCommentCursor: vi.fn(), getComment: vi.fn(), + listAttachments: vi.fn(), })); const mockProjectService = vi.hoisted(() => ({ @@ -129,6 +130,7 @@ describe("issue goal context routes", () => { latestCommentAt: null, }); mockIssueService.getComment.mockResolvedValue(null); + mockIssueService.listAttachments.mockResolvedValue([]); mockProjectService.getById.mockResolvedValue({ id: legacyProjectLinkedIssue.projectId, companyId: "company-1", diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 5eb0b83e..b7f7bdee 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -448,11 +448,12 @@ export function issueRoutes(db: Db, storage: StorageService) { ? req.query.wakeCommentId.trim() : null; - const [{ project, goal }, ancestors, commentCursor, wakeComment] = await Promise.all([ + const [{ project, goal }, ancestors, commentCursor, wakeComment, attachments] = await Promise.all([ resolveIssueProjectAndGoal(issue), svc.getAncestors(issue.id), svc.getCommentCursor(issue.id), wakeCommentId ? svc.getComment(wakeCommentId) : null, + svc.listAttachments(issue.id), ]); res.json({ @@ -499,6 +500,14 @@ export function issueRoutes(db: Db, storage: StorageService) { wakeComment && wakeComment.issueId === issue.id ? wakeComment : null, + attachments: attachments.map((a) => ({ + id: a.id, + filename: a.originalFilename, + contentType: a.contentType, + byteSize: a.byteSize, + contentPath: `/api/attachments/${a.id}/content`, + createdAt: a.createdAt, + })), }); });