Skip to content

Commit 5973b4d

Browse files
authored
v4.0.7-r2 bugfix (#117)
### Issue for this PR Closes # ### Type of change - [ ] Bug fix - [ ] New feature - [ ] Refactor / code improvement - [ ] Documentation ### What does this PR do? Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR. **If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!** ### How did you verify your code works? ### Screenshots / recordings _If this is a UI change, please include a screenshot or recording._ ### Checklist - [ ] I have tested my changes locally - [ ] I have not included unrelated changes in this PR _If you do not follow this template your PR will be automatically rejected._
1 parent 2f88c14 commit 5973b4d

4 files changed

Lines changed: 46 additions & 141 deletions

File tree

packages/app/src/pages/session/message-timeline.data.test.ts

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ mock.module("@deepagent-code/ui/message-part", () => ({
88
type: "part",
99
ref: { messageID: item.messageID, partID: item.part.id },
1010
})),
11-
renderable: () => true,
11+
renderable: (part: Part, showReasoningSummaries = true) =>
12+
part.type !== "reasoning" || showReasoningSummaries,
1213
}))
1314

1415
afterAll(() => mock.restore())
@@ -122,7 +123,7 @@ describe("message timeline activity progress", () => {
122123
},
123124
}) as Part
124125

125-
test("shows only the latest settled progress for one activity", async () => {
126+
test("renders every progress revision for one activity", async () => {
126127
const { Timeline } = await import("./message-timeline.data")
127128
const messages = [assistant("msg_a0"), assistant("msg_a1")]
128129
const parts = new Map([
@@ -133,10 +134,10 @@ describe("message timeline activity progress", () => {
133134
const rows = Timeline.constructMessageRows(user, (id) => parts.get(id) ?? [], messages, 0, false, "idle", false)
134135
expect(
135136
rows.flatMap((row) => (row._tag === "AssistantPart" && row.group.type === "part" ? [row.group.ref.partID] : [])),
136-
).toEqual(["prt_progress_1"])
137+
).toEqual(["prt_progress_0", "prt_progress_1"])
137138
})
138139

139-
test("replaces settled progress with the activity final", async () => {
140+
test("keeps progress revisions when the activity final arrives", async () => {
140141
const { Timeline } = await import("./message-timeline.data")
141142
const messages = [assistant("msg_a0"), assistant("msg_a1"), { ...assistant("msg_a2"), finish: "stop" }]
142143
const parts = new Map([
@@ -148,10 +149,10 @@ describe("message timeline activity progress", () => {
148149
const rows = Timeline.constructMessageRows(user, (id) => parts.get(id) ?? [], messages, 0, false, "idle", false)
149150
expect(
150151
rows.flatMap((row) => (row._tag === "AssistantPart" && row.group.type === "part" ? [row.group.ref.partID] : [])),
151-
).toEqual(["prt_final_2"])
152+
).toEqual(["prt_progress_0", "prt_progress_1", "prt_final_2"])
152153
})
153154

154-
test("collapses every text part in one activity across separate parent user rows", async () => {
155+
test("keeps text parts across separate parent user rows", async () => {
155156
const { Timeline } = await import("./message-timeline.data")
156157
const user2 = { ...user, id: "msg_user_2" }
157158
const firstAssistant = assistant("msg_cross_a0")
@@ -173,7 +174,6 @@ describe("message timeline activity progress", () => {
173174
],
174175
])
175176
const getParts = (id: string) => parts.get(id) ?? []
176-
const visibility = Timeline.activityProgressVisibility(messages, getParts)
177177
const firstRows = Timeline.constructMessageRows(
178178
user,
179179
getParts,
@@ -182,7 +182,6 @@ describe("message timeline activity progress", () => {
182182
false,
183183
"idle",
184184
false,
185-
visibility,
186185
)
187186
const secondRows = Timeline.constructMessageRows(
188187
user2,
@@ -192,17 +191,20 @@ describe("message timeline activity progress", () => {
192191
false,
193192
"idle",
194193
false,
195-
visibility,
196194
)
197-
expect(firstRows.some((row) => row._tag === "AssistantPart")).toBe(false)
195+
expect(
196+
firstRows.flatMap((row) =>
197+
row._tag === "AssistantPart" && row.group.type === "part" ? [row.group.ref.partID] : [],
198+
),
199+
).toEqual(["prt_progress_0", "prt_cross_old_plain"])
198200
expect(
199201
secondRows.flatMap((row) =>
200202
row._tag === "AssistantPart" && row.group.type === "part" ? [row.group.ref.partID] : [],
201203
),
202204
).toEqual(["prt_progress_1", "prt_cross_latest_plain"])
203205
})
204206

205-
test("applies one revision marker to every text part in the assistant message", async () => {
207+
test("keeps every text part in every revision", async () => {
206208
const { Timeline } = await import("./message-timeline.data")
207209
const messages = [assistant("msg_multi_a0"), { ...assistant("msg_multi_a1"), finish: "stop" }]
208210
const plain = (messageID: string, id: string, text: string) =>
@@ -219,10 +221,10 @@ describe("message timeline activity progress", () => {
219221

220222
expect(
221223
rows.flatMap((row) => (row._tag === "AssistantPart" && row.group.type === "part" ? [row.group.ref.partID] : [])),
222-
).toEqual(["prt_final_1", "prt_final_plain"])
224+
).toEqual(["prt_progress_0", "prt_old_plain", "prt_final_1", "prt_final_plain"])
223225
})
224226

225-
test("uses the message marker to collapse reasoning-only revisions", async () => {
227+
test("keeps reasoning summaries from every revision when enabled", async () => {
226228
const { Timeline } = await import("./message-timeline.data")
227229
const messages = [
228230
{
@@ -245,19 +247,34 @@ describe("message timeline activity progress", () => {
245247

246248
expect(
247249
rows.flatMap((row) => (row._tag === "AssistantPart" && row.group.type === "part" ? [row.group.ref.partID] : [])),
248-
).toEqual(["prt_reasoning_latest"])
250+
).toEqual(["prt_reasoning_old", "prt_reasoning_latest"])
249251
})
250252

251-
test("hides an older tool revision when the latest terminal message has no renderable parts", async () => {
253+
test("uses showReasoning as the explicit reasoning visibility control", async () => {
254+
const { Timeline } = await import("./message-timeline.data")
255+
const message = assistant("msg_reasoning_setting")
256+
const reasoning = {
257+
id: "prt_reasoning_setting",
258+
sessionID: user.sessionID,
259+
messageID: message.id,
260+
type: "reasoning",
261+
text: "summary",
262+
} as Part
263+
const getParts = () => [reasoning]
264+
265+
const hidden = Timeline.constructMessageRows(user, getParts, [message], 0, false, "idle", false)
266+
const shown = Timeline.constructMessageRows(user, getParts, [message], 0, true, "idle", false)
267+
268+
expect(hidden.some((row) => row._tag === "AssistantPart")).toBe(false)
269+
expect(shown.some((row) => row._tag === "AssistantPart")).toBe(true)
270+
})
271+
272+
test("keeps an older tool revision when a later terminal message has no renderable parts", async () => {
252273
const { Timeline } = await import("./message-timeline.data")
253274
const old = {
254275
...assistant("msg_tool_old"),
255276
activityProgress: { activityID: "activity-tool", revision: 0, state: "progress" as const },
256277
}
257-
const terminal = {
258-
...assistant("msg_tool_terminal"),
259-
activityProgress: { activityID: "activity-tool", revision: 1, state: "final" as const },
260-
}
261278
const tool = {
262279
id: "prt_tool_old",
263280
sessionID: user.sessionID,
@@ -268,10 +285,8 @@ describe("message timeline activity progress", () => {
268285
state: { status: "completed", input: {}, output: "pending", title: "poll", time: { start: 1, end: 2 } },
269286
} as Part
270287
const parts = new Map([[old.id, [tool]]])
271-
const visibility = Timeline.activityProgressVisibility([old, terminal], (id) => parts.get(id) ?? [])
272-
273-
const rows = Timeline.constructMessageRows(user, (id) => parts.get(id) ?? [], [old], 0, true, "idle", false, visibility)
288+
const rows = Timeline.constructMessageRows(user, (id) => parts.get(id) ?? [], [old], 0, true, "idle", false)
274289

275-
expect(rows.some((row) => row._tag === "AssistantPart")).toBe(false)
290+
expect(rows.some((row) => row._tag === "AssistantPart")).toBe(true)
276291
})
277292
})

packages/app/src/pages/session/message-timeline.data.ts

Lines changed: 4 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ export namespace Timeline {
147147
showReasoning: boolean,
148148
status: SessionStatus["type"],
149149
isActive: boolean,
150-
activityProgressVisibility?: ReadonlySet<string>,
151150
) {
152151
const rows: TimelineRow.TimelineRow[] = []
153152

@@ -159,14 +158,10 @@ export namespace Timeline {
159158
const interrupted = interruptedMessageIndex !== -1
160159
const error = assistantMessages.find((m) => m.error && m.error.name !== "MessageAbortedError")?.error
161160

162-
const assistantPartRefs = latestActivityProgress(
163-
assistantMessages,
164-
assistantMessages.flatMap((message, messageIndex) =>
165-
getMessageParts(message.id)
166-
.filter((part) => renderable(part, showReasoning))
167-
.map((part) => ({ messageID: message.id, messageIndex, part })),
168-
),
169-
activityProgressVisibility,
161+
const assistantPartRefs = assistantMessages.flatMap((message, messageIndex) =>
162+
getMessageParts(message.id)
163+
.filter((part) => renderable(part, showReasoning))
164+
.map((part) => ({ messageID: message.id, messageIndex, part })),
170165
)
171166
const assistantItems =
172167
interrupted && !compaction
@@ -291,100 +286,6 @@ export namespace Timeline {
291286
return rows
292287
}
293288

294-
export function activityProgressVisibility(
295-
assistantMessages: AssistantMessage[],
296-
getMessageParts: (messageID: string) => Part[],
297-
) {
298-
const refs = assistantMessages.flatMap((message, messageIndex) =>
299-
getMessageParts(message.id).map((part) => ({ messageID: message.id, messageIndex, part })),
300-
)
301-
return new Set(
302-
latestActivityProgress(assistantMessages, refs).map((ref) => `${ref.messageID}:${ref.part.id}`),
303-
)
304-
}
305-
306-
function latestActivityProgress<T extends { messageID: string; part: Part }>(
307-
assistantMessages: AssistantMessage[],
308-
refs: T[],
309-
visibility?: ReadonlySet<string>,
310-
) {
311-
const progressByMessage = new Map<string, NonNullable<ReturnType<typeof messageActivityProgress>>>()
312-
assistantMessages.forEach((message) => {
313-
const marker = messageActivityProgress(message)
314-
if (marker) progressByMessage.set(message.id, marker)
315-
})
316-
refs.forEach((ref) => {
317-
const marker = partActivityProgress(ref.part)
318-
const projected = progressByMessage.get(ref.messageID)
319-
if (
320-
marker &&
321-
projected &&
322-
(marker.activityID !== projected.activityID || marker.revision !== projected.revision)
323-
)
324-
console.error("Conflicting activity progress projection", {
325-
messageID: ref.messageID,
326-
projected,
327-
legacy: marker,
328-
})
329-
if (projected) return
330-
if (marker) progressByMessage.set(ref.messageID, marker)
331-
})
332-
const markerFor = (ref: T) => progressByMessage.get(ref.messageID) ?? partActivityProgress(ref.part)
333-
if (visibility)
334-
return refs.filter((ref) => {
335-
if (!markerFor(ref)) return true
336-
return visibility.has(`${ref.messageID}:${ref.part.id}`)
337-
})
338-
const selected = new Map<string, { revision: number; terminal: boolean }>()
339-
progressByMessage.forEach((marker) => {
340-
if (!marker) return
341-
const terminal = marker.state !== "provisional" && marker.state !== "progress"
342-
const current = selected.get(marker.activityID)
343-
if (
344-
current &&
345-
((current.terminal && !terminal) || (current.terminal === terminal && current.revision > marker.revision))
346-
)
347-
return
348-
selected.set(marker.activityID, { revision: marker.revision, terminal })
349-
})
350-
return refs.filter((ref) => {
351-
const marker = markerFor(ref)
352-
if (!marker) return true
353-
const current = selected.get(marker.activityID)
354-
return (
355-
current?.revision === marker.revision &&
356-
current.terminal === (marker.state !== "provisional" && marker.state !== "progress")
357-
)
358-
})
359-
}
360-
361-
function messageActivityProgress(message: AssistantMessage) {
362-
const marker = message.activityProgress
363-
if (!marker) return
364-
if (!marker.activityID || !Number.isInteger(marker.revision) || marker.revision < 0) return
365-
if (
366-
!["provisional", "progress", "final", "interrupted", "recovery_required", "failed"].includes(marker.state)
367-
)
368-
return
369-
return marker
370-
}
371-
372-
function partActivityProgress(part: Part) {
373-
if (part.type !== "text") return
374-
const value = part.metadata?.deepagent_activity_progress
375-
if (!value || typeof value !== "object") return
376-
const marker = value as Record<string, unknown>
377-
if (typeof marker.activity_id !== "string" || marker.activity_id.length === 0) return
378-
if (typeof marker.revision !== "number" || !Number.isInteger(marker.revision) || marker.revision < 0) return
379-
if (!["provisional", "progress", "final", "interrupted", "recovery_required"].includes(String(marker.state)))
380-
return
381-
return {
382-
activityID: marker.activity_id,
383-
revision: marker.revision,
384-
state: marker.state as "provisional" | "progress" | "final" | "interrupted" | "recovery_required",
385-
}
386-
}
387-
388289
function isSummaryDiff(value: SnapshotFileDiff): value is SummaryDiff {
389290
return typeof value.file === "string"
390291
}

packages/app/src/pages/session/message-timeline.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -586,12 +586,6 @@ export function MessageTimeline(props: {
586586
})
587587
const parentTitle = createMemo(() => sessionTitle(parent()?.title) ?? language.t("command.session.new"))
588588
const getMsgParts = (msgId: string) => sync.data.part[msgId] ?? emptyParts
589-
const activityProgressVisibility = createMemo(() =>
590-
Timeline.activityProgressVisibility(
591-
sessionMessages().filter((message): message is AssistantMessage => message.role === "assistant"),
592-
getMsgParts,
593-
),
594-
)
595589
const childTaskDescription = createMemo(() => {
596590
const id = sessionID()
597591
if (!id) return
@@ -622,7 +616,6 @@ export function MessageTimeline(props: {
622616
settings.general.showReasoningSummaries(),
623617
sessionStatus().type,
624618
activeMessageID() === userMessage.id,
625-
activityProgressVisibility(),
626619
)
627620

628621
return reuseTimelineRows(previous, rows)

packages/desktop/electron.vite.config.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const sentry =
3030
})
3131
: false
3232

33-
export default defineConfig(({ command }) => ({
33+
export default defineConfig({
3434
main: {
3535
define: {
3636
"import.meta.env.DEEPAGENT_CODE_CHANNEL": JSON.stringify(channel),
@@ -47,19 +47,15 @@ export default defineConfig(({ command }) => ({
4747
enforce: "pre",
4848
resolveId(id) {
4949
if (id !== "virtual:deepagent-code-server") return
50-
if (command === "build") return { id: "./chunks/node.js", external: true }
51-
return this.resolve(`${DEEPAGENT_CODE_SERVER_DIST}/node.js`)
50+
return { id: "./chunks/node.js", external: true }
5251
},
5352
},
5453
{
5554
name: "deepagent-code:copy-server-assets",
5655
async writeBundle() {
5756
await mkdir("./out/main/chunks", { recursive: true })
5857
for (const file of await readdir(DEEPAGENT_CODE_SERVER_DIST)) {
59-
if (
60-
!file.endsWith(".wasm") &&
61-
(command !== "build" || !["node.js", "node.js.map", "models-dev.build.json"].includes(file))
62-
)
58+
if (!file.endsWith(".wasm") && !["node.js", "node.js.map", "models-dev.build.json"].includes(file))
6359
continue
6460
await copyFile(`${DEEPAGENT_CODE_SERVER_DIST}/${file}`, `./out/main/chunks/${file}`)
6561
}
@@ -93,4 +89,4 @@ export default defineConfig(({ command }) => ({
9389
},
9490
},
9591
},
96-
}))
92+
})

0 commit comments

Comments
 (0)