@@ -257,10 +257,15 @@ export const GlobalInfo = Schema.Struct({
257257} ) . annotate ( { identifier : "GlobalSession" } )
258258export type GlobalInfo = Types . DeepMutable < Schema . Schema . Type < typeof GlobalInfo > >
259259
260+ // session.slug feeds filesystem paths (see plan()); restrict to id/path-safe characters.
261+ export const SESSION_SLUG_PATTERN = / ^ [ a - z 0 - 9 ] [ a - z 0 - 9 - _ ] { 0 , 63 } $ /
262+
260263export const CreateInput = Schema . optional (
261264 Schema . Struct ( {
265+ id : Schema . optional ( SessionID ) ,
262266 parentID : Schema . optional ( SessionID ) ,
263267 title : Schema . optional ( Schema . String ) ,
268+ slug : Schema . optional ( Schema . String ) ,
264269 agent : Schema . optional ( Schema . String ) ,
265270 model : Schema . optional ( Model ) ,
266271 metadata : Schema . optional ( Metadata ) ,
@@ -416,15 +421,18 @@ export interface Interface {
416421 readonly list : ( input ?: ListInput ) => Effect . Effect < Info [ ] >
417422 readonly listGlobal : ( input ?: GlobalListInput ) => Effect . Effect < GlobalInfo [ ] >
418423 readonly create : ( input ?: {
424+ id ?: SessionID
419425 parentID ?: SessionID
420426 title ?: string
427+ slug ?: string
421428 agent ?: string
422429 model ?: Schema . Schema . Type < typeof Model >
423430 metadata ?: typeof Metadata . Type
424431 permission ?: PermissionV1 . Ruleset
425432 workspaceID ?: WorkspaceV2 . ID
426433 } ) => Effect . Effect < Info >
427434 readonly fork : ( input : { sessionID : SessionID ; messageID ?: MessageID } ) => Effect . Effect < Info , NotFound >
435+ readonly root : ( sessionID : SessionID ) => Effect . Effect < SessionID , NotFound >
428436 readonly touch : ( sessionID : SessionID ) => Effect . Effect < void >
429437 readonly get : ( id : SessionID ) => Effect . Effect < Info , NotFound >
430438 readonly setTitle : ( input : { sessionID : SessionID ; title : string } ) => Effect . Effect < void >
@@ -509,11 +517,14 @@ const layer: Layer.Layer<
509517 path ?: string
510518 metadata ?: typeof Metadata . Type
511519 permission ?: PermissionV1 . Ruleset
520+ slug ?: string
512521 } ) {
522+ if ( input . slug !== undefined && ! SESSION_SLUG_PATTERN . test ( input . slug ) )
523+ return yield * Effect . die ( new Error ( `Invalid session slug: "${ input . slug } "` ) )
513524 const ctx = yield * InstanceState . context
514525 const result : Info = {
515526 id : SessionID . descending ( input . id ) ,
516- slug : Slug . create ( ) ,
527+ slug : input . slug ?? Slug . create ( ) ,
517528 version : InstallationVersion ,
518529 projectID : ctx . project . id ,
519530 directory : input . directory ,
@@ -667,8 +678,10 @@ const layer: Layer.Layer<
667678 } )
668679
669680 const create = Effect . fn ( "Session.create" ) ( function * ( input ?: {
681+ id ?: SessionID
670682 parentID ?: SessionID
671683 title ?: string
684+ slug ?: string
672685 agent ?: string
673686 model ?: Schema . Schema . Type < typeof Model >
674687 metadata ?: typeof Metadata . Type
@@ -678,10 +691,12 @@ const layer: Layer.Layer<
678691 const ctx = yield * InstanceState . context
679692 const workspace = yield * InstanceState . workspaceID
680693 return yield * createNext ( {
694+ id : input ?. id ,
681695 parentID : input ?. parentID ,
682696 directory : ctx . directory ,
683697 path : sessionPath ( ctx . worktree , ctx . directory ) ,
684698 title : input ?. title ,
699+ slug : input ?. slug ,
685700 agent : input ?. agent ,
686701 model : input ?. model ,
687702 metadata : input ?. metadata ,
@@ -733,6 +748,15 @@ const layer: Layer.Layer<
733748 return session
734749 } )
735750
751+ const root = Effect . fn ( "Session.root" ) ( function * ( sessionID : SessionID ) {
752+ let current = sessionID
753+ while ( true ) {
754+ const s = yield * get ( current )
755+ if ( ! s . parentID ) return current
756+ current = s . parentID
757+ }
758+ } )
759+
736760 const patch = ( sessionID : SessionID , info : Patch ) =>
737761 Effect . gen ( function * ( ) {
738762 const current = yield * get ( sessionID )
@@ -910,6 +934,7 @@ const layer: Layer.Layer<
910934 listGlobal,
911935 create,
912936 fork,
937+ root,
913938 touch,
914939 get,
915940 setTitle,
0 commit comments