33import os
44import re
55import select
6+ import shlex
67import shutil
78import subprocess
89import sys
1718from .agents import (
1819 codexapi_home ,
1920 control_agent ,
21+ cron_installed as agent_cron_installed ,
2022 current_hostname ,
2123 delete_agent as delete_managed_agent ,
2224 install_cron as install_agent_cron ,
@@ -194,6 +196,39 @@ def _print_managed_agent_identity():
194196 print (f"Home: { codexapi_home ()} " )
195197
196198
199+ def _agent_install_cron_command ():
200+ parts = []
201+ home = os .environ .get ("CODEXAPI_HOME" , "" ).strip ()
202+ host = os .environ .get ("CODEXAPI_HOSTNAME" , "" ).strip ()
203+ if home :
204+ parts .append (f"CODEXAPI_HOME={ shlex .quote (home )} " )
205+ if host :
206+ parts .append (f"CODEXAPI_HOSTNAME={ shlex .quote (host )} " )
207+ parts .extend (["codexapi" , "agent" , "install-cron" ])
208+ return " " .join (parts )
209+
210+
211+ def _warn_agent_scheduler_missing ():
212+ try :
213+ installed = agent_cron_installed ()
214+ except Exception as exc :
215+ print (
216+ "Warning: could not verify whether the codexapi agent scheduler hook is installed." ,
217+ file = sys .stderr ,
218+ )
219+ print (f"Reason: { exc } " , file = sys .stderr )
220+ print (f"Install it with: { _agent_install_cron_command ()} " , file = sys .stderr )
221+ return
222+ if installed :
223+ return
224+ print (
225+ "Warning: no codexapi agent scheduler hook is installed for this CODEXAPI_HOME. "
226+ "Background agent wakes will not run until you install it." ,
227+ file = sys .stderr ,
228+ )
229+ print (f"Install it with: { _agent_install_cron_command ()} " , file = sys .stderr )
230+
231+
197232def _send_reply_info (agent_ref , message_id ):
198233 """Return the matching run reply for one sent message, if already delivered."""
199234 shown = show_managed_agent (agent_ref )
@@ -1401,7 +1436,7 @@ def main(argv=None):
14011436
14021437 agent_start = agent_subparsers .add_parser (
14031438 "start" ,
1404- help = "Create a durable agent." ,
1439+ help = "Create a durable agent and return immediately unless --wait is set ." ,
14051440 )
14061441 agent_start .add_argument (
14071442 "prompt" ,
@@ -1445,6 +1480,11 @@ def main(argv=None):
14451480 "--flags" ,
14461481 help = "Additional raw CLI flags to pass to the backend." ,
14471482 )
1483+ agent_start .add_argument (
1484+ "--wait" ,
1485+ action = "store_true" ,
1486+ help = "Wait for the first local wake to finish instead of just scheduling it." ,
1487+ )
14481488
14491489 agent_subparsers .add_parser (
14501490 "list" ,
@@ -1481,21 +1521,32 @@ def main(argv=None):
14811521
14821522 agent_send = agent_subparsers .add_parser (
14831523 "send" ,
1484- help = "Queue a message for an agent." ,
1524+ help = "Queue a message for an agent and return immediately unless --wait is set ." ,
14851525 )
14861526 agent_send .add_argument ("agent_ref" , help = "Agent id, unique prefix, or name." )
14871527 agent_send .add_argument ("message" , help = "Message to queue." )
14881528 agent_send .add_argument ("--author" , help = "Author label for the message." )
1529+ agent_send .add_argument (
1530+ "--wait" ,
1531+ action = "store_true" ,
1532+ help = "Wait for a local wake after queueing the message." ,
1533+ )
14891534
14901535 for subcommand , help_text in (
1491- ("wake" , "Request an extra wake for an agent." ),
1536+ ("wake" , "Request an extra wake for an agent and return immediately unless --wait is set ." ),
14921537 ("pause" , "Pause an agent." ),
1493- ("resume" , "Resume a paused agent." ),
1538+ ("resume" , "Resume a paused agent and return immediately unless --wait is set ." ),
14941539 ("cancel" , "Cancel an agent." ),
14951540 ):
14961541 subparser = agent_subparsers .add_parser (subcommand , help = help_text )
14971542 subparser .add_argument ("agent_ref" , help = "Agent id, unique prefix, or name." )
14981543 subparser .add_argument ("--author" , help = "Author label for the command." )
1544+ if subcommand in ("wake" , "resume" ):
1545+ subparser .add_argument (
1546+ "--wait" ,
1547+ action = "store_true" ,
1548+ help = "Wait for a local wake after queueing the command." ,
1549+ )
14991550
15001551 agent_delete = agent_subparsers .add_parser (
15011552 "delete" ,
@@ -1834,6 +1885,10 @@ def main(argv=None):
18341885 args .yolo ,
18351886 args .flags ,
18361887 )
1888+ result ["waited" ] = bool (args .wait )
1889+ if args .wait :
1890+ result ["nudge" ] = nudge_agent (result ["id" ])
1891+ _warn_agent_scheduler_missing ()
18371892 print (json .dumps (result , indent = 2 , sort_keys = True ))
18381893 return
18391894 if args .agent_command == "list" :
@@ -1855,18 +1910,32 @@ def main(argv=None):
18551910 return
18561911 if args .agent_command == "send" :
18571912 result = send_agent (args .agent_ref , args .message , args .author )
1858- result ["nudge" ] = nudge_agent (args .agent_ref )
1859- reply_info = _send_reply_info (args .agent_ref , result ["id" ])
1860- if reply_info :
1861- result .update (reply_info )
1913+ result ["waited" ] = bool (args .wait )
1914+ if args .wait :
1915+ result ["nudge" ] = nudge_agent (args .agent_ref )
1916+ reply_info = _send_reply_info (args .agent_ref , result ["id" ])
1917+ if reply_info :
1918+ result .update (reply_info )
1919+ print (json .dumps (result , indent = 2 , sort_keys = True ))
1920+ return
1921+ if args .agent_command in ("wake" , "resume" ):
1922+ result = control_agent (
1923+ args .agent_ref ,
1924+ args .agent_command ,
1925+ args .author ,
1926+ )
1927+ result ["waited" ] = bool (args .wait )
1928+ if args .wait :
1929+ result ["nudge" ] = nudge_agent (args .agent_ref )
18621930 print (json .dumps (result , indent = 2 , sort_keys = True ))
18631931 return
1864- if args .agent_command in ("wake" , " pause" , "resume " , "cancel" ):
1932+ if args .agent_command in ("pause" , "cancel" ):
18651933 result = control_agent (
18661934 args .agent_ref ,
18671935 args .agent_command ,
18681936 args .author ,
18691937 )
1938+ result ["waited" ] = False
18701939 result ["nudge" ] = nudge_agent (args .agent_ref )
18711940 print (json .dumps (result , indent = 2 , sort_keys = True ))
18721941 return
0 commit comments