@@ -10,6 +10,7 @@ import { SessionManager } from "../session/manager.js";
1010import type { SessionDatabase } from "../session/types.js" ;
1111import { ProviderConfigRepo } from "../storage/repositories/provider-config-repo.js" ;
1212import { SessionMetadataRepo } from "../storage/repositories/session-metadata-repo.js" ;
13+ import { SettingsRepo } from "../storage/repositories/settings-repo.js" ;
1314import { WorkspaceRepo } from "../storage/repositories/workspace-repo.js" ;
1415import type { TerminalManager } from "../terminal/manager.js" ;
1516import { WorkspaceManager } from "../workspace/manager.js" ;
@@ -40,6 +41,9 @@ describe("Session Commands", () => {
4041 eventBus = new EventBus ( ) ;
4142 stateDir = mkdtempSync ( join ( tmpdir ( ) , "session-command-state-" ) ) ;
4243 const providerConfigRepo = createProviderConfigRepo ( join ( stateDir , "provider-configs.json" ) ) ;
44+ const settingsRepo = new SettingsRepo ( {
45+ filePath : join ( stateDir , "settings.json" ) ,
46+ } ) ;
4347 workspaceRepo = new WorkspaceRepo ( {
4448 filePath : join ( stateDir , "workspaces.json" ) ,
4549 } ) ;
@@ -79,6 +83,7 @@ describe("Session Commands", () => {
7983 terminalMgr : { } as never ,
8084 eventBus,
8185 broadcaster,
86+ settingsRepo,
8287 providerRegistry : [ ] ,
8388 fencingMgr : { } as never ,
8489 supervisorMgr : { } as never ,
@@ -163,6 +168,73 @@ describe("Session Commands", () => {
163168 }
164169 } ) ;
165170
171+ it ( "publishes agent instructions before session.create starts the agent" , async ( ) => {
172+ const testDir = join ( tmpdir ( ) , `coder-studio-session-publish-${ Date . now ( ) } ` ) ;
173+ mkdirSync ( join ( testDir , ".git" ) , { recursive : true } ) ;
174+ writeFileSync ( join ( testDir , ".git" , "HEAD" ) , "ref: refs/heads/main\n" ) ;
175+
176+ const calls : string [ ] = [ ] ;
177+ ctx . providerRegistry = providerRegistry as ProviderDefinition [ ] ;
178+ ctx . providerRuntimeDeps = {
179+ commandExists : async ( command : string ) => command === "claude" ,
180+ } ;
181+ ctx . agentInstructionPublisher = {
182+ syncWorkspace : vi . fn ( async ( ) => {
183+ calls . push ( "publish" ) ;
184+ } ) ,
185+ scheduleWorkspaceSync : vi . fn ( ) ,
186+ syncAllOpenWorkspaces : vi . fn ( ) ,
187+ } as never ;
188+
189+ const createSpy = vi . spyOn ( sessionMgr , "create" ) . mockImplementation ( async ( ) => {
190+ calls . push ( "create" ) ;
191+ return {
192+ id : "sess-1" ,
193+ workspaceId : "ws-1" ,
194+ providerId : "claude" ,
195+ terminalId : "term-1" ,
196+ capability : "full" ,
197+ state : "starting" ,
198+ startedAt : Date . now ( ) ,
199+ lastActiveAt : Date . now ( ) ,
200+ } ;
201+ } ) ;
202+
203+ try {
204+ const openResult = await dispatch (
205+ {
206+ kind : "command" ,
207+ id : "workspace-publish-order" ,
208+ op : "workspace.open" ,
209+ args : { path : testDir } ,
210+ } ,
211+ ctx
212+ ) ;
213+
214+ expect ( openResult . ok ) . toBe ( true ) ;
215+ calls . length = 0 ;
216+
217+ const result = await dispatch (
218+ {
219+ kind : "command" ,
220+ id : "session-publish-order" ,
221+ op : "session.create" ,
222+ args : {
223+ workspaceId : openResult . data ! . id ,
224+ providerId : "claude" ,
225+ } ,
226+ } ,
227+ ctx
228+ ) ;
229+
230+ expect ( result . ok ) . toBe ( true ) ;
231+ expect ( calls ) . toEqual ( [ "publish" , "create" ] ) ;
232+ } finally {
233+ createSpy . mockRestore ( ) ;
234+ rmSync ( testDir , { recursive : true , force : true } ) ;
235+ }
236+ } ) ;
237+
166238 it ( "launches a custom provider through the existing session.create flow" , async ( ) => {
167239 const testDir = join ( tmpdir ( ) , `coder-studio-custom-provider-session-${ Date . now ( ) } ` ) ;
168240 mkdirSync ( join ( testDir , ".git" ) , { recursive : true } ) ;
@@ -235,6 +307,61 @@ describe("Session Commands", () => {
235307 }
236308 } ) ;
237309
310+ it . each ( [
311+ { providerId : "gemini" , command : "gemini" , expectedCapability : "full" } ,
312+ { providerId : "cursor" , command : "agent" , expectedCapability : "full" } ,
313+ { providerId : "opencode" , command : "opencode" , expectedCapability : "limited" } ,
314+ ] ) ( "launches $providerId through the shared session.create flow" , async ( {
315+ providerId,
316+ command,
317+ expectedCapability,
318+ } ) => {
319+ const testDir = join ( tmpdir ( ) , `coder-studio-${ providerId } -session-${ Date . now ( ) } ` ) ;
320+ mkdirSync ( join ( testDir , ".git" ) , { recursive : true } ) ;
321+ writeFileSync ( join ( testDir , ".git" , "HEAD" ) , "ref: refs/heads/main\n" ) ;
322+
323+ ctx . providerRegistry = providerRegistry as ProviderDefinition [ ] ;
324+ ctx . providerRuntimeDeps = {
325+ commandExists : async ( candidate : string ) => candidate === command ,
326+ } ;
327+
328+ try {
329+ const openResult = await dispatch (
330+ {
331+ kind : "command" ,
332+ id : `workspace-${ providerId } ` ,
333+ op : "workspace.open" ,
334+ args : { path : testDir } ,
335+ } ,
336+ ctx
337+ ) ;
338+
339+ expect ( openResult . ok ) . toBe ( true ) ;
340+
341+ const result = await dispatch (
342+ {
343+ kind : "command" ,
344+ id : `session-${ providerId } ` ,
345+ op : "session.create" ,
346+ args : {
347+ workspaceId : openResult . data ! . id ,
348+ providerId,
349+ } ,
350+ } ,
351+ ctx
352+ ) ;
353+
354+ expect ( result . ok ) . toBe ( true ) ;
355+ expect ( result . data ) . toMatchObject ( {
356+ providerId,
357+ capability : expectedCapability ,
358+ state : "starting" ,
359+ } ) ;
360+ } finally {
361+ rmSync ( testDir , { recursive : true , force : true } ) ;
362+ }
363+ } ) ;
364+
238365 it ( "captures session objective and git baseline metadata when available" , async ( ) => {
239366 const testDir = join ( tmpdir ( ) , `coder-studio-session-metadata-${ Date . now ( ) } ` ) ;
240367 mkdirSync ( testDir , { recursive : true } ) ;
0 commit comments