@@ -77,6 +77,74 @@ describe("PostHogAPIClient", () => {
7777 ) ;
7878 } ) ;
7979
80+ it ( "maps plan to read-only for cloud Codex runs" , async ( ) => {
81+ const client = new PostHogAPIClient (
82+ "http://localhost:8000" ,
83+ async ( ) => "token" ,
84+ async ( ) => "token" ,
85+ 123 ,
86+ ) ;
87+
88+ const post = vi . fn ( ) . mockResolvedValue ( {
89+ id : "task-123" ,
90+ title : "Task" ,
91+ description : "Task" ,
92+ created_at : "2026-04-14T00:00:00Z" ,
93+ updated_at : "2026-04-14T00:00:00Z" ,
94+ origin_product : "user_created" ,
95+ } ) ;
96+
97+ ( client as unknown as { api : { post : typeof post } } ) . api = { post } ;
98+
99+ await client . runTaskInCloud ( "task-123" , "feature/codex-plan" , {
100+ adapter : "codex" ,
101+ model : "gpt-5.4" ,
102+ initialPermissionMode : "plan" ,
103+ } ) ;
104+
105+ expect ( post ) . toHaveBeenCalledWith (
106+ "/api/projects/{project_id}/tasks/{id}/run/" ,
107+ expect . objectContaining ( {
108+ body : expect . objectContaining ( {
109+ initial_permission_mode : "read-only" ,
110+ } ) ,
111+ } ) ,
112+ ) ;
113+ } ) ;
114+
115+ it ( "omits the permission mode when no adapter is set" , async ( ) => {
116+ const client = new PostHogAPIClient (
117+ "http://localhost:8000" ,
118+ async ( ) => "token" ,
119+ async ( ) => "token" ,
120+ 123 ,
121+ ) ;
122+
123+ const post = vi . fn ( ) . mockResolvedValue ( {
124+ id : "task-123" ,
125+ title : "Task" ,
126+ description : "Task" ,
127+ created_at : "2026-04-14T00:00:00Z" ,
128+ updated_at : "2026-04-14T00:00:00Z" ,
129+ origin_product : "user_created" ,
130+ } ) ;
131+
132+ ( client as unknown as { api : { post : typeof post } } ) . api = { post } ;
133+
134+ await client . runTaskInCloud ( "task-123" , "feature/no-adapter" , {
135+ initialPermissionMode : "plan" ,
136+ } ) ;
137+
138+ expect ( post ) . toHaveBeenCalledWith (
139+ "/api/projects/{project_id}/tasks/{id}/run/" ,
140+ expect . objectContaining ( {
141+ body : expect . not . objectContaining ( {
142+ initial_permission_mode : expect . anything ( ) ,
143+ } ) ,
144+ } ) ,
145+ ) ;
146+ } ) ;
147+
80148 it ( "rejects unsupported reasoning effort for cloud Codex runs" , async ( ) => {
81149 const client = new PostHogAPIClient (
82150 "http://localhost:8000" ,
@@ -177,6 +245,102 @@ describe("PostHogAPIClient", () => {
177245 ) ;
178246 } ) ;
179247
248+ it ( "maps the permission mode per adapter when creating task runs" , async ( ) => {
249+ const fetch = vi . fn ( ) . mockResolvedValue ( {
250+ ok : true ,
251+ json : async ( ) => ( { id : "run-123" , environment : "cloud" } ) ,
252+ } ) ;
253+ const client = new PostHogAPIClient (
254+ "http://localhost:8000" ,
255+ async ( ) => "token" ,
256+ async ( ) => "token" ,
257+ 123 ,
258+ ) ;
259+
260+ (
261+ client as unknown as {
262+ api : { baseUrl : string ; fetcher : { fetch : typeof fetch } } ;
263+ }
264+ ) . api = {
265+ baseUrl : "http://localhost:8000" ,
266+ fetcher : { fetch } ,
267+ } ;
268+
269+ await client . createTaskRun ( "task-123" , {
270+ environment : "cloud" ,
271+ adapter : "claude" ,
272+ model : "claude-opus-4-8" ,
273+ initialPermissionMode : "read-only" ,
274+ } ) ;
275+
276+ const body = JSON . parse ( fetch . mock . calls [ 0 ] [ 0 ] . overrides . body as string ) ;
277+ expect ( body . initial_permission_mode ) . toBe ( "plan" ) ;
278+ } ) ;
279+
280+ it ( "omits the permission mode from created task runs without an adapter" , async ( ) => {
281+ const fetch = vi . fn ( ) . mockResolvedValue ( {
282+ ok : true ,
283+ json : async ( ) => ( { id : "run-123" , environment : "cloud" } ) ,
284+ } ) ;
285+ const client = new PostHogAPIClient (
286+ "http://localhost:8000" ,
287+ async ( ) => "token" ,
288+ async ( ) => "token" ,
289+ 123 ,
290+ ) ;
291+
292+ (
293+ client as unknown as {
294+ api : { baseUrl : string ; fetcher : { fetch : typeof fetch } } ;
295+ }
296+ ) . api = {
297+ baseUrl : "http://localhost:8000" ,
298+ fetcher : { fetch } ,
299+ } ;
300+
301+ await client . createTaskRun ( "task-123" , {
302+ environment : "cloud" ,
303+ initialPermissionMode : "plan" ,
304+ } ) ;
305+
306+ const body = JSON . parse ( fetch . mock . calls [ 0 ] [ 0 ] . overrides . body as string ) ;
307+ expect ( body ) . not . toHaveProperty ( "initial_permission_mode" ) ;
308+ } ) ;
309+
310+ it ( "omits the permission mode when none is selected" , async ( ) => {
311+ const client = new PostHogAPIClient (
312+ "http://localhost:8000" ,
313+ async ( ) => "token" ,
314+ async ( ) => "token" ,
315+ 123 ,
316+ ) ;
317+
318+ const post = vi . fn ( ) . mockResolvedValue ( {
319+ id : "task-123" ,
320+ title : "Task" ,
321+ description : "Task" ,
322+ created_at : "2026-04-14T00:00:00Z" ,
323+ updated_at : "2026-04-14T00:00:00Z" ,
324+ origin_product : "user_created" ,
325+ } ) ;
326+
327+ ( client as unknown as { api : { post : typeof post } } ) . api = { post } ;
328+
329+ await client . runTaskInCloud ( "task-123" , "feature/no-mode" , {
330+ adapter : "codex" ,
331+ model : "gpt-5.4" ,
332+ } ) ;
333+
334+ expect ( post ) . toHaveBeenCalledWith (
335+ "/api/projects/{project_id}/tasks/{id}/run/" ,
336+ expect . objectContaining ( {
337+ body : expect . not . objectContaining ( {
338+ initial_permission_mode : expect . anything ( ) ,
339+ } ) ,
340+ } ) ,
341+ ) ;
342+ } ) ;
343+
180344 it ( "starts an existing cloud task run with run-scoped artifact ids" , async ( ) => {
181345 const fetch = vi . fn ( ) . mockResolvedValue ( {
182346 ok : true ,
0 commit comments