@@ -373,11 +373,12 @@ export async function runDream(args: {
373373 throw error ;
374374 }
375375 log ( `[dreamer] task ${ taskName } : child session created ${ agentSessionId } ` ) ;
376+ const childSessionId = agentSessionId ;
376377
377- await shared . promptSyncWithModelSuggestionRetry (
378+ const dreamTaskRun = await shared . promptSyncWithValidatedOutputRetry (
378379 args . client ,
379380 {
380- path : { id : agentSessionId } ,
381+ path : { id : childSessionId } ,
381382 query : { directory : args . sessionDirectory ?? args . projectIdentity } ,
382383 body : {
383384 agent : DREAMER_AGENT ,
@@ -392,24 +393,33 @@ export async function runDream(args: {
392393 signal : taskAbortController . signal ,
393394 fallbackModels : args . fallbackModels ,
394395 callContext : `dreamer:${ taskName } ` ,
396+ fetchOutput : async ( ) => {
397+ const messagesResponse = await args . client . session . messages ( {
398+ path : { id : childSessionId } ,
399+ query : {
400+ directory : args . sessionDirectory ?? args . projectIdentity ,
401+ limit : 50 ,
402+ } ,
403+ } ) ;
404+ return shared . normalizeSDKResponse ( messagesResponse , [ ] as unknown [ ] , {
405+ preferResponseOnMissingData : true ,
406+ } ) ;
407+ } ,
408+ validateOutput : ( messages ) => {
409+ const taskResult = extractLatestAssistantText ( messages ) ;
410+ if ( ! taskResult ) {
411+ throw new Error ( "Dreamer returned no assistant output." ) ;
412+ }
413+ return taskResult ;
414+ } ,
395415 } ,
396416 ) ;
397417 if ( lostLease ) {
398418 throw new Error ( lostLeaseReason ?? `Dream lease lost during ${ taskName } ` ) ;
399419 }
400420
401- const messagesResponse = await args . client . session . messages ( {
402- path : { id : agentSessionId } ,
403- query : { directory : args . sessionDirectory ?? args . projectIdentity , limit : 50 } ,
404- } ) ;
405- const messages = shared . normalizeSDKResponse ( messagesResponse , [ ] as unknown [ ] , {
406- preferResponseOnMissingData : true ,
407- } ) ;
408- recordInvocation ( { status : "completed" , messages } ) ;
409- const taskResult = extractLatestAssistantText ( messages ) ;
410- if ( ! taskResult ) {
411- throw new Error ( "Dreamer returned no assistant output." ) ;
412- }
421+ const taskResult = dreamTaskRun . validated ;
422+ recordInvocation ( { status : "completed" , messages : dreamTaskRun . output } ) ;
413423
414424 if (
415425 taskName === "maintain-docs" &&
@@ -867,12 +877,13 @@ Only include notes whose conditions you could definitively evaluate against exte
867877 }
868878
869879 log ( `[dreamer] smart notes: child session created ${ agentSessionId } ` ) ;
880+ const childSessionId = agentSessionId ;
870881
871882 const remainingMs = Math . max ( 0 , args . deadline - Date . now ( ) ) ;
872- await shared . promptSyncWithModelSuggestionRetry (
883+ const smartNoteRun = await shared . promptSyncWithValidatedOutputRetry (
873884 args . client ,
874885 {
875- path : { id : agentSessionId } ,
886+ path : { id : childSessionId } ,
876887 query : { directory : args . sessionDirectory ?? args . projectIdentity } ,
877888 body : {
878889 agent : DREAMER_AGENT ,
@@ -887,37 +898,44 @@ Only include notes whose conditions you could definitively evaluate against exte
887898 signal : abortController . signal ,
888899 fallbackModels : args . fallbackModels ,
889900 callContext : "dreamer:smart-notes" ,
901+ fetchOutput : async ( ) => {
902+ const messagesResponse = await args . client . session . messages ( {
903+ path : { id : childSessionId } ,
904+ query : {
905+ directory : args . sessionDirectory ?? args . projectIdentity ,
906+ limit : 50 ,
907+ } ,
908+ } ) ;
909+ return shared . normalizeSDKResponse ( messagesResponse , [ ] as unknown [ ] , {
910+ preferResponseOnMissingData : true ,
911+ } ) ;
912+ } ,
913+ validateOutput : ( messages ) => {
914+ const output = extractLatestAssistantText ( messages ) ;
915+ if ( ! output ) throw new Error ( "Smart note evaluation returned no output." ) ;
916+
917+ // Parse the JSON results from the LLM response — use greedy match to handle
918+ // `]` chars inside JSON string values (e.g., reasons containing brackets).
919+ const jsonMatch = output . match ( / \[ [ \s \S ] * \] / ) ;
920+ if ( ! jsonMatch ) {
921+ throw new Error ( "Smart note evaluation returned no JSON array." ) ;
922+ }
923+
924+ try {
925+ return JSON . parse ( jsonMatch [ 0 ] ) as Array < {
926+ id : number ;
927+ met : boolean ;
928+ reason ?: string ;
929+ } > ;
930+ } catch {
931+ throw new Error ( "Smart note evaluation returned invalid JSON." ) ;
932+ }
933+ } ,
890934 } ,
891935 ) ;
892936
893- const messagesResponse = await args . client . session . messages ( {
894- path : { id : agentSessionId } ,
895- query : { directory : args . sessionDirectory ?? args . projectIdentity , limit : 50 } ,
896- } ) ;
897- const messages = shared . normalizeSDKResponse ( messagesResponse , [ ] as unknown [ ] , {
898- preferResponseOnMissingData : true ,
899- } ) ;
900- recordInvocation ( { status : "completed" , messages } ) ;
901- const output = extractLatestAssistantText ( messages ) ;
902- if ( ! output ) throw new Error ( "Smart note evaluation returned no output." ) ;
903-
904- // Parse the JSON results from the LLM response — use greedy match to handle
905- // `]` chars inside JSON string values (e.g., reasons containing brackets).
906- const jsonMatch = output . match ( / \[ [ \s \S ] * \] / ) ;
907- if ( ! jsonMatch ) {
908- log ( "[dreamer] smart notes: no JSON array found in output, skipping" ) ;
909- for ( const note of pendingNotes ) markNoteChecked ( args . db , note . id ) ;
910- throw new Error ( "Smart note evaluation returned no JSON array." ) ;
911- }
912-
913- let evaluations : Array < { id : number ; met : boolean ; reason ?: string } > ;
914- try {
915- evaluations = JSON . parse ( jsonMatch [ 0 ] ) ;
916- } catch {
917- log ( `[dreamer] smart notes: failed to parse JSON from LLM output, marking all checked` ) ;
918- for ( const note of pendingNotes ) markNoteChecked ( args . db , note . id ) ;
919- throw new Error ( "Smart note evaluation returned invalid JSON." ) ;
920- }
937+ recordInvocation ( { status : "completed" , messages : smartNoteRun . output } ) ;
938+ const evaluations = smartNoteRun . validated ;
921939 let surfaced = 0 ;
922940 for ( const evaluation of evaluations ) {
923941 if ( typeof evaluation . id !== "number" ) continue ;
@@ -956,6 +974,19 @@ Only include notes whose conditions you could definitively evaluate against exte
956974 } ) ;
957975 } catch ( error ) {
958976 phaseFailed = true ;
977+ if (
978+ error instanceof Error &&
979+ error . message === "Smart note evaluation returned no JSON array."
980+ ) {
981+ log ( "[dreamer] smart notes: no JSON array found in output, skipping" ) ;
982+ for ( const note of pendingNotes ) markNoteChecked ( args . db , note . id ) ;
983+ } else if (
984+ error instanceof Error &&
985+ error . message === "Smart note evaluation returned invalid JSON."
986+ ) {
987+ log ( `[dreamer] smart notes: failed to parse JSON from LLM output, marking all checked` ) ;
988+ for ( const note of pendingNotes ) markNoteChecked ( args . db , note . id ) ;
989+ }
959990 const durationMs = Date . now ( ) - taskStartedAt ;
960991 const errorDescription = describeError ( error ) ;
961992 args . result . smartNotesSurfaced = 0 ;
0 commit comments