@@ -750,65 +750,36 @@ export class AIServicePlugin implements Plugin {
750750 ctx . logger . info ( `[AI] ${ DATA_TOOL_DEFINITIONS . length } data tools registered as metadata` ) ;
751751 }
752752
753- // Register the built-in data_chat agent (requires metadata service)
753+ // Register the built-in agent + skills (requires metadata service).
754+ //
755+ // UPSERT, not exists-gate (ADR-0040): built-in records are
756+ // platform-owned — when the shipped definition changes (new skills,
757+ // new instructions), existing environments must pick it up on next
758+ // boot. The old exists-gate froze the FIRST shipped version forever
759+ // once sys_metadata became durable, which silently stranded every
760+ // persona/skill improvement on existing envs. Tenants who want a
761+ // different assistant define a CUSTOM agent and bind it via
762+ // app.defaultAgent — editing built-ins in place is not a supported
763+ // path, so an unconditional content-refresh clobbers nothing legit.
754764 if ( metadataService ) {
755- try {
756- const agentExists =
757- typeof metadataService . exists === 'function'
758- ? await withTimeout ( metadataService . exists ( 'agent' , DATA_CHAT_AGENT . name ) )
759- : false ;
760-
761- if ( agentExists === null ) {
762- ctx . logger . warn ( '[AI] Metadata service timed out checking data_chat agent, skipping' ) ;
763- } else if ( ! agentExists ) {
764- await withTimeout ( metadataService . register ( 'agent' , DATA_CHAT_AGENT . name , DATA_CHAT_AGENT ) ) ;
765- console . log ( '[AI] Registered data_chat agent to metadataService' ) ;
766- ctx . logger . info ( '[AI] data_chat agent registered' ) ;
767- } else {
768- console . log ( '[AI] data_chat agent already exists, skipping' ) ;
769- ctx . logger . debug ( '[AI] data_chat agent already exists, skipping auto-registration' ) ;
770- }
771- } catch ( err ) {
772- ctx . logger . warn ( '[AI] Failed to register data_chat agent' , err instanceof Error ? { error : err . message , stack : err . stack } : { error : String ( err ) } ) ;
773- }
774-
775- // Register the built-in data_explorer skill (capability bundle for data_chat)
776- try {
777- const skillExists =
778- typeof metadataService . exists === 'function'
779- ? await withTimeout ( metadataService . exists ( 'skill' , DATA_EXPLORER_SKILL . name ) )
780- : false ;
781-
782- if ( skillExists === null ) {
783- ctx . logger . warn ( '[AI] Metadata service timed out checking data_explorer skill, skipping' ) ;
784- } else if ( ! skillExists ) {
785- await withTimeout ( metadataService . register ( 'skill' , DATA_EXPLORER_SKILL . name , DATA_EXPLORER_SKILL ) ) ;
786- ctx . logger . info ( '[AI] data_explorer skill registered' ) ;
787- } else {
788- ctx . logger . debug ( '[AI] data_explorer skill already exists, skipping auto-registration' ) ;
789- }
790- } catch ( err ) {
791- ctx . logger . warn ( '[AI] Failed to register data_explorer skill' , err instanceof Error ? { error : err . message } : { error : String ( err ) } ) ;
792- }
793-
794- // Register the built-in actions_executor skill (write-side bundle for data_chat)
795- try {
796- const skillExists =
797- typeof metadataService . exists === 'function'
798- ? await withTimeout ( metadataService . exists ( 'skill' , ACTIONS_EXECUTOR_SKILL . name ) )
799- : false ;
800-
801- if ( skillExists === null ) {
802- ctx . logger . warn ( '[AI] Metadata service timed out checking actions_executor skill, skipping' ) ;
803- } else if ( ! skillExists ) {
804- await withTimeout ( metadataService . register ( 'skill' , ACTIONS_EXECUTOR_SKILL . name , ACTIONS_EXECUTOR_SKILL ) ) ;
805- ctx . logger . info ( '[AI] actions_executor skill registered' ) ;
806- } else {
807- ctx . logger . debug ( '[AI] actions_executor skill already exists, skipping auto-registration' ) ;
765+ const upsertBuiltin = async ( type : string , name : string , def : unknown ) : Promise < void > => {
766+ try {
767+ const stored = await withTimeout ( metadataService . get ( type , name ) ) ;
768+ if ( stored !== null && stored !== undefined && JSON . stringify ( stored ) === JSON . stringify ( def ) ) {
769+ ctx . logger . debug ( `[AI] built-in ${ type } ${ name } up to date` ) ;
770+ return ;
771+ }
772+ await withTimeout ( metadataService . register ( type , name , def ) ) ;
773+ ctx . logger . info (
774+ stored ? `[AI] built-in ${ type } ${ name } refreshed (shipped definition changed)` : `[AI] built-in ${ type } ${ name } registered` ,
775+ ) ;
776+ } catch ( err ) {
777+ ctx . logger . warn ( `[AI] Failed to register built-in ${ type } ${ name } ` , err instanceof Error ? { error : err . message } : { error : String ( err ) } ) ;
808778 }
809- } catch ( err ) {
810- ctx . logger . warn ( '[AI] Failed to register actions_executor skill' , err instanceof Error ? { error : err . message } : { error : String ( err ) } ) ;
811- }
779+ } ;
780+ await upsertBuiltin ( 'agent' , DATA_CHAT_AGENT . name , DATA_CHAT_AGENT ) ;
781+ await upsertBuiltin ( 'skill' , DATA_EXPLORER_SKILL . name , DATA_EXPLORER_SKILL ) ;
782+ await upsertBuiltin ( 'skill' , ACTIONS_EXECUTOR_SKILL . name , ACTIONS_EXECUTOR_SKILL ) ;
812783 }
813784 }
814785 } catch {
0 commit comments