@@ -648,14 +648,15 @@ export function createDispatcherPlugin(config: DispatcherPluginConfig = {}): Plu
648648 // ── Automation ──────────────────────────────────────────────
649649 // Registered at both `${prefix}/automation/...` and
650650 // `${prefix}/projects/:projectId/automation/...` when project
651- // scoping is enabled. Handlers surface `req.params.projectId` to
652- // the HttpDispatcher through the `request` context so downstream
653- // resolution (see HttpDispatcher.resolveEnvironmentContext) can
654- // pick the right data driver.
651+ // scoping is enabled. Always dispatched through
652+ // `dispatcher.dispatch()` so the multi-kernel host can swap
653+ // to the per-project kernel before resolving the
654+ // `automation` service (which lives on the project kernel,
655+ // not the host kernel, in ObjectOS multi-tenant mode).
655656 const registerAutomationRoutes = ( base : string ) => {
656657 server ! . get ( `${ base } /automation` , async ( req : any , res : any ) => {
657658 try {
658- const result = await dispatcher . handleAutomation ( ' ', 'GET ' , { } , { request : req } ) ;
659+ const result = await dispatcher . dispatch ( 'GET ', '/automation ' , undefined , req . query , { request : req } ) ;
659660 sendResult ( result , res ) ;
660661 } catch ( err : any ) {
661662 errorResponse ( err , res ) ;
@@ -664,7 +665,7 @@ export function createDispatcherPlugin(config: DispatcherPluginConfig = {}): Plu
664665
665666 server ! . post ( `${ base } /automation` , async ( req : any , res : any ) => {
666667 try {
667- const result = await dispatcher . handleAutomation ( ' ', 'POST ' , req . body , { request : req } ) ;
668+ const result = await dispatcher . dispatch ( 'POST ', '/automation ' , req . body , req . query , { request : req } ) ;
668669 sendResult ( result , res ) ;
669670 } catch ( err : any ) {
670671 errorResponse ( err , res ) ;
@@ -673,7 +674,7 @@ export function createDispatcherPlugin(config: DispatcherPluginConfig = {}): Plu
673674
674675 server ! . get ( `${ base } /automation/:name` , async ( req : any , res : any ) => {
675676 try {
676- const result = await dispatcher . handleAutomation ( ` ${ req . params . name } `, 'GET' , { } , { request : req } ) ;
677+ const result = await dispatcher . dispatch ( 'GET' , `/automation/ ${ req . params . name } `, undefined , req . query , { request : req } ) ;
677678 sendResult ( result , res ) ;
678679 } catch ( err : any ) {
679680 errorResponse ( err , res ) ;
@@ -682,7 +683,7 @@ export function createDispatcherPlugin(config: DispatcherPluginConfig = {}): Plu
682683
683684 server ! . put ( `${ base } /automation/:name` , async ( req : any , res : any ) => {
684685 try {
685- const result = await dispatcher . handleAutomation ( ` ${ req . params . name } `, 'PUT' , req . body , { request : req } ) ;
686+ const result = await dispatcher . dispatch ( 'PUT' , `/automation/ ${ req . params . name } `, req . body , req . query , { request : req } ) ;
686687 sendResult ( result , res ) ;
687688 } catch ( err : any ) {
688689 errorResponse ( err , res ) ;
@@ -691,7 +692,7 @@ export function createDispatcherPlugin(config: DispatcherPluginConfig = {}): Plu
691692
692693 server ! . delete ( `${ base } /automation/:name` , async ( req : any , res : any ) => {
693694 try {
694- const result = await dispatcher . handleAutomation ( ` ${ req . params . name } `, 'DELETE' , { } , { request : req } ) ;
695+ const result = await dispatcher . dispatch ( 'DELETE' , `/automation/ ${ req . params . name } `, undefined , req . query , { request : req } ) ;
695696 sendResult ( result , res ) ;
696697 } catch ( err : any ) {
697698 errorResponse ( err , res ) ;
@@ -700,7 +701,7 @@ export function createDispatcherPlugin(config: DispatcherPluginConfig = {}): Plu
700701
701702 server ! . post ( `${ base } /automation/trigger/:name` , async ( req : any , res : any ) => {
702703 try {
703- const result = await dispatcher . handleAutomation ( ` trigger/${ req . params . name } `, 'POST' , req . body , { request : req } ) ;
704+ const result = await dispatcher . dispatch ( 'POST' , `/automation/ trigger/${ req . params . name } `, req . body , req . query , { request : req } ) ;
704705 sendResult ( result , res ) ;
705706 } catch ( err : any ) {
706707 errorResponse ( err , res ) ;
@@ -709,7 +710,7 @@ export function createDispatcherPlugin(config: DispatcherPluginConfig = {}): Plu
709710
710711 server ! . post ( `${ base } /automation/:name/trigger` , async ( req : any , res : any ) => {
711712 try {
712- const result = await dispatcher . handleAutomation ( ` ${ req . params . name } /trigger`, 'POST' , req . body , { request : req } ) ;
713+ const result = await dispatcher . dispatch ( 'POST' , `/automation/ ${ req . params . name } /trigger`, req . body , req . query , { request : req } ) ;
713714 sendResult ( result , res ) ;
714715 } catch ( err : any ) {
715716 errorResponse ( err , res ) ;
@@ -718,7 +719,7 @@ export function createDispatcherPlugin(config: DispatcherPluginConfig = {}): Plu
718719
719720 server ! . post ( `${ base } /automation/:name/toggle` , async ( req : any , res : any ) => {
720721 try {
721- const result = await dispatcher . handleAutomation ( ` ${ req . params . name } /toggle`, 'POST' , req . body , { request : req } ) ;
722+ const result = await dispatcher . dispatch ( 'POST' , `/automation/ ${ req . params . name } /toggle`, req . body , req . query , { request : req } ) ;
722723 sendResult ( result , res ) ;
723724 } catch ( err : any ) {
724725 errorResponse ( err , res ) ;
@@ -727,7 +728,7 @@ export function createDispatcherPlugin(config: DispatcherPluginConfig = {}): Plu
727728
728729 server ! . get ( `${ base } /automation/:name/runs` , async ( req : any , res : any ) => {
729730 try {
730- const result = await dispatcher . handleAutomation ( ` ${ req . params . name } /runs`, 'GET' , { } , { request : req } , req . query ) ;
731+ const result = await dispatcher . dispatch ( 'GET' , `/automation/ ${ req . params . name } /runs`, undefined , req . query , { request : req } ) ;
731732 sendResult ( result , res ) ;
732733 } catch ( err : any ) {
733734 errorResponse ( err , res ) ;
@@ -736,14 +737,48 @@ export function createDispatcherPlugin(config: DispatcherPluginConfig = {}): Plu
736737
737738 server ! . get ( `${ base } /automation/:name/runs/:runId` , async ( req : any , res : any ) => {
738739 try {
739- const result = await dispatcher . handleAutomation ( ` ${ req . params . name } /runs/${ req . params . runId } `, 'GET' , { } , { request : req } ) ;
740+ const result = await dispatcher . dispatch ( 'GET' , `/automation/ ${ req . params . name } /runs/${ req . params . runId } `, undefined , req . query , { request : req } ) ;
740741 sendResult ( result , res ) ;
741742 } catch ( err : any ) {
742743 errorResponse ( err , res ) ;
743744 }
744745 } ) ;
745746 } ;
746747
748+ // ── AI / Assistants ─────────────────────────────────────────
749+ // The AI service plugin registers a large, dynamic surface
750+ // (chat, models, conversations, tools, agents, assistants)
751+ // whose exact routes are built at start() time from the
752+ // service's tool / agent registries. To support multi-tenant
753+ // hosts where the AI service lives on per-project kernels,
754+ // mount a method-wildcard catch-all that always dispatches
755+ // through `dispatcher.dispatch()` — that triggers the kernel
756+ // swap and then routes via `handleAI`, which looks up the
757+ // AI service on the current (project) kernel.
758+ const registerAIRoutes = ( base : string ) => {
759+ const wildcards : Array < [ 'get' | 'post' | 'delete' | 'put' , string ] > = [
760+ [ 'get' , `${ base } /ai/*` ] ,
761+ [ 'post' , `${ base } /ai/*` ] ,
762+ [ 'delete' , `${ base } /ai/*` ] ,
763+ [ 'put' , `${ base } /ai/*` ] ,
764+ ] ;
765+ for ( const [ method , pattern ] of wildcards ) {
766+ ( server as any ) ! [ method ] ( pattern , async ( req : any , res : any ) => {
767+ try {
768+ // Reconstruct the AI subpath without the prefix
769+ // so dispatch() routes via the /ai branch.
770+ const fullPath : string = req . path ?? '' ;
771+ const idx = fullPath . lastIndexOf ( '/ai' ) ;
772+ const aiSubPath = idx >= 0 ? fullPath . slice ( idx ) : '/ai' ;
773+ const result = await dispatcher . dispatch ( method . toUpperCase ( ) , aiSubPath , req . body , req . query , { request : req } ) ;
774+ sendResult ( result , res ) ;
775+ } catch ( err : any ) {
776+ errorResponse ( err , res ) ;
777+ }
778+ } ) ;
779+ }
780+ } ;
781+
747782 // ── Actions (server-registered handlers, e.g. CRM convertLead) ───
748783 // Bridges UI `script` / `modal` actions to ObjectQL handlers
749784 // registered via `engine.registerAction(object, action, fn)`.
@@ -776,12 +811,15 @@ export function createDispatcherPlugin(config: DispatcherPluginConfig = {}): Plu
776811 if ( enableProjectScoping && projectResolution === 'required' ) {
777812 registerAutomationRoutes ( `${ prefix } /projects/:projectId` ) ;
778813 registerActionRoutes ( `${ prefix } /projects/:projectId` ) ;
814+ registerAIRoutes ( `${ prefix } /projects/:projectId` ) ;
779815 } else {
780816 registerAutomationRoutes ( prefix ) ;
781817 registerActionRoutes ( prefix ) ;
818+ registerAIRoutes ( prefix ) ;
782819 if ( enableProjectScoping ) {
783820 registerAutomationRoutes ( `${ prefix } /projects/:projectId` ) ;
784821 registerActionRoutes ( `${ prefix } /projects/:projectId` ) ;
822+ registerAIRoutes ( `${ prefix } /projects/:projectId` ) ;
785823 }
786824 }
787825
0 commit comments