@@ -8,6 +8,8 @@ const scopedClientDirectories: string[] = []
88const registeredSessionDirectories : Array < { sessionID : string ; directory : string } > = [ ]
99let sessionRevertResult : { data ?: unknown ; error ?: unknown ; response ?: { status ?: number } } = { }
1010let questionReplyError : unknown | null = null
11+ let sessionShareResult : { data ?: unknown ; error ?: unknown ; response ?: { status ?: number } } = { }
12+ const globalUpsertedSessions : unknown [ ] = [ ]
1113
1214const mockScopedClient = {
1315 permission : {
@@ -45,6 +47,14 @@ const mockSdk = {
4547 replyCalls . push ( { method : "session.abort" , params } )
4648 return Promise . resolve ( { data : true } )
4749 } ) ,
50+ share : mock ( ( params : Record < string , unknown > ) => {
51+ replyCalls . push ( { method : "session.share" , params } )
52+ return Promise . resolve ( sessionShareResult )
53+ } ) ,
54+ unshare : mock ( ( params : Record < string , unknown > ) => {
55+ replyCalls . push ( { method : "session.unshare" , params } )
56+ return Promise . resolve ( sessionShareResult )
57+ } ) ,
4858 } ,
4959 permission : {
5060 reply : mock ( ( params : Record < string , unknown > ) => {
@@ -141,9 +151,21 @@ mock.module("./input-store", () => ({
141151} ) )
142152
143153mock . module ( "@/stores/useGlobalSessionsStore" , ( ) => ( {
154+ mergeSessionDirectoryMetadata : ( incoming : Session , existing ?: SessionWithDirectory | null ) : SessionWithDirectory => {
155+ if ( ! existing ) return incoming as SessionWithDirectory
156+ const next = { ...( incoming as SessionWithDirectory ) }
157+ if ( ! next . directory && existing . directory ) next . directory = existing . directory
158+ if ( ! next . project && existing . project ) next . project = existing . project
159+ if ( next . project && ! next . project . worktree && existing . project ?. worktree ) {
160+ next . project = { ...next . project , worktree : existing . project . worktree }
161+ }
162+ return next
163+ } ,
144164 useGlobalSessionsStore : {
145165 getState : ( ) => ( {
146- upsertSession : ( ) => { } ,
166+ upsertSession : ( session : unknown ) => {
167+ globalUpsertedSessions . push ( session )
168+ } ,
147169 } ) ,
148170 } ,
149171} ) )
@@ -161,6 +183,10 @@ import type { Message, OpencodeClient, Part, Session } from "@opencode-ai/sdk/v2
161183
162184type OptimisticAddCall = { sessionID : string ; directory ?: string | null ; message : Message ; parts : Part [ ] }
163185type OptimisticRemoveCall = { sessionID : string ; directory ?: string | null ; messageID : string }
186+ type SessionWithDirectory = Session & {
187+ directory ?: string | null
188+ project ?: { worktree ?: string | null }
189+ }
164190
165191function createStore (
166192 permissions : Record < string , PermissionRequest [ ] > ,
@@ -183,9 +209,114 @@ function createChildStores(entries: Array<[string, StoreApi<DirectoryStore>]>) {
183209 if ( ! store ) throw new Error ( `No store for ${ dir } ` )
184210 return store
185211 } ,
212+ getChild : ( dir : string ) => new Map ( entries ) . get ( dir ) ,
186213 } as unknown as import ( "./child-store" ) . ChildStoreManager
187214}
188215
216+ describe ( "shareSession live state" , ( ) => {
217+ beforeEach ( ( ) => {
218+ replyCalls . length = 0
219+ globalUpsertedSessions . length = 0
220+ sessionShareResult = { }
221+ } )
222+
223+ test ( "updates the directory live store after unsharing" , async ( ) => {
224+ const sharedSession = { id : "session-a" , time : { created : 1 } , share : { url : "https://share.example/a" } } as Session
225+ const unsharedSession = { id : "session-a" , time : { created : 1 , updated : 2 } } as Session
226+ const sessionStore = createStore ( { } , { session : [ sharedSession ] } )
227+ const otherStore = createStore ( { } , { session : [ { id : "other" , time : { created : 1 } } as Session ] } )
228+ const childStores = createChildStores ( [
229+ [ "/test/project" , sessionStore ] ,
230+ [ "/other/project" , otherStore ] ,
231+ ] )
232+ sessionShareResult = { data : unsharedSession }
233+
234+ const { setActionRefs, unshareSession } = await import ( "./session-actions" )
235+ setActionRefs ( mockSdk as unknown as OpencodeClient , childStores , ( ) => "/current/project" )
236+
237+ const result = await unshareSession ( "session-a" )
238+
239+ expect ( result ) . toBe ( unsharedSession )
240+ expect ( replyCalls . find ( ( call ) => call . method === "session.unshare" ) ?. params . directory ) . toBe ( "/test/project" )
241+ expect ( sessionStore . getState ( ) . session [ 0 ] . share ) . toBe ( undefined )
242+ expect ( otherStore . getState ( ) . session [ 0 ] . id ) . toBe ( "other" )
243+ expect ( globalUpsertedSessions ) . toEqual ( [ unsharedSession ] )
244+ } )
245+
246+ test ( "updates the directory live store after sharing" , async ( ) => {
247+ const unsharedSession = { id : "session-a" , time : { created : 1 } } as Session
248+ const sharedSession = { id : "session-a" , time : { created : 1 , updated : 2 } , share : { url : "https://share.example/a" } } as Session
249+ const sessionStore = createStore ( { } , { session : [ unsharedSession ] } )
250+ const childStores = createChildStores ( [ [ "/test/project" , sessionStore ] ] )
251+ sessionShareResult = { data : sharedSession }
252+
253+ const { setActionRefs, shareSession } = await import ( "./session-actions" )
254+ setActionRefs ( mockSdk as unknown as OpencodeClient , childStores , ( ) => "/current/project" )
255+
256+ const result = await shareSession ( "session-a" )
257+
258+ expect ( result ) . toBe ( sharedSession )
259+ expect ( replyCalls . find ( ( call ) => call . method === "session.share" ) ?. params . directory ) . toBe ( "/test/project" )
260+ expect ( sessionStore . getState ( ) . session [ 0 ] . share ?. url ) . toBe ( "https://share.example/a" )
261+ expect ( globalUpsertedSessions ) . toEqual ( [ sharedSession ] )
262+ } )
263+
264+ test ( "preserves live directory metadata while clearing share from null response" , async ( ) => {
265+ const sharedSession = {
266+ id : "session-a" ,
267+ time : { created : 1 } ,
268+ directory : "/test/project" ,
269+ project : { worktree : "/test/project" } ,
270+ share : { url : "https://share.example/a" } ,
271+ } as SessionWithDirectory
272+ const unsharedSession = {
273+ id : "session-a" ,
274+ time : { created : 1 , updated : 2 } ,
275+ share : null ,
276+ } as unknown as Session
277+ const sessionStore = createStore ( { } , { session : [ sharedSession ] } )
278+ const childStores = createChildStores ( [ [ "/test/project" , sessionStore ] ] )
279+ sessionShareResult = { data : unsharedSession }
280+
281+ const { setActionRefs, unshareSession } = await import ( "./session-actions" )
282+ setActionRefs ( mockSdk as unknown as OpencodeClient , childStores , ( ) => "/current/project" )
283+
284+ await unshareSession ( "session-a" )
285+
286+ const liveSession = sessionStore . getState ( ) . session [ 0 ] as SessionWithDirectory & { share ?: null }
287+ expect ( liveSession . share ) . toBe ( null )
288+ expect ( liveSession . directory ) . toBe ( "/test/project" )
289+ expect ( liveSession . project ?. worktree ) . toBe ( "/test/project" )
290+ } )
291+
292+ test ( "strips oversized diff snapshots before updating session stores" , async ( ) => {
293+ const sessionWithDiff = {
294+ id : "session-a" ,
295+ time : { created : 1 , updated : 2 } ,
296+ share : { url : "https://share.example/a" } ,
297+ summary : {
298+ diffs : [ { file : "a.txt" , before : "old" , after : "new" , additions : 1 , deletions : 1 } ] ,
299+ } ,
300+ } as unknown as Session
301+ const sessionStore = createStore ( { } , { session : [ { id : "session-a" , time : { created : 1 } } as Session ] } )
302+ const childStores = createChildStores ( [ [ "/test/project" , sessionStore ] ] )
303+ sessionShareResult = { data : sessionWithDiff }
304+
305+ const { setActionRefs, shareSession } = await import ( "./session-actions" )
306+ setActionRefs ( mockSdk as unknown as OpencodeClient , childStores , ( ) => "/current/project" )
307+
308+ const result = await shareSession ( "session-a" )
309+
310+ const storedDiff = ( ( sessionStore . getState ( ) . session [ 0 ] as { summary ?: { diffs ?: Array < Record < string , unknown > > } } ) . summary ?. diffs ?? [ ] ) [ 0 ]
311+ const globalDiff = ( ( ( globalUpsertedSessions [ 0 ] as { summary ?: { diffs ?: Array < Record < string , unknown > > } } ) . summary ?. diffs ?? [ ] ) [ 0 ] )
312+ const resultDiff = ( ( result as { summary ?: { diffs ?: Array < Record < string , unknown > > } } ) . summary ?. diffs ?? [ ] ) [ 0 ]
313+ expect ( storedDiff . before ) . toBe ( undefined )
314+ expect ( storedDiff . after ) . toBe ( undefined )
315+ expect ( globalDiff . before ) . toBe ( undefined )
316+ expect ( resultDiff . after ) . toBe ( undefined )
317+ } )
318+ } )
319+
189320describe ( "optimisticSend target directory" , ( ) => {
190321 beforeEach ( ( ) => {
191322 replyCalls . length = 0
0 commit comments