@@ -19,10 +19,13 @@ import * as Stream from "effect/Stream"
1919import { ApiConflictError , ApiInternalError , ApiNotFoundError } from "../api/errors.js"
2020import {
2121 containerCodexSkillsPath ,
22+ externalSkillerLaunchUrl ,
2223 parseDockerMountLines ,
2324 remapContainerPathToMountedHost ,
25+ resolveConfiguredSkillerWebUrl ,
2426 sameSkillerScope ,
2527 skillerBrowserScopeForContainer ,
28+ type SkillerBrowserScope ,
2629 type SkillerContainerScope
2730} from "./skiller-core.js"
2831import { getProjectItemByKey } from "./projects.js"
@@ -31,14 +34,21 @@ import { getProjectTerminalSession } from "./terminal-sessions.js"
3134export type SkillerLaunch = {
3235 readonly alreadyRunning : boolean
3336 readonly appPath : string
37+ readonly backendUrl : string | null
3438 readonly logPath : string
39+ readonly mode : "bundled" | "external"
3540 readonly pid : number | null
3641 readonly scope : SkillerContainerScope | null
3742 readonly startedAtIso : string
3843 readonly trpcBasePath : string
3944 readonly trpcPort : number
4045}
4146
47+ export type SkillerProjectContext = {
48+ readonly browserScope : SkillerBrowserScope
49+ readonly scope : SkillerContainerScope
50+ }
51+
4252type SkillerProcess = {
4353 readonly appPath : string
4454 readonly logPath : string
@@ -133,7 +143,9 @@ const toLaunch = (
133143) : SkillerLaunch => ( {
134144 alreadyRunning,
135145 appPath : sessionId === undefined ? process . appPath : sessionSkillerAppPath ( sessionId ) ,
146+ backendUrl : null ,
136147 logPath : process . logPath ,
148+ mode : "bundled" ,
137149 pid : process . process . pid ?? null ,
138150 scope : process . scope ,
139151 startedAtIso : process . startedAtIso ,
@@ -369,6 +381,27 @@ const resolveRequestedSkillerScope = (
369381 Effect . flatMap ( ( project ) => resolveSkillerScope ( projectKey , project ) )
370382 )
371383
384+ export const readSkillerProjectContext = (
385+ projectKey : string ,
386+ sessionId : string | null
387+ ) : Effect . Effect <
388+ SkillerProjectContext ,
389+ ApiConflictError | ApiInternalError | ApiNotFoundError | PlatformError ,
390+ ListProjectsContext
391+ > =>
392+ getProjectItemByKey ( projectKey ) . pipe (
393+ Effect . flatMap ( ( project ) =>
394+ sessionId === null
395+ ? Effect . succeed ( project )
396+ : getProjectTerminalSession ( project . projectDir , sessionId ) . pipe ( Effect . as ( project ) )
397+ ) ,
398+ Effect . flatMap ( ( project ) => resolveSkillerScope ( projectKey , project ) ) ,
399+ Effect . map ( ( scope ) => ( {
400+ browserScope : skillerBrowserScopeForContainer ( scope , sessionId ) ,
401+ scope
402+ } ) )
403+ )
404+
372405const waitForSkillerReady = ( trpcPort : number ) : Effect . Effect < void , ApiInternalError > =>
373406 Effect . tryPromise ( {
374407 catch : ( cause ) => new ApiInternalError ( {
@@ -794,9 +827,43 @@ const touchSkillerActivity = (
794827 )
795828 )
796829
830+ const externalSkillerLaunch = (
831+ scope : SkillerContainerScope | null ,
832+ projectKey : string | undefined ,
833+ sessionId : string | undefined ,
834+ backendUrl : string
835+ ) : Effect . Effect < SkillerLaunch | null , ApiInternalError > => {
836+ const config = resolveConfiguredSkillerWebUrl ( process . env )
837+ if ( config . _tag === "Disabled" ) {
838+ return Effect . succeed ( null )
839+ }
840+ if ( config . _tag === "Invalid" ) {
841+ return Effect . fail ( new ApiInternalError ( { message : config . message } ) )
842+ }
843+ const appPath = externalSkillerLaunchUrl ( {
844+ backendUrl,
845+ projectKey,
846+ sessionId,
847+ skillerWebUrl : config . baseUrl
848+ } )
849+ return Effect . succeed ( {
850+ alreadyRunning : true ,
851+ appPath,
852+ backendUrl,
853+ logPath : "" ,
854+ mode : "external" ,
855+ pid : null ,
856+ scope,
857+ startedAtIso : new Date ( ) . toISOString ( ) ,
858+ trpcBasePath : `${ config . baseUrl } /trpc` ,
859+ trpcPort : 0
860+ } )
861+ }
862+
797863export const openSkiller = (
798864 projectKey ?: string ,
799- sessionId ?: string
865+ sessionId ?: string ,
866+ backendUrl = "http://localhost:3334"
800867) : Effect . Effect <
801868 SkillerLaunch ,
802869 ApiConflictError | ApiInternalError | ApiNotFoundError | PlatformError ,
@@ -806,6 +873,10 @@ export const openSkiller = (
806873 const scope = yield * _ ( resolveRequestedSkillerScope ( projectKey ) )
807874 yield * _ ( touchSkillerActivity ( scope ) )
808875 rememberSessionScope ( sessionId , scope )
876+ const externalLaunch = yield * _ ( externalSkillerLaunch ( scope , projectKey , sessionId , backendUrl ) )
877+ if ( externalLaunch !== null ) {
878+ return externalLaunch
879+ }
809880 if ( currentProcess !== null && isRunning ( currentProcess . process ) ) {
810881 if ( sameSkillerScope ( currentProcess . scope , scope ) ) {
811882 yield * _ ( Effect . try ( {
@@ -851,7 +922,8 @@ export const hasLiveProjectSkillerSession = (projectId: string): boolean =>
851922
852923export const openSkillerForTerminalSession = (
853924 projectKey : string ,
854- sessionId : string
925+ sessionId : string ,
926+ backendUrl = "http://localhost:3334"
855927) : Effect . Effect <
856928 SkillerLaunch ,
857929 ApiConflictError | ApiInternalError | ApiNotFoundError | PlatformError ,
@@ -863,7 +935,7 @@ export const openSkillerForTerminalSession = (
863935 Effect . as ( projectKey )
864936 )
865937 ) ,
866- Effect . flatMap ( ( resolvedProjectKey ) => openSkiller ( resolvedProjectKey , sessionId ) )
938+ Effect . flatMap ( ( resolvedProjectKey ) => openSkiller ( resolvedProjectKey , sessionId , backendUrl ) )
867939 )
868940
869941export const parseSkillerRoute = ( pathname : string ) : SkillerRoute | null => {
0 commit comments