11import type { Model } from "@codeworksh/aikit" ;
2- import { DateTime , Effect , Option , Stream } from "effect" ;
3- import { SqlClient } from "effect/unstable/sql" ;
2+ import { Effect , Option , Stream } from "effect" ;
43import * as Control from "../control.ts" ;
54import * as Event from "../event/event.ts" ;
65import type { EventSchema } from "../event/schema.ts" ;
6+ import { Location } from "../location/location.ts" ;
77import * as SandboxController from "../sandbox/control.ts" ;
88import { SandboxInstance as SandboxInstanceSchema } from "../sandbox/instance.ts" ;
9+ import { SandboxIO } from "../sandbox/io.ts" ;
910import { AbsolutePath } from "../schema.ts" ;
1011import { SessionMessageSchema } from "../session/message/schema.ts" ;
1112import type { Delivery } from "../session/prompt/schema.ts" ;
@@ -48,6 +49,12 @@ export interface AttachInput extends RuntimeInput {
4849 readonly sessionId : SessionSchema . ID ;
4950}
5051
52+ export interface RelinkInput {
53+ readonly sessionId : SessionSchema . ID ;
54+ readonly sandbox ?: SandboxInfo ;
55+ readonly directory ?: string ;
56+ }
57+
5158export type PromptInput =
5259 | string
5360 | {
@@ -105,15 +112,18 @@ const makeHandle = Effect.fn("Session.makeHandle")(function* (id: SessionSchema.
105112 const found = yield * sessions . get ( id ) ;
106113 if ( Option . isNone ( found ) ) return yield * new SessionStore . SessionNotFoundError ( { sessionId : id } ) ;
107114 const row = found . value ;
108- const sandboxId = SandboxInstanceSchema . fromField ( row . sandboxInstanceId ) ;
115+ // The env is the space's. A missing space or a destroyed env both read as
116+ // "no sandbox": the handle stays readable (title, directory); running it
117+ // is the mount's call, which refuses a removed instance.
118+ const space = Option . getOrUndefined ( yield * sessions . space ( id ) ) ;
109119 const sandbox =
110- sandboxId === SandboxInstanceSchema . ID . local
120+ space === undefined || space . env === SandboxInstanceSchema . ID . local
111121 ? undefined
112- : Option . getOrUndefined ( yield * sandboxes . get ( sandboxId ) ) ;
122+ : Option . getOrUndefined ( yield * sandboxes . get ( space . env ) ) ;
113123 return {
114124 id,
115125 title : row . title ,
116- directory : AbsolutePath . make ( row . directory ) ,
126+ directory : row . directory ,
117127 ...( sandbox === undefined ? { } : { sandbox } ) ,
118128 } ;
119129 } ) . pipe ( Effect . withSpan ( "Session.info" ) ) ;
@@ -135,36 +145,63 @@ const makeHandle = Effect.fn("Session.makeHandle")(function* (id: SessionSchema.
135145 } satisfies Handle ;
136146} ) ;
137147
138- const ensureLocalProject = Effect . fn ( "Session.ensureLocalProject" ) ( function * ( ) {
139- const sql = yield * SqlClient . SqlClient ;
140- const now = DateTime . toEpochMillis ( yield * DateTime . now ) ;
141- yield * sql `
142- INSERT INTO project (id, name, created_at, updated_at)
143- VALUES ('local', 'local', ${ now } , ${ now } )
144- ON CONFLICT(id) DO NOTHING
145- ` . pipe ( Effect . orDie ) ;
146- } ) ;
147-
148148export const create = Effect . fn ( "Session.create" ) ( function * ( input : CreateInput = { } ) {
149149 const sessions = yield * SessionStore . Service ;
150150 const runtime = yield * SessionRuntime . Service ;
151151 const sandboxes = yield * SandboxController . Controller ;
152152 const id = SessionSchema . ID . create ( ) ;
153153 const sandboxId = input . sandbox ?. id ?? SandboxInstanceSchema . ID . local ;
154- const directory = yield * sandboxes . resolveCwd ( sandboxId , input . directory ) ;
155- yield * ensureLocalProject ( ) ;
154+ // Mount, then resolve the cwd into its space. The mount is
155+ // what makes the directory mean anything, so resolution happens inside it.
156+ const location = yield * sandboxes . withMount (
157+ sandboxId ,
158+ Effect . provide ( Location . Service . use ( Effect . succeed ) , Location . layerMounted ( ) ) ,
159+ input . directory === undefined ? undefined : { cwd : input . directory } ,
160+ ) ;
156161 yield * sessions . create ( {
157162 id,
158- projectId : "local" ,
163+ spaceId : location . space . id ,
164+ directory : location . directory ,
159165 slug : id ,
160166 title : input . title ?? "Session" ,
161- directory : AbsolutePath . make ( directory ) ,
162- sandboxInstanceId : sandboxId ,
163167 } ) ;
164168 yield * runtime . set ( id , runtimeBindings ( input ) ) ;
165169 return yield * makeHandle ( id ) ;
166170} ) ;
167171
172+ /**
173+ * Move a session to wherever a checkout of the same project now lives — the
174+ * remote env died and the repo was cloned locally, say. `directory` is
175+ * resolved into its space exactly like `create`; the session keeps its
176+ * position under the new root when that absolute path exists there, else it
177+ * lands on the resolved directory. The transcript is untouched.
178+ */
179+ export const relink = Effect . fn ( "Session.relink" ) ( function * ( input : RelinkInput ) {
180+ const sessions = yield * SessionStore . Service ;
181+ const sandboxes = yield * SandboxController . Controller ;
182+ const sandboxId = input . sandbox ?. id ?? SandboxInstanceSchema . ID . local ;
183+ return yield * sandboxes . withMount (
184+ sandboxId ,
185+ Effect . gen ( function * ( ) {
186+ const location = yield * Location . Service . use ( Effect . succeed ) . pipe ( Effect . provide ( Location . layerMounted ( ) ) ) ;
187+ const session = yield * sessions . get ( input . sessionId ) ;
188+ const current = yield * sessions . space ( input . sessionId ) ;
189+ const rebased =
190+ Option . isSome ( session ) && Option . isSome ( current )
191+ ? SessionStore . rebaseDirectory ( current . value . location , location . space . location , session . value . directory )
192+ : undefined ;
193+ const fs = yield * SandboxIO . FileSystem ;
194+ const directory =
195+ rebased !== undefined && ( yield * fs . exists ( rebased ) . pipe ( Effect . orElseSucceed ( ( ) => false ) ) )
196+ ? rebased
197+ : location . directory ;
198+ yield * sessions . relink ( { sessionId : input . sessionId , spaceId : location . space . id , directory } ) ;
199+ return yield * makeHandle ( input . sessionId ) ;
200+ } ) ,
201+ input . directory === undefined ? undefined : { cwd : input . directory } ,
202+ ) ;
203+ } ) ;
204+
168205export const get = Effect . fn ( "Session.get" ) ( function * ( sessionId : SessionSchema . ID ) {
169206 const sessions = yield * SessionStore . Service ;
170207 const found = yield * sessions . get ( sessionId ) ;
0 commit comments