From d765000f44f8dd6bec6b866db5caea7d44c0915f Mon Sep 17 00:00:00 2001 From: fdkevin <25277753+fdkevin0@users.noreply.github.com> Date: Sat, 7 Nov 2026 17:17:06 +0800 Subject: [PATCH] fix(server): avoid duplicating startup notices when timeline is already primed --- .../src/server/agent/agent-manager.test.ts | 13 +++++++ .../server/src/server/agent/agent-manager.ts | 35 +++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/server/src/server/agent/agent-manager.test.ts b/packages/server/src/server/agent/agent-manager.test.ts index 85a303baf1..a8f6c27286 100644 --- a/packages/server/src/server/agent/agent-manager.test.ts +++ b/packages/server/src/server/agent/agent-manager.test.ts @@ -12687,6 +12687,19 @@ test("commits startup notices once on create and after restored history", async const created = await manager.createAgent({ provider: "codex", cwd: workdir }, undefined, {}); ids.push(created.id); expect((await manager.getTimelineRows(created.id)).map((row) => row.item)).toEqual([notice]); + await manager.reloadAgentSession(created.id); + expect((await manager.getTimelineRows(created.id)).map((row) => row.item)).toEqual([notice]); + await manager.closeAgent(created.id); + await manager.resumeAgentFromPersistence( + { + provider: "codex", + sessionId: "existing-created", + metadata: { cwd: workdir }, + }, + undefined, + created.id, + ); + expect((await manager.getTimelineRows(created.id)).map((row) => row.item)).toEqual([notice]); const resumed = await manager.resumeAgentFromPersistence({ provider: "codex", sessionId: "existing", diff --git a/packages/server/src/server/agent/agent-manager.ts b/packages/server/src/server/agent/agent-manager.ts index b13de1930a..3022b1e7e6 100644 --- a/packages/server/src/server/agent/agent-manager.ts +++ b/packages/server/src/server/agent/agent-manager.ts @@ -4029,9 +4029,11 @@ export class AgentManager { // Legacy/imported chats need their existing history before startup rows. await this.primeTimelineFromLegacyProviderHistory(managed, false, startupHistory); } else { - for (const entry of buildImportedTimelineRows(session.initialTimeline)) { - this.recordTimeline(managed.id, entry.item, { timestamp: entry.timestamp }); - } + await this.recordInitialTimelineIfMissing( + managed, + session.initialTimeline, + durableTimelineHasRows, + ); } this.refreshSessionPersistence(managed); } @@ -4224,6 +4226,33 @@ export class AgentManager { }; } + private async recordInitialTimelineIfMissing( + agent: ActiveManagedAgent, + initialTimeline: NonNullable, + durableTimelineHasRows: boolean, + ): Promise { + const existingItems: AgentTimelineItem[] = [...this.timelineStore.getItems(agent.id)]; + if (existingItems.length === 0 && durableTimelineHasRows && this.durableTimelineStore) { + for (const row of await this.durableTimelineStore.getCommittedRows(agent.id)) { + existingItems.push(row.item); + } + } + for (const entry of buildImportedTimelineRows(initialTimeline)) { + const alreadyRecorded = existingItems.some( + (item) => + item.type === "notification" && + entry.item.type === "notification" && + item.level === entry.item.level && + item.message === entry.item.message, + ); + if (alreadyRecorded) { + continue; + } + this.recordTimeline(agent.id, entry.item, { timestamp: entry.timestamp }); + existingItems.push(entry.item); + } + } + private prepareAgentForClosure( agent: LiveManagedAgent, cancelReason: string,