@@ -40,7 +40,13 @@ import {
4040import { resolveGenerationWorkspaceRoot } from '../generation-workspace' ;
4141import { resolveImageGenerationConfig , toGenerateImageOptions } from '../image-generation-settings' ;
4242import { getLogger } from '../logger' ;
43- import { loadMemoryContext , triggerMemoryUpdate } from '../memory-ipc' ;
43+ import {
44+ loadMemoryContext ,
45+ triggerUserMemoryCandidateCapture ,
46+ triggerUserMemoryConsolidation ,
47+ triggerWorkspaceMemoryUpdate ,
48+ workspaceNameFromPath ,
49+ } from '../memory-ipc' ;
4450import { getApiKeyForProvider , getCachedConfig , hasApiKeyForProvider } from '../onboarding-ipc' ;
4551import { readPersisted as readPreferences } from '../preferences-ipc' ;
4652import { runPreview } from '../preview-runtime' ;
@@ -70,6 +76,56 @@ export function contextWindowForContextPack(model: unknown): number {
7076 : DEFAULT_CONTEXT_WINDOW_FOR_CONTEXT_PACK ;
7177}
7278
79+ function designMdSummaryForMemory (
80+ projectContext : Awaited < ReturnType < typeof preparePromptContext > > [ 'projectContext' ] ,
81+ ) : string | null {
82+ const raw = projectContext . designMd ?? projectContext . invalidDesignMd ?. raw ?? null ;
83+ if ( raw === null || raw . trim ( ) . length === 0 ) return null ;
84+ const headings = raw
85+ . split ( '\n' )
86+ . map ( ( line ) => line . trim ( ) )
87+ . filter ( ( line ) => / ^ # { 1 , 3 } \s + \S / . test ( line ) )
88+ . slice ( 0 , 24 ) ;
89+ if ( headings . length === 0 ) {
90+ return projectContext . invalidDesignMd
91+ ? 'DESIGN.md exists but currently fails validation.'
92+ : 'DESIGN.md exists and should remain the authoritative design-system source.' ;
93+ }
94+ return [
95+ projectContext . invalidDesignMd
96+ ? 'DESIGN.md exists but currently fails validation.'
97+ : 'DESIGN.md exists and should remain the authoritative design-system source.' ,
98+ 'Headings:' ,
99+ ...headings . map ( ( heading ) => `- ${ heading . replace ( / ^ # + \s * / , '' ) } ` ) ,
100+ ] . join ( '\n' ) ;
101+ }
102+
103+ function extractUserMessagesForMemory ( messages : DesignBriefConversationMessages ) : string [ ] {
104+ const out : string [ ] = [ ] ;
105+ for ( const msg of messages ) {
106+ if ( msg . role !== 'user' ) continue ;
107+ const content = ( msg as { content ?: unknown } ) . content ;
108+ if ( typeof content === 'string' ) {
109+ const trimmed = content . trim ( ) ;
110+ if ( trimmed . length > 0 ) out . push ( trimmed ) ;
111+ continue ;
112+ }
113+ if ( ! Array . isArray ( content ) ) continue ;
114+ const text = content
115+ . map ( ( part ) => {
116+ if ( typeof part !== 'object' || part === null ) return '' ;
117+ const record = part as Record < string , unknown > ;
118+ return record [ 'type' ] === 'text' && typeof record [ 'text' ] === 'string'
119+ ? record [ 'text' ]
120+ : '' ;
121+ } )
122+ . join ( '\n' )
123+ . trim ( ) ;
124+ if ( text . length > 0 ) out . push ( text ) ;
125+ }
126+ return out ;
127+ }
128+
73129/**
74130 * Pull an HTTP status code out of a caught provider error. Mirrors
75131 * `packages/providers/src/retry.ts::extractStatus` intentionally — we don't
@@ -570,22 +626,25 @@ export function registerGenerateIpc({ db, getMainWindow }: RegisterGenerateIpcDe
570626 } ) ;
571627
572628 const { designId, workspaceRoot } = requireWorkspaceRootForDesign ( payload . designId ) ;
629+ const prefs = await readPreferences ( ) ;
573630 const promptContext = await preparePromptContext ( {
574631 attachments : payload . attachments ,
575632 referenceUrl : payload . referenceUrl ,
576633 designSystem : cfg . designSystem ?? null ,
577634 workspaceRoot,
578635 } ) ;
579- let memoryContext : string [ ] | undefined ;
636+ let memoryContext : Awaited < ReturnType < typeof loadMemoryContext > > | undefined ;
580637 let memoryLoadWarning : string | undefined ;
581- try {
582- memoryContext = await loadMemoryContext ( workspaceRoot ) ;
583- } catch ( err ) {
584- memoryLoadWarning = `Project memory unavailable: ${ err instanceof Error ? err . message : String ( err ) } ` ;
585- logIpc . warn ( 'memory.load.fail' , {
586- generationId : id ,
587- message : err instanceof Error ? err . message : String ( err ) ,
588- } ) ;
638+ if ( prefs . memoryEnabled ) {
639+ try {
640+ memoryContext = await loadMemoryContext ( workspaceRoot ) ;
641+ } catch ( err ) {
642+ memoryLoadWarning = `Project memory unavailable: ${ err instanceof Error ? err . message : String ( err ) } ` ;
643+ logIpc . warn ( 'memory.load.fail' , {
644+ generationId : id ,
645+ message : err instanceof Error ? err . message : String ( err ) ,
646+ } ) ;
647+ }
589648 }
590649
591650 logIpc . info ( 'generate' , {
@@ -615,7 +674,7 @@ export function registerGenerateIpc({ db, getMainWindow }: RegisterGenerateIpcDe
615674 let aggressivePruneDetected = false ;
616675 const chatRows = chatRowsForDesign ( designId ) ;
617676 const resourceState = deriveResourceStateFromChatRows ( chatRows ) ;
618- const existingBrief = briefForDesign ( designId ) ;
677+ const existingBrief = prefs . memoryEnabled ? briefForDesign ( designId ) : null ;
619678 const contextPack = buildDesignContextPack ( {
620679 chatRows,
621680 brief : existingBrief ,
@@ -646,7 +705,7 @@ export function registerGenerateIpc({ db, getMainWindow }: RegisterGenerateIpcDe
646705 referenceUrl : promptContext . referenceUrl ,
647706 designSystem : promptContext . designSystem ?? null ,
648707 sessionContext : contextPack . contextSections ,
649- ...( memoryContext !== undefined ? { memoryContext } : { } ) ,
708+ ...( memoryContext !== undefined ? { memoryContext : memoryContext . sections } : { } ) ,
650709 projectContext : promptContext . projectContext ,
651710 initialResourceState : resourceState ,
652711 ...( baseUrl !== undefined ? { baseUrl } : { } ) ,
@@ -679,6 +738,7 @@ export function registerGenerateIpc({ db, getMainWindow }: RegisterGenerateIpcDe
679738 cost : result . costUsd ,
680739 } ) ;
681740 if ( capturedMessages !== null ) {
741+ const messagesForMemory = capturedMessages ;
682742 const design = db !== null ? getDesign ( db , designId ) : null ;
683743 let memoryWorkspaceRoot = workspaceRoot ;
684744 try {
@@ -689,59 +749,114 @@ export function registerGenerateIpc({ db, getMainWindow }: RegisterGenerateIpcDe
689749 message : err instanceof Error ? err . message : String ( err ) ,
690750 } ) ;
691751 }
692- const briefUpdate = updateDesignSessionBrief ( {
693- existingBrief,
694- conversationMessages : capturedMessages ,
695- designId,
696- designName : design ?. name ?? 'Untitled' ,
697- model : active . model ,
698- apiKey,
699- ...( baseUrl !== undefined ? { baseUrl } : { } ) ,
700- wire : active . wire ,
701- ...( active . httpHeaders !== undefined ? { httpHeaders : active . httpHeaders } : { } ) ,
702- ...( active . reasoningLevel !== undefined
703- ? { reasoningLevel : active . reasoningLevel }
704- : { } ) ,
705- ...( allowKeyless ? { allowKeyless : true } : { } ) ,
706- } )
707- . then ( ( briefResult ) => {
708- const opts = chatStoreOptions ( ) ;
709- if ( opts !== null ) appendSessionDesignBrief ( opts , designId , briefResult . brief ) ;
710- logIpc . info ( 'design-brief.update.ok' , {
711- generationId : id ,
712- outputLen : JSON . stringify ( briefResult . brief ) . length ,
713- } ) ;
714- } )
715- . catch ( ( err ) => {
716- logIpc . warn ( 'design-brief.update.fail' , {
717- generationId : id ,
718- message : err instanceof Error ? err . message : String ( err ) ,
719- } ) ;
720- } ) ;
721- const memoryUpdate = triggerMemoryUpdate ( {
722- workspacePath : memoryWorkspaceRoot ,
723- designId,
724- designName : design ?. name ?? 'Untitled' ,
725- conversationMessages : capturedMessages ,
726- model : active . model ,
727- apiKey,
728- db,
729- ...( baseUrl !== undefined ? { baseUrl } : { } ) ,
730- wire : active . wire ,
731- ...( active . httpHeaders !== undefined ? { httpHeaders : active . httpHeaders } : { } ) ,
732- ...( active . reasoningLevel !== undefined
733- ? { reasoningLevel : active . reasoningLevel }
734- : { } ) ,
735- ...( allowKeyless ? { allowKeyless : true } : { } ) ,
736- } ) . catch ( ( err ) => {
737- logIpc . warn ( 'memory.update.fail' , {
738- generationId : id ,
739- message : err instanceof Error ? err . message : String ( err ) ,
740- } ) ;
741- } ) ;
752+ const designName = design ?. name ?? 'Untitled' ;
753+ const workspaceMemoryUpdate =
754+ prefs . memoryEnabled === true && prefs . workspaceMemoryAutoUpdate === true
755+ ? triggerWorkspaceMemoryUpdate ( {
756+ workspacePath : memoryWorkspaceRoot ,
757+ workspaceName : workspaceNameFromPath ( memoryWorkspaceRoot ) ,
758+ designId,
759+ designName,
760+ conversationMessages : messagesForMemory ,
761+ userMemory : memoryContext ?. userMemory ?. content ?? null ,
762+ designMdSummary : designMdSummaryForMemory ( promptContext . projectContext ) ,
763+ model : active . model ,
764+ apiKey,
765+ ...( baseUrl !== undefined ? { baseUrl } : { } ) ,
766+ wire : active . wire ,
767+ ...( active . httpHeaders !== undefined
768+ ? { httpHeaders : active . httpHeaders }
769+ : { } ) ,
770+ ...( active . reasoningLevel !== undefined
771+ ? { reasoningLevel : active . reasoningLevel }
772+ : { } ) ,
773+ ...( allowKeyless ? { allowKeyless : true } : { } ) ,
774+ } ) . catch ( ( err ) => {
775+ logIpc . warn ( 'workspace-memory.update.fail' , {
776+ generationId : id ,
777+ message : err instanceof Error ? err . message : String ( err ) ,
778+ } ) ;
779+ return memoryContext ?. workspaceMemory ?? null ;
780+ } )
781+ : Promise . resolve ( memoryContext ?. workspaceMemory ?? null ) ;
742782
783+ const briefUpdate = prefs . memoryEnabled
784+ ? workspaceMemoryUpdate
785+ . then ( ( workspaceMemory ) =>
786+ updateDesignSessionBrief ( {
787+ existingBrief,
788+ conversationMessages : messagesForMemory ,
789+ designId,
790+ designName,
791+ userMemory : memoryContext ?. userMemory ?. content ?? null ,
792+ workspaceMemory : workspaceMemory ?. content ?? null ,
793+ sourceUserMemoryHash : memoryContext ?. userMemory ?. hash ,
794+ sourceWorkspaceMemoryHash : workspaceMemory ?. hash ,
795+ sourceMemoryUpdatedAt :
796+ workspaceMemory ?. updatedAt ?? memoryContext ?. userMemory ?. updatedAt ,
797+ model : active . model ,
798+ apiKey,
799+ ...( baseUrl !== undefined ? { baseUrl } : { } ) ,
800+ wire : active . wire ,
801+ ...( active . httpHeaders !== undefined
802+ ? { httpHeaders : active . httpHeaders }
803+ : { } ) ,
804+ ...( active . reasoningLevel !== undefined
805+ ? { reasoningLevel : active . reasoningLevel }
806+ : { } ) ,
807+ ...( allowKeyless ? { allowKeyless : true } : { } ) ,
808+ } ) ,
809+ )
810+ . then ( ( briefResult ) => {
811+ const opts = chatStoreOptions ( ) ;
812+ if ( opts !== null )
813+ appendSessionDesignBrief ( opts , designId , briefResult . brief ) ;
814+ logIpc . info ( 'design-brief.update.ok' , {
815+ generationId : id ,
816+ outputLen : JSON . stringify ( briefResult . brief ) . length ,
817+ } ) ;
818+ } )
819+ . catch ( ( err ) => {
820+ logIpc . warn ( 'design-brief.update.fail' , {
821+ generationId : id ,
822+ message : err instanceof Error ? err . message : String ( err ) ,
823+ } ) ;
824+ } )
825+ : Promise . resolve ( ) ;
826+ const userMemoryMaintenance =
827+ prefs . memoryEnabled === true
828+ ? triggerUserMemoryCandidateCapture ( {
829+ designId,
830+ designName,
831+ userMessages : extractUserMessagesForMemory ( messagesForMemory ) ,
832+ } )
833+ . then ( ( ) => {
834+ if ( prefs . userMemoryAutoUpdate !== true ) {
835+ return { updated : false , candidateCount : 0 } ;
836+ }
837+ return triggerUserMemoryConsolidation ( {
838+ model : active . model ,
839+ apiKey,
840+ ...( baseUrl !== undefined ? { baseUrl } : { } ) ,
841+ wire : active . wire ,
842+ ...( active . httpHeaders !== undefined
843+ ? { httpHeaders : active . httpHeaders }
844+ : { } ) ,
845+ ...( active . reasoningLevel !== undefined
846+ ? { reasoningLevel : active . reasoningLevel }
847+ : { } ) ,
848+ ...( allowKeyless ? { allowKeyless : true } : { } ) ,
849+ } ) ;
850+ } )
851+ . catch ( ( err ) => {
852+ logIpc . warn ( 'user-memory.maintenance.fail' , {
853+ generationId : id ,
854+ message : err instanceof Error ? err . message : String ( err ) ,
855+ } ) ;
856+ } )
857+ : Promise . resolve ( ) ;
743858 if ( aggressivePruneDetected ) {
744- await Promise . all ( [ briefUpdate , memoryUpdate ] ) ;
859+ await Promise . all ( [ briefUpdate , userMemoryMaintenance ] ) ;
745860 }
746861 }
747862 if ( memoryLoadWarning !== undefined ) {
0 commit comments