@@ -540,6 +540,11 @@ async function processUrlWithProgress(
540540 let terminalPhaseSeen = false ;
541541 let eventCount = 0 ;
542542 let finalResult : AddResult | undefined ;
543+ // Keep a reference to the last posted Discord message so we can
544+ // append the created item link/ID to it when the CLI returns the ID.
545+ // In real runtime this will be a discord.js Message; in tests the
546+ // mocked send/reply helpers may return undefined which we handle.
547+ let lastPostedMessage : any = null ;
543548
544549 // Process progress events using manual iteration to capture return value
545550 logger . info ( "Waiting for CLI progress events..." , { messageId : message . id , url } ) ;
@@ -582,10 +587,10 @@ async function processUrlWithProgress(
582587 }
583588
584589 // Only send update if phase changed (avoid spam)
585- if ( event . phase !== lastPhase ) {
586- lastPhase = event . phase ;
590+ if ( event . phase !== lastPhase ) {
591+ lastPhase = event . phase ;
587592
588- const progressMsg = formatProgressMessage ( event ) ;
593+ const progressMsg = formatProgressMessage ( event ) ;
589594
590595 // Always ensure URLs shown to users are wrapped in backticks to avoid embeds.
591596 // If event contains a url or title, prefer showing the title wrapped in ticks.
@@ -599,37 +604,42 @@ async function processUrlWithProgress(
599604 }
600605 } ) ( ) ;
601606
602- if ( thread ) {
603- try {
604- await thread . send ( safeProgressMsg ) ;
605- } catch ( error ) {
606- logger . warn ( "Failed to send progress update to thread; falling back to channel reply" , {
607- threadId : thread . id ,
608- phase : event . phase ,
609- error : error instanceof Error ? error . message : String ( error ) ,
610- } ) ;
611- // Fallback: reply in channel so user still receives updates
607+ if ( thread ) {
612608 try {
613- await message . reply ( safeProgressMsg ) ;
609+ // Capture the returned message when possible so we can edit it
610+ // later to append the created item link/ID.
611+ const posted = await thread . send ( safeProgressMsg ) ;
612+ lastPostedMessage = posted ?? lastPostedMessage ;
613+ } catch ( error ) {
614+ logger . warn ( "Failed to send progress update to thread; falling back to channel reply" , {
615+ threadId : thread . id ,
616+ phase : event . phase ,
617+ error : error instanceof Error ? error . message : String ( error ) ,
618+ } ) ;
619+ // Fallback: reply in channel so user still receives updates
620+ try {
621+ const posted = await message . reply ( safeProgressMsg ) ;
622+ lastPostedMessage = posted ?? lastPostedMessage ;
623+ } catch ( err ) {
624+ logger . warn ( "Failed to send fallback progress reply to channel" , {
625+ messageId : message . id ,
626+ error : err instanceof Error ? err . message : String ( err ) ,
627+ } ) ;
628+ }
629+ }
630+ } else {
631+ // No thread available -> send progress updates to channel (safe)
632+ try {
633+ const posted = await message . reply ( safeProgressMsg ) ;
634+ lastPostedMessage = posted ?? lastPostedMessage ;
614635 } catch ( err ) {
615- logger . warn ( "Failed to send fallback progress reply to channel" , {
636+ logger . warn ( "Failed to send progress update to channel" , {
616637 messageId : message . id ,
638+ phase : event . phase ,
617639 error : err instanceof Error ? err . message : String ( err ) ,
618640 } ) ;
619641 }
620642 }
621- } else {
622- // No thread available -> send progress updates to channel (safe)
623- try {
624- await message . reply ( safeProgressMsg ) ;
625- } catch ( err ) {
626- logger . warn ( "Failed to send progress update to channel" , {
627- messageId : message . id ,
628- phase : event . phase ,
629- error : err instanceof Error ? err . message : String ( err ) ,
630- } ) ;
631- }
632- }
633643
634644 // If this event indicates a terminal state, mark it so we ignore
635645 // any subsequent non-actionable events.
@@ -643,58 +653,99 @@ async function processUrlWithProgress(
643653 }
644654 }
645655
646- if ( finalResult ) {
647- logger . info ( "CLI processing complete" , {
648- messageId : message . id ,
649- url,
650- success : finalResult . success ,
651- title : finalResult . title ,
652- error : finalResult . error
653- } ) ;
654-
655- if ( finalResult . success ) {
656- // Remove processing reaction and add success reaction
657- await removeReaction ( message , PROCESSING_REACTION ) ;
658- await addReaction ( message , SUCCESS_REACTION ) ;
659-
660- // Ensure title or URL displayed is wrapped in backticks to avoid embeds
661- const displayName = finalResult . title ? `\`${ finalResult . title } \`` : `\`${ url } \`` ;
662- const successMsg = `✅ Added: ${ displayName } ` ;
656+ if ( finalResult ) {
657+ logger . info ( "CLI processing complete" , {
658+ messageId : message . id ,
659+ url,
660+ success : finalResult . success ,
661+ title : finalResult . title ,
662+ error : finalResult . error
663+ } ) ;
664+
665+ if ( finalResult . success ) {
666+ // Remove processing reaction and add success reaction
667+ await removeReaction ( message , PROCESSING_REACTION ) ;
668+ await addReaction ( message , SUCCESS_REACTION ) ;
669+
670+ // Ensure title or URL displayed is wrapped in backticks to avoid embeds
671+ const displayName = finalResult . title ? `\`${ finalResult . title } \`` : `\`${ url } \`` ;
672+ // If we already have an item id, prepare an item link for the
673+ // immediate confirmation so users see the created item id/link
674+ // as soon as possible.
675+ let successMsg = `✅ Added: ${ displayName } ` ;
676+ const itemId = finalResult . id ;
677+ const itemLink = itemId !== undefined ? buildOpenBrainItemLink ( itemId , finalResult . url || url ) : undefined ;
678+ if ( itemLink ) {
679+ // Use an angle-bracketed link to avoid Discord embeds
680+ successMsg = `✅ Added: ${ displayName } — OpenBrain item: <${ itemLink } >` ;
681+ }
663682
664- let summaryTargetThread : ThreadChannel | null = thread ;
683+ let summaryTargetThread : ThreadChannel | null = thread ;
665684
666- // If we already observed a 'completed' progress event earlier and
667- // posted it to the thread/channel, avoid posting a duplicate final
668- // success message. We detect this by checking the lastPhase value.
669- const alreadyCompleted = lastPhase === "completed" ;
685+ // If we already observed a 'completed' progress event earlier and
686+ // posted it to the thread/channel, avoid posting a duplicate final
687+ // success message. We detect this by checking the lastPhase value.
688+ const alreadyCompleted = lastPhase === "completed" ;
670689
671- if ( ! alreadyCompleted ) {
672- if ( thread ) {
673- try {
674- await thread . send ( successMsg ) ;
675- } catch ( error ) {
676- logger . warn ( "Failed to send final success message to thread; falling back to channel reply" , {
677- threadId : thread . id ,
678- error : error instanceof Error ? error . message : String ( error ) ,
679- } ) ;
680- summaryTargetThread = null ;
681- // Fallback to channel reply
682- try {
683- await message . reply ( successMsg ) ;
684- } catch ( err ) {
685- logger . warn ( "Failed to send fallback success reply to channel" , {
686- messageId : message . id ,
687- error : err instanceof Error ? err . message : String ( err ) ,
688- } ) ;
690+ if ( ! alreadyCompleted ) {
691+ if ( thread ) {
692+ try {
693+ const posted = await thread . send ( successMsg ) ;
694+ // capture the final posted message when possible
695+ lastPostedMessage = posted ?? lastPostedMessage ;
696+ } catch ( error ) {
697+ logger . warn ( "Failed to send final success message to thread; falling back to channel reply" , {
698+ threadId : thread . id ,
699+ error : error instanceof Error ? error . message : String ( error ) ,
700+ } ) ;
701+ summaryTargetThread = null ;
702+ // Fallback to channel reply
703+ try {
704+ const posted = await message . reply ( successMsg ) ;
705+ lastPostedMessage = posted ?? lastPostedMessage ;
706+ } catch ( err ) {
707+ logger . warn ( "Failed to send fallback success reply to channel" , {
708+ messageId : message . id ,
709+ error : err instanceof Error ? err . message : String ( err ) ,
710+ } ) ;
711+ }
712+ }
713+ } else {
714+ // Fallback to message reply if no thread
715+ const posted = await message . reply ( successMsg ) ;
716+ lastPostedMessage = posted ?? lastPostedMessage ;
717+ }
718+ } else {
719+ // We previously posted a 'completed' progress message. If the
720+ // CLI also returned an item id, append it to the posted message
721+ // so users see the created item id immediately. If editing the
722+ // posted message is not possible, send a concise follow-up.
723+ if ( itemId !== undefined ) {
724+ try {
725+ const appended = `\n\nOpenBrain item: <${ itemLink } >\nItem ID: ${ itemId } ` ;
726+ if ( lastPostedMessage && typeof lastPostedMessage . edit === "function" ) {
727+ // Try to augment the existing message
728+ const prevContent = typeof lastPostedMessage . content === "string" ? lastPostedMessage . content : successMsg ;
729+ try {
730+ await lastPostedMessage . edit ( prevContent + appended ) ;
731+ } catch ( err ) {
732+ logger . warn ( "Failed to edit completed message with item id" , { messageId : message . id , error : err instanceof Error ? err . message : String ( err ) } ) ;
733+ // Fallback: post a short follow-up containing the item link
734+ if ( thread ) await thread . send ( `✅ OpenBrain item: <${ itemLink } >` ) ;
735+ else await message . reply ( `✅ OpenBrain item: <${ itemLink } >` ) ;
736+ }
737+ } else {
738+ // Cannot edit previous message – send a short follow-up
739+ if ( thread ) await thread . send ( `✅ OpenBrain item: <${ itemLink } >` ) ;
740+ else await message . reply ( `✅ OpenBrain item: <${ itemLink } >` ) ;
741+ }
742+ } catch ( err ) {
743+ logger . warn ( "Failed to post item link follow-up" , { messageId : message . id , error : err instanceof Error ? err . message : String ( err ) } ) ;
744+ }
745+ } else {
746+ logger . debug ( "Skipping duplicate final success message because completed event was already posted" , { messageId : message . id , url } ) ;
689747 }
690748 }
691- } else {
692- // Fallback to message reply if no thread
693- await message . reply ( successMsg ) ;
694- }
695- } else {
696- logger . debug ( "Skipping duplicate final success message because completed event was already posted" , { messageId : message . id , url } ) ;
697- }
698749
699750 await sendGeneratedSummary ( message , summaryTargetThread , finalResult ) ;
700751
0 commit comments