@@ -306,6 +306,7 @@ describe("CodexAppServerAgent", () => {
306306 response : { goal : null } ,
307307 expectedParams : { threadId : "thr_1" } ,
308308 expectedText : "No goal set. Usage: `/goal <objective>`" ,
309+ expectedGoal : undefined ,
309310 } ,
310311 {
311312 label : "reads an active goal" ,
@@ -314,6 +315,7 @@ describe("CodexAppServerAgent", () => {
314315 response : { goal : { objective : "Ship the fix" , status : "active" } } ,
315316 expectedParams : { threadId : "thr_1" } ,
316317 expectedText : "Goal active: Ship the fix" ,
318+ expectedGoal : undefined ,
317319 } ,
318320 {
319321 label : "sets a goal" ,
@@ -322,6 +324,7 @@ describe("CodexAppServerAgent", () => {
322324 response : { goal : { objective : "Ship the fix" , status : "active" } } ,
323325 expectedParams : { threadId : "thr_1" , objective : "Ship the fix" } ,
324326 expectedText : "Goal set: Ship the fix" ,
327+ expectedGoal : { objective : "Ship the fix" , status : "active" } ,
325328 } ,
326329 {
327330 label : "clears a goal" ,
@@ -330,6 +333,7 @@ describe("CodexAppServerAgent", () => {
330333 response : { cleared : true } ,
331334 expectedParams : { threadId : "thr_1" } ,
332335 expectedText : "Goal cleared." ,
336+ expectedGoal : null ,
333337 } ,
334338 {
335339 label : "pauses a goal" ,
@@ -338,6 +342,7 @@ describe("CodexAppServerAgent", () => {
338342 response : { goal : { objective : "Ship the fix" , status : "paused" } } ,
339343 expectedParams : { threadId : "thr_1" , status : "paused" } ,
340344 expectedText : "Goal paused: Ship the fix" ,
345+ expectedGoal : { objective : "Ship the fix" , status : "paused" } ,
341346 } ,
342347 {
343348 label : "resumes a goal" ,
@@ -346,13 +351,14 @@ describe("CodexAppServerAgent", () => {
346351 response : { goal : { objective : "Ship the fix" , status : "active" } } ,
347352 expectedParams : { threadId : "thr_1" , status : "active" } ,
348353 expectedText : "Goal resumed: Ship the fix" ,
354+ expectedGoal : { objective : "Ship the fix" , status : "active" } ,
349355 } ,
350356 ] ) ( "$label without starting a model turn" , async ( testCase ) => {
351357 const stub = makeStubRpc ( {
352358 "thread/start" : { thread : { id : "thr_1" } } ,
353359 [ testCase . method ] : testCase . response ,
354360 } ) ;
355- const { client, sessionUpdates } = makeFakeClient ( ) ;
361+ const { client, sessionUpdates, extNotifications } = makeFakeClient ( ) ;
356362 const agent = new CodexAppServerAgent ( client , {
357363 processOptions : { binaryPath : "/bundle/codex" } ,
358364 rpcFactory : stub . factory ,
@@ -379,6 +385,113 @@ describe("CodexAppServerAgent", () => {
379385 content : { type : "text" , text : testCase . expectedText } ,
380386 } ,
381387 } ) ;
388+ if ( testCase . expectedGoal !== undefined ) {
389+ expect ( extNotifications ) . toContainEqual ( {
390+ method : "_posthog/codex_goal" ,
391+ params : { goal : testCase . expectedGoal } ,
392+ } ) ;
393+ }
394+ } ) ;
395+
396+ it ( "handles a goal command wrapped in hidden cold-resume context" , async ( ) => {
397+ const stub = makeStubRpc ( {
398+ "thread/start" : { thread : { id : "thr_1" } } ,
399+ "thread/goal/get" : {
400+ goal : { objective : "Ship the fix" , status : "paused" } ,
401+ } ,
402+ } ) ;
403+ const { client, sessionUpdates } = makeFakeClient ( ) ;
404+ const agent = new CodexAppServerAgent ( client , {
405+ processOptions : { binaryPath : "/bundle/codex" } ,
406+ rpcFactory : stub . factory ,
407+ } ) ;
408+ await agent . newSession ( { cwd : "/repo" } as unknown as NewSessionRequest ) ;
409+
410+ await agent . prompt ( {
411+ sessionId : "thr_1" ,
412+ prompt : [
413+ {
414+ type : "text" ,
415+ text : "Previous conversation context" ,
416+ _meta : { ui : { hidden : true } } ,
417+ } ,
418+ { type : "text" , text : "/goal" } ,
419+ {
420+ type : "text" ,
421+ text : "Respond to the user above" ,
422+ _meta : { ui : { hidden : true } } ,
423+ } ,
424+ ] ,
425+ } as unknown as PromptRequest ) ;
426+
427+ expect ( stub . requests ) . toContainEqual ( {
428+ method : "thread/goal/get" ,
429+ params : { threadId : "thr_1" } ,
430+ } ) ;
431+ expect (
432+ stub . requests . some ( ( request ) => request . method === "turn/start" ) ,
433+ ) . toBe ( false ) ;
434+ expect ( sessionUpdates ) . toContainEqual ( {
435+ sessionId : "thr_1" ,
436+ update : {
437+ sessionUpdate : "user_message_chunk" ,
438+ content : { type : "text" , text : "/goal" } ,
439+ } ,
440+ } ) ;
441+ } ) ;
442+
443+ it ( "restores a persisted goal when starting a replacement thread" , async ( ) => {
444+ const restoredGoal = { objective : "Ship the fix" , status : "paused" } ;
445+ const stub = makeStubRpc ( {
446+ "thread/start" : { thread : { id : "thr_1" } } ,
447+ "thread/goal/set" : { goal : restoredGoal } ,
448+ } ) ;
449+ const { client, extNotifications } = makeFakeClient ( ) ;
450+ const agent = new CodexAppServerAgent ( client , {
451+ processOptions : { binaryPath : "/bundle/codex" } ,
452+ rpcFactory : stub . factory ,
453+ } ) ;
454+
455+ await agent . newSession ( {
456+ cwd : "/repo" ,
457+ _meta : { codexGoal : restoredGoal } ,
458+ } as unknown as NewSessionRequest ) ;
459+
460+ expect ( stub . requests ) . toContainEqual ( {
461+ method : "thread/goal/set" ,
462+ params : { threadId : "thr_1" , ...restoredGoal } ,
463+ } ) ;
464+ expect ( extNotifications ) . toContainEqual ( {
465+ method : "_posthog/codex_goal" ,
466+ params : { goal : restoredGoal } ,
467+ } ) ;
468+ } ) ;
469+
470+ it ( "interrupts a native goal turn that was already queued when paused" , async ( ) => {
471+ const stub = makeStubRpc ( {
472+ "thread/start" : { thread : { id : "thr_1" } } ,
473+ "thread/goal/set" : {
474+ goal : { objective : "Ship the fix" , status : "paused" } ,
475+ } ,
476+ } ) ;
477+ const { client } = makeFakeClient ( ) ;
478+ const agent = new CodexAppServerAgent ( client , {
479+ processOptions : { binaryPath : "/bundle/codex" } ,
480+ rpcFactory : stub . factory ,
481+ } ) ;
482+ await agent . newSession ( { cwd : "/repo" } as unknown as NewSessionRequest ) ;
483+
484+ await agent . prompt ( {
485+ sessionId : "thr_1" ,
486+ prompt : [ { type : "text" , text : "/goal pause" } ] ,
487+ } as unknown as PromptRequest ) ;
488+ stub . emit ( "turn/started" , { turn : { id : "goal_tick_1" } } ) ;
489+ await Promise . resolve ( ) ;
490+
491+ expect ( stub . requests ) . toContainEqual ( {
492+ method : "turn/interrupt" ,
493+ params : { threadId : "thr_1" , turnId : "goal_tick_1" } ,
494+ } ) ;
382495 } ) ;
383496
384497 it ( "includes buffered command output when completion omits aggregatedOutput" , async ( ) => {
0 commit comments