From 72179d114595fd5634cdc67ccd1dc2d63ab89658 Mon Sep 17 00:00:00 2001 From: Bohan Jiang <52446949+Bohan-J@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:15:50 +0800 Subject: [PATCH] refactor(transcript): reuse payload helper + cover coalesce timestamps (MUL-3174) (#3958) * refactor(transcript): reuse taskMessageToPayload in WS broadcast The ReportTaskMessages WebSocket broadcast hand-built the payload and duplicated the created_at formatting that taskMessageToPayload already does. Reuse the helper with the just-inserted row, which carries the same redacted values and the DB-assigned timestamp. Co-authored-by: multica-agent * test(transcript): cover coalesce created_at behavior Lock in that coalescing streaming fragments carries the latest created_at, and falls back to the previous timestamp when the merged fragment has none. Co-authored-by: multica-agent --------- Co-authored-by: J Co-authored-by: multica-agent --- .../task-transcript/build-timeline.test.ts | 25 +++++++++++++++++++ server/internal/handler/daemon.go | 17 ++----------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/packages/views/common/task-transcript/build-timeline.test.ts b/packages/views/common/task-transcript/build-timeline.test.ts index 188ca9f89d..5f70299516 100644 --- a/packages/views/common/task-transcript/build-timeline.test.ts +++ b/packages/views/common/task-transcript/build-timeline.test.ts @@ -76,4 +76,29 @@ describe("task transcript timeline", () => { expect(items[0]?.content).not.toContain("abc123xyz"); expect(items[0]?.content).not.toContain("def456"); }); + + it("keeps the latest created_at when coalescing streaming fragments", () => { + const items = coalesceTimelineItems([ + { seq: 1, type: "text", content: "hello ", created_at: "2026-06-09T09:00:00.000Z" }, + { seq: 2, type: "text", content: "world", created_at: "2026-06-09T09:00:05.000Z" }, + ]); + + expect(items).toEqual([ + expect.objectContaining({ + seq: 1, + type: "text", + content: "hello world", + created_at: "2026-06-09T09:00:05.000Z", + }), + ]); + }); + + it("falls back to the previous created_at when the merged fragment has none", () => { + const items = coalesceTimelineItems([ + { seq: 1, type: "text", content: "hello ", created_at: "2026-06-09T09:00:00.000Z" }, + { seq: 2, type: "text", content: "world" }, + ]); + + expect(items[0]?.created_at).toBe("2026-06-09T09:00:00.000Z"); + }); }); diff --git a/server/internal/handler/daemon.go b/server/internal/handler/daemon.go index 6953cd4e45..ef254fde6b 100644 --- a/server/internal/handler/daemon.go +++ b/server/internal/handler/daemon.go @@ -2096,21 +2096,8 @@ func (h *Handler) ReportTaskMessages(w http.ResponseWriter, r *http.Request) { } if workspaceID != "" { - createdAt := "" - if created.CreatedAt.Valid { - createdAt = created.CreatedAt.Time.UTC().Format(time.RFC3339Nano) - } - h.publishTask(protocol.EventTaskMessage, workspaceID, "system", "", taskID, protocol.TaskMessagePayload{ - TaskID: taskID, - IssueID: uuidToString(task.IssueID), - Seq: msg.Seq, - Type: msg.Type, - Tool: msg.Tool, - Content: msg.Content, - Input: msg.Input, - Output: msg.Output, - CreatedAt: createdAt, - }) + h.publishTask(protocol.EventTaskMessage, workspaceID, "system", "", taskID, + taskMessageToPayload(created, taskID, uuidToString(task.IssueID))) } }