From b4b91752873181fd75325dffa708196ee40fba11 Mon Sep 17 00:00:00 2001 From: 777genius Date: Fri, 29 May 2026 15:43:24 +0300 Subject: [PATCH] perf: reuse team summary for comment notification init --- src/main/services/team/TeamDataService.ts | 37 ++++++-- .../services/team/TeamDataService.test.ts | 89 +++++++++++++++++++ 2 files changed, 117 insertions(+), 9 deletions(-) diff --git a/src/main/services/team/TeamDataService.ts b/src/main/services/team/TeamDataService.ts index fbac3aee..3859bb00 100644 --- a/src/main/services/team/TeamDataService.ts +++ b/src/main/services/team/TeamDataService.ts @@ -218,6 +218,12 @@ interface EligibleTaskCommentNotification { summary: string; } +interface TaskCommentNotificationTeamContext { + deletedAt?: string; + leadName?: string; + leadSessionId?: string; +} + interface TaskChangeLogSourceSnapshot { projectFingerprint: string | null; logSourceGeneration: string | null; @@ -2441,6 +2447,11 @@ export class TeamDataService { await this.processTaskCommentNotifications(team.teamName, undefined, { seedHistoricalIfJournalMissing: true, recoverPending: true, + teamContext: { + deletedAt: team.deletedAt, + leadName: team.leadName, + leadSessionId: team.leadSessionId, + }, }); } catch (error) { logger.warn( @@ -2722,20 +2733,28 @@ export class TeamDataService { options?: { seedHistoricalIfJournalMissing?: boolean; recoverPending?: boolean; + teamContext?: TaskCommentNotificationTeamContext; } ): Promise { const seedHistoricalIfJournalMissing = options?.seedHistoricalIfJournalMissing === true; const recoverPending = options?.recoverPending === true; - let config: TeamConfig | null = null; - try { - config = await readConfigForUiSnapshot(this.configReader, teamName); - } catch { - return; - } - if (!config || config.deletedAt) return; + const teamContext = options?.teamContext; + if (teamContext?.deletedAt) return; - const leadName = this.resolveLeadNameFromConfig(config); - const leadSessionId = config.leadSessionId; + let leadName = teamContext?.leadName?.trim() ?? ''; + let leadSessionId = teamContext?.leadSessionId; + if (!leadName) { + let config: TeamConfig | null = null; + try { + config = await readConfigForUiSnapshot(this.configReader, teamName); + } catch { + return; + } + if (!config || config.deletedAt) return; + + leadName = this.resolveLeadNameFromConfig(config); + leadSessionId = config.leadSessionId; + } if (!leadName.trim()) return; const journalExists = await this.taskCommentNotificationJournal.exists(teamName); diff --git a/test/main/services/team/TeamDataService.test.ts b/test/main/services/team/TeamDataService.test.ts index edf6c0fc..22c0c57a 100644 --- a/test/main/services/team/TeamDataService.test.ts +++ b/test/main/services/team/TeamDataService.test.ts @@ -2520,6 +2520,95 @@ describe('TeamDataService', () => { } }); + it('uses startup team summary lead fields without rereading config for comment notification baselines', async () => { + const previous = process.env[TASK_COMMENT_FORWARDING_ENV]; + process.env[TASK_COMMENT_FORWARDING_ENV] = 'on'; + const journalEntries: Array> = []; + const inboxWriter = { sendMessage: vi.fn() }; + const getConfig = vi.fn(async () => { + throw new Error('unexpected config read'); + }); + const journal = { + exists: vi.fn(async () => false), + ensureFile: vi.fn(async () => undefined), + withEntries: vi.fn( + async (_teamName: string, fn: (entries: unknown[]) => Promise<{ result: unknown }>) => { + const outcome = await fn(journalEntries); + return outcome.result; + } + ), + }; + + try { + const service = new TeamDataService( + { + listTeams: vi.fn(async () => [ + { + teamName: 'my-team', + displayName: 'My team', + description: '', + memberCount: 1, + taskCount: 1, + lastActivity: null, + leadName: 'team-lead', + leadSessionId: 'lead-1', + }, + ]), + getConfig, + } as never, + { + getTasks: vi.fn(async () => [ + { + id: 'task-1', + displayId: 'abcd1234', + subject: 'Investigate', + status: 'pending', + owner: 'alice', + comments: [ + { + id: 'comment-1', + author: 'alice', + text: 'Found the root cause.', + createdAt: '2026-03-14T10:00:00.000Z', + type: 'regular', + }, + ], + }, + ]), + } as never, + { + listInboxNames: vi.fn(async () => []), + getMessages: vi.fn(async () => []), + getMessagesFor: vi.fn(async () => []), + } as never, + inboxWriter as never, + {} as never, + {} as never, + {} as never, + {} as never, + {} as never, + {} as never, + (() => ({}) as never) as never, + journal as never + ); + + await service.initializeTaskCommentNotificationState(); + + expect(getConfig).not.toHaveBeenCalled(); + expect(inboxWriter.sendMessage).not.toHaveBeenCalled(); + expect(journalEntries).toEqual([ + expect.objectContaining({ + key: 'task-1:comment-1', + state: 'seeded', + messageId: 'task-comment-forward:my-team:task-1:comment-1', + }), + ]); + } finally { + if (previous === undefined) delete process.env[TASK_COMMENT_FORWARDING_ENV]; + else process.env[TASK_COMMENT_FORWARDING_ENV] = previous; + } + }); + it('forwards a new eligible task comment to the lead exactly once in live mode', async () => { const previous = process.env[TASK_COMMENT_FORWARDING_ENV]; process.env[TASK_COMMENT_FORWARDING_ENV] = 'on';