@@ -16,6 +16,7 @@ import {
1616} from '../errors/index.js' ;
1717import { retry , withTimeout } from '../errors/recovery.js' ;
1818import { sessionManager , FrameQueryMode } from '../session/index.js' ;
19+ import { frameLifecycleHooks } from './frame-lifecycle-hooks.js' ;
1920
2021// Constants for frame validation
2122const MAX_FRAME_DEPTH = 100 ; // Maximum allowed frame depth
@@ -308,6 +309,15 @@ export class RefactoredFrameManager {
308309 // Close all child frames recursively
309310 this . closeChildFrames ( targetFrameId ) ;
310311
312+ // Trigger lifecycle hooks (fire and forget)
313+ const events = this . frameDb . getFrameEvents ( targetFrameId ) ;
314+ const anchors = this . frameDb . getFrameAnchors ( targetFrameId ) ;
315+ frameLifecycleHooks
316+ . triggerClose ( { frame : { ...frame , state : 'closed' } , events, anchors } )
317+ . catch ( ( ) => {
318+ // Silently ignore errors - hooks are non-critical
319+ } ) ;
320+
311321 logger . info ( 'Closed frame' , {
312322 frameId : targetFrameId ,
313323 name : frame . name ,
@@ -754,6 +764,141 @@ export class RefactoredFrameManager {
754764 warnings,
755765 } ;
756766 }
767+
768+ /**
769+ * Set query mode for frame retrieval
770+ */
771+ setQueryMode ( mode : FrameQueryMode ) : void {
772+ this . queryMode = mode ;
773+ // Reinitialize stack with new query mode
774+ this . frameStack . setQueryMode ( mode ) ;
775+ }
776+
777+ /**
778+ * Get recent frames for context sharing
779+ */
780+ async getRecentFrames ( limit : number = 100 ) : Promise < Frame [ ] > {
781+ try {
782+ const frames = this . frameDb . getFramesByProject ( this . projectId ) ;
783+
784+ // Sort by created_at descending and limit
785+ return frames
786+ . sort ( ( a , b ) => ( b . created_at || 0 ) - ( a . created_at || 0 ) )
787+ . slice ( 0 , limit )
788+ . map ( ( frame ) => ( {
789+ ...frame ,
790+ // Add compatibility fields
791+ frameId : frame . frame_id ,
792+ runId : frame . run_id ,
793+ projectId : frame . project_id ,
794+ parentFrameId : frame . parent_frame_id ,
795+ title : frame . name ,
796+ timestamp : frame . created_at ,
797+ metadata : {
798+ tags : this . extractTagsFromFrame ( frame ) ,
799+ importance : this . calculateFrameImportance ( frame ) ,
800+ } ,
801+ data : {
802+ inputs : frame . inputs ,
803+ outputs : frame . outputs ,
804+ digest : frame . digest_json ,
805+ } ,
806+ } ) ) ;
807+ } catch ( error : unknown ) {
808+ logger . error ( 'Failed to get recent frames' , error as Error ) ;
809+ return [ ] ;
810+ }
811+ }
812+
813+ /**
814+ * Add context metadata to the current frame
815+ */
816+ async addContext ( key : string , value : any ) : Promise < void > {
817+ const currentFrameId = this . frameStack . getCurrentFrameId ( ) ;
818+ if ( ! currentFrameId ) return ;
819+
820+ try {
821+ const frame = this . frameDb . getFrame ( currentFrameId ) ;
822+ if ( ! frame ) return ;
823+
824+ const metadata = frame . outputs || { } ;
825+ metadata [ key ] = value ;
826+
827+ this . frameDb . updateFrame ( currentFrameId , {
828+ outputs : metadata ,
829+ } ) ;
830+ } catch ( error : unknown ) {
831+ logger . warn ( 'Failed to add context to frame' , { error, key } ) ;
832+ }
833+ }
834+
835+ /**
836+ * Delete a frame completely from the database (used in handoffs)
837+ */
838+ deleteFrame ( frameId : string ) : void {
839+ try {
840+ // Remove from active stack if present
841+ this . frameStack . removeFrame ( frameId ) ;
842+
843+ // Delete the frame and related data (cascades via FrameDatabase)
844+ this . frameDb . deleteFrame ( frameId ) ;
845+
846+ logger . debug ( 'Deleted frame completely' , { frameId } ) ;
847+ } catch ( error : unknown ) {
848+ logger . error ( 'Failed to delete frame' , { frameId, error } ) ;
849+ throw error ;
850+ }
851+ }
852+
853+ /**
854+ * Extract tags from frame for categorization
855+ */
856+ private extractTagsFromFrame ( frame : Frame ) : string [ ] {
857+ const tags : string [ ] = [ ] ;
858+
859+ if ( frame . type ) tags . push ( frame . type ) ;
860+
861+ if ( frame . name ) {
862+ const nameLower = frame . name . toLowerCase ( ) ;
863+ if ( nameLower . includes ( 'error' ) ) tags . push ( 'error' ) ;
864+ if ( nameLower . includes ( 'fix' ) ) tags . push ( 'resolution' ) ;
865+ if ( nameLower . includes ( 'decision' ) ) tags . push ( 'decision' ) ;
866+ if ( nameLower . includes ( 'milestone' ) ) tags . push ( 'milestone' ) ;
867+ }
868+
869+ try {
870+ if ( frame . digest_json && typeof frame . digest_json === 'object' ) {
871+ const digest = frame . digest_json as Record < string , unknown > ;
872+ if ( Array . isArray ( digest . tags ) ) {
873+ tags . push ( ...( digest . tags as string [ ] ) ) ;
874+ }
875+ }
876+ } catch {
877+ // Ignore parse errors
878+ }
879+
880+ return [ ...new Set ( tags ) ] ;
881+ }
882+
883+ /**
884+ * Calculate frame importance for prioritization
885+ */
886+ private calculateFrameImportance ( frame : Frame ) : 'high' | 'medium' | 'low' {
887+ if ( frame . type === 'milestone' || frame . name ?. includes ( 'decision' ) ) {
888+ return 'high' ;
889+ }
890+
891+ if ( frame . type === 'error' || frame . type === 'resolution' ) {
892+ return 'medium' ;
893+ }
894+
895+ if ( frame . closed_at && frame . created_at ) {
896+ const duration = frame . closed_at - frame . created_at ;
897+ if ( duration > 300 ) return 'medium' ;
898+ }
899+
900+ return 'low' ;
901+ }
757902}
758903
759904// Re-export types for compatibility
0 commit comments