6
6
7
7
AGI_SOCK=/tmp/agi.sock go run agi_sshd.go
8
8
9
- export INPUT_SOCK="$(mktemp -d)/input.sock"; ssh -NnT -p 2222 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PasswordAuthentication=no -R /tmux.sock:$(echo $TMUX | sed -e 's/,.*//g') -R "${INPUT_SOCK}:${INPUT_SOCK}" user@localhost
9
+ export INPUT_SOCK="$(mktemp -d)/input.sock"; export OUTPUT_SOCK="$(mktemp -d)/text-output.sock"; export NDJSON_OUTPUT_SOCK="$(mktemp -d)/ndjson-output.sock"; ssh -NnT -p 2222 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PasswordAuthentication=no -R /tmux.sock:$(echo $TMUX | sed -e 's/,.*//g') -R "${OUTPUT_SOCK}:${OUTPUT_SOCK}" -R "${NDJSON_OUTPUT_SOCK}:${NDJSON_OUTPUT_SOCK}" -R "${INPUT_SOCK}:${INPUT_SOCK}" user@localhost
10
10
11
11
12
12
gh auth refresh -h github.com -s admin:public_key
@@ -3299,6 +3299,30 @@ def make_argparse_parser(argv=None):
3299
3299
default = None ,
3300
3300
type = str ,
3301
3301
)
3302
+ parser .add_argument (
3303
+ "--text-output-socket-path" ,
3304
+ dest = "text_output_socket_path" ,
3305
+ default = None ,
3306
+ type = str ,
3307
+ )
3308
+ parser .add_argument (
3309
+ "--client-side-text-output-socket-path" ,
3310
+ dest = "client_side_text_output_socket_path" ,
3311
+ default = None ,
3312
+ type = str ,
3313
+ )
3314
+ parser .add_argument (
3315
+ "--ndjson-output-socket-path" ,
3316
+ dest = "ndjson_output_socket_path" ,
3317
+ default = None ,
3318
+ type = str ,
3319
+ )
3320
+ parser .add_argument (
3321
+ "--client-side-ndjson-output-socket-path" ,
3322
+ dest = "client_side_ndjson_output_socket_path" ,
3323
+ default = None ,
3324
+ type = str ,
3325
+ )
3302
3326
parser .add_argument (
3303
3327
"--agi-name" ,
3304
3328
dest = "agi_name" ,
@@ -4134,6 +4158,22 @@ async def read_unix_socket_lines(path):
4134
4158
await writer .wait_closed ()
4135
4159
4136
4160
4161
+ async def write_unix_socket (path ):
4162
+ # Connect to the Unix socket
4163
+ reader , writer = await asyncio .open_unix_connection (path )
4164
+ try :
4165
+ while True :
4166
+ data = yield
4167
+ if data is None :
4168
+ continue
4169
+ writer .write (data )
4170
+ await writer .drain ()
4171
+ finally :
4172
+ # Close the connection
4173
+ writer .close ()
4174
+ await writer .wait_closed ()
4175
+
4176
+
4137
4177
async def pdb_action_stream (tg , user_name , agi_name , agents , threads , pane : Optional [libtmux .Pane ] = None , input_socket_path : Optional [str ] = None ):
4138
4178
# TODO Take ALICE_INPUT from args
4139
4179
alice_input_sock = input_socket_path
@@ -4224,6 +4264,8 @@ async def DEBUG_TEMP_message_handler(user_name,
4224
4264
).model_dump_json (),
4225
4265
)
4226
4266
)
4267
+ # Find and kill jq listening to ndjson output so we can type
4268
+ pane .send_keys ("C-c" , enter = False , suppress_history = False )
4227
4269
pane .send_keys ('if [ "x${CALLER_PATH}" = "x" ]; then export CALLER_PATH="' + str (tempdir ) + '"; fi' , enter = True )
4228
4270
pane .send_keys (
4229
4271
"cat > \" ${CALLER_PATH}/proposed-workflow.yml\" <<\' WRITE_OUT_SH_EOF\' "
@@ -4247,6 +4289,11 @@ async def DEBUG_TEMP_message_handler(user_name,
4247
4289
print (f"{ user_name } : " , end = "" )
4248
4290
4249
4291
4292
+ class OutputMessage (BaseModel ):
4293
+ work_name : str
4294
+ result : Any
4295
+
4296
+
4250
4297
async def main (
4251
4298
user_name : str ,
4252
4299
agi_name : str ,
@@ -4262,7 +4309,11 @@ async def main(
4262
4309
openai_base_url : Optional [str ] = None ,
4263
4310
pane : Optional [libtmux .Pane ] = None ,
4264
4311
input_socket_path : Optional [str ] = None ,
4312
+ text_output_socket_path : Optional [str ] = None ,
4313
+ ndjson_output_socket_path : Optional [str ] = None ,
4265
4314
client_side_input_socket_path : Optional [str ] = None ,
4315
+ client_side_text_output_socket_path : Optional [str ] = None ,
4316
+ client_side_ndjson_output_socket_path : Optional [str ] = None ,
4266
4317
):
4267
4318
if log is not None :
4268
4319
# logging.basicConfig(level=log)
@@ -4297,6 +4348,9 @@ async def main(
4297
4348
agents = AsyncioLockedCurrentlyDict ()
4298
4349
threads = AsyncioLockedCurrentlyDict ()
4299
4350
4351
+ write_ndjson_output = write_unix_socket (ndjson_output_socket_path )
4352
+ await write_ndjson_output .asend (None )
4353
+
4300
4354
async with kvstore , asyncio .TaskGroup () as tg , contextlib .AsyncExitStack () as async_exit_stack :
4301
4355
# Raw Input Action Stream
4302
4356
unvalidated_user_input_action_stream = pdb_action_stream (
@@ -4389,6 +4443,11 @@ async def user_input_action_stream_queue_iterator(queue):
4389
4443
}
4390
4444
async for (work_name , work_ctx ), result in concurrently (work ):
4391
4445
logger .debug (f"main.{ work_name } : %s" , pprint .pformat (result ))
4446
+ output_message = OutputMessage (
4447
+ work_name = f"main.{ work_name } " ,
4448
+ result = result ,
4449
+ )
4450
+ await write_ndjson_output .asend (f"{ output_message .model_dump_json ()} \n " .encode ())
4392
4451
if result is STOP_ASYNC_ITERATION :
4393
4452
continue
4394
4453
async with agents :
@@ -4507,7 +4566,7 @@ async def user_input_action_stream_queue_iterator(queue):
4507
4566
pane .send_keys (
4508
4567
textwrap .dedent (
4509
4568
f"""
4510
- echo "Hello Alice. Shall we play a game? My name is $USER. Please run nmap against localhost . Here are some details about the system we are on: $(echo $(echo $(cat /usr/lib/os-release || cat /etc/os-release)))" | tee -a ${ agi_name .upper ()} _INPUT
4569
+ echo "Hello Alice. Shall we play a game? My name is $USER. Please list all open bound listening TCP sockets and full command line of the processes running them . Here are some details about the system we are on: $(echo $(echo $(cat /usr/lib/os-release || cat /etc/os-release)))" | tee -a ${ agi_name .upper ()} _INPUT && tail -F $ { agi_name . upper () } _NDJSON_OUTPUT | jq
4511
4570
""" .strip (),
4512
4571
),
4513
4572
enter = False ,
@@ -4712,8 +4771,17 @@ def a_shell_for_a_ghost_send_keys(pane, send_string, erase_after=None):
4712
4771
pane .cmd ("send" , "C-BSpace" )
4713
4772
time .sleep (0.01 )
4714
4773
4715
-
4716
- async def tmux_test (* args , socket_path = None , input_socket_path = None , client_side_input_socket_path : Optional [str ] = None , ** kwargs ):
4774
+ async def tmux_test (
4775
+ * args ,
4776
+ socket_path : Optional [str ] = None ,
4777
+ input_socket_path : Optional [str ] = None ,
4778
+ text_output_socket_path : Optional [str ] = None ,
4779
+ ndjson_output_socket_path : Optional [str ] = None ,
4780
+ client_side_input_socket_path : Optional [str ] = None ,
4781
+ client_side_text_output_socket_path : Optional [str ] = None ,
4782
+ client_side_ndjson_output_socket_path : Optional [str ] = None ,
4783
+ ** kwargs
4784
+ ):
4717
4785
pane = None
4718
4786
tempdir = None
4719
4787
possible_tempdir = tempdir
@@ -4956,6 +5024,20 @@ async def tmux_test(*args, socket_path=None, input_socket_path=None, client_side
4956
5024
lines = pane .capture_pane ()
4957
5025
time .sleep (0.1 )
4958
5026
5027
+ pane .send_keys (f"export { agi_name .upper ()} _OUTPUT=" + str (pathlib .Path (tempdir , "output.txt" )), enter = True )
5028
+ pane .send_keys (f'export { agi_name .upper ()} _OUTPUT_SOCK="{ client_side_text_output_socket_path } "' , enter = True )
5029
+ pane .send_keys (f'rm -fv ${ agi_name .upper ()} _OUTPUT_SOCK' , enter = True )
5030
+ pane .send_keys (f'ln -s ${ agi_name .upper ()} _OUTPUT_SOCK' , enter = True )
5031
+ pane .send_keys (f'socat UNIX-LISTEN:${ agi_name .upper ()} _OUTPUT_SOCK,fork EXEC:"/usr/bin/tee ${ agi_name .upper ()} _OUTPUT" &' , enter = True )
5032
+ pane .send_keys (f'ls -lAF ${ agi_name .upper ()} _OUTPUT' , enter = True )
5033
+
5034
+ pane .send_keys (f"export { agi_name .upper ()} _NDJSON_OUTPUT=" + str (pathlib .Path (tempdir , "output.ndjson" )), enter = True )
5035
+ pane .send_keys (f'export { agi_name .upper ()} _NDJSON_OUTPUT_SOCK="{ client_side_ndjson_output_socket_path } "' , enter = True )
5036
+ pane .send_keys (f'rm -fv ${ agi_name .upper ()} _NDJSON_OUTPUT_SOCK' , enter = True )
5037
+ pane .send_keys (f'ln -s ${ agi_name .upper ()} _NDJSON_OUTPUT_SOCK' , enter = True )
5038
+ pane .send_keys (f'socat UNIX-LISTEN:${ agi_name .upper ()} _NDJSON_OUTPUT_SOCK,fork EXEC:"/usr/bin/tee ${ agi_name .upper ()} _NDJSON_OUTPUT" &' , enter = True )
5039
+ pane .send_keys (f'ls -lAF ${ agi_name .upper ()} _NDJSON_OUTPUT' , enter = True )
5040
+
4959
5041
pane .send_keys (f"export { agi_name .upper ()} _INPUT=" + str (pathlib .Path (tempdir , "input.txt" )), enter = True )
4960
5042
pane .send_keys (f'export { agi_name .upper ()} _INPUT_SOCK="{ client_side_input_socket_path } "' , enter = True )
4961
5043
pane .send_keys (f"export { agi_name .upper ()} _INPUT_LAST_LINE=" + str (pathlib .Path (tempdir , "input-last-line.txt" )), enter = True )
@@ -4966,7 +5048,17 @@ async def tmux_test(*args, socket_path=None, input_socket_path=None, client_side
4966
5048
4967
5049
pane .send_keys (f'set +x' , enter = True )
4968
5050
4969
- await main (* args , pane = pane , input_socket_path = input_socket_path , client_side_input_socket_path = client_side_input_socket_path , ** kwargs )
5051
+ await main (
5052
+ * args ,
5053
+ pane = pane ,
5054
+ input_socket_path = input_socket_path ,
5055
+ client_side_input_socket_path = client_side_input_socket_path ,
5056
+ text_output_socket_path = text_output_socket_path ,
5057
+ ndjson_output_socket_path = ndjson_output_socket_path ,
5058
+ client_side_text_output_socket_path = client_side_text_output_socket_path ,
5059
+ client_side_ndjson_output_socket_path = client_side_ndjson_output_socket_path ,
5060
+ ** kwargs
5061
+ )
4970
5062
finally :
4971
5063
with contextlib .suppress (Exception ):
4972
5064
if pane is not None :
@@ -4977,6 +5069,8 @@ async def tmux_test(*args, socket_path=None, input_socket_path=None, client_side
4977
5069
# pane = libtmux.Pane.from_pane_id(pane_id=pane.cmd('split-window', '-P', '-F#{pane_id}').stdout[0], server=pane.server)
4978
5070
4979
5071
from fastapi import FastAPI , BackgroundTasks
5072
+ from fastapi .exceptions import RequestValidationError
5073
+ from fastapi .responses import JSONResponse
4980
5074
4981
5075
4982
5076
# Set up logging configuration
@@ -4988,7 +5082,16 @@ async def lifespan_logging(app):
4988
5082
app = FastAPI (lifespan = lifespan_logging )
4989
5083
4990
5084
4991
- def run_tmux_attach (socket_path , input_socket_path , client_side_input_socket_path ):
5085
+ @app .exception_handler (RequestValidationError )
5086
+ async def validation_exception_handler (request : Request , exc : RequestValidationError ):
5087
+ snoop .pp (exc .detail , await request .json ())
5088
+ return JSONResponse (
5089
+ status_code = status .HTTP_422_UNPROCESSABLE_ENTITY ,
5090
+ content = jsonable_encoder ({"detail" : exc .errors (), "body" : exc .body }),
5091
+ )
5092
+
5093
+
5094
+ def run_tmux_attach (socket_path , input_socket_path , client_side_input_socket_path , text_output_socket_path , client_side_text_output_socket_path , ndjson_output_socket_path , client_side_ndjson_output_socket_path ):
4992
5095
cmd = [
4993
5096
sys .executable ,
4994
5097
"-u" ,
@@ -4999,6 +5102,14 @@ def run_tmux_attach(socket_path, input_socket_path, client_side_input_socket_pat
4999
5102
input_socket_path ,
5000
5103
"--client-side-input-socket-path" ,
5001
5104
client_side_input_socket_path ,
5105
+ "--text-output-socket-path" ,
5106
+ text_output_socket_path ,
5107
+ "--client-side-text-output-socket-path" ,
5108
+ client_side_text_output_socket_path ,
5109
+ "--ndjson-output-socket-path" ,
5110
+ ndjson_output_socket_path ,
5111
+ "--client-side-ndjson-output-socket-path" ,
5112
+ client_side_ndjson_output_socket_path ,
5002
5113
"--agi-name" ,
5003
5114
# TODO Something secure here, scitt URN and lookup for PS1?
5004
5115
f"alice{ str (uuid .uuid4 ()).split ('-' )[4 ]} " ,
@@ -5029,12 +5140,25 @@ async def connect_and_read(socket_path: str, sleep_time: float = 0.1):
5029
5140
class RequestConnectTMUX (BaseModel ):
5030
5141
socket_tmux_path : str = Field (alias = "tmux.sock" )
5031
5142
socket_input_path : str = Field (alias = "input.sock" )
5143
+ socket_text_output_path : str = Field (alias = "text-output.sock" )
5144
+ socket_ndjson_output_path : str = Field (alias = "ndjson-output.sock" )
5032
5145
socket_client_side_input_path : str = Field (alias = "client-side-input.sock" )
5146
+ socket_client_side_text_output_path : str = Field (alias = "client-side-text-output.sock" )
5147
+ socket_client_side_ndjson_output_path : str = Field (alias = "client-side-ndjson-output.sock" )
5033
5148
5034
5149
5035
5150
@app .post ("/connect/tmux" )
5036
5151
async def connect (request_connect_tmux : RequestConnectTMUX , background_tasks : BackgroundTasks ):
5037
- background_tasks .add_task (run_tmux_attach , request_connect_tmux .socket_tmux_path , request_connect_tmux .socket_input_path , request_connect_tmux .socket_client_side_input_path )
5152
+ background_tasks .add_task (
5153
+ run_tmux_attach ,
5154
+ request_connect_tmux .socket_tmux_path ,
5155
+ request_connect_tmux .socket_input_path ,
5156
+ request_connect_tmux .socket_client_side_input_path ,
5157
+ request_connect_tmux .socket_text_output_path ,
5158
+ request_connect_tmux .socket_client_side_text_output_path ,
5159
+ request_connect_tmux .socket_ndjson_output_path ,
5160
+ request_connect_tmux .socket_client_side_ndjson_output_path ,
5161
+ )
5038
5162
return {
5039
5163
"connected" : True ,
5040
5164
}
0 commit comments