@@ -14,6 +14,9 @@ use tracing::{debug, info};
1414
1515use crate :: adapters:: { AdapterError , AgentAdapter , DispatchContext } ;
1616
17+ /// Shared cwd for ACPX sessions created through Calciforge.
18+ pub const ACPX_SESSION_DIR : & str = "/tmp/acpx-sessions" ;
19+
1720/// ACPX adapter — wraps acpx CLI for ACP agent communication
1821pub struct AcpxAdapter {
1922 agent_name : String ,
@@ -36,7 +39,7 @@ impl AcpxAdapter {
3639 _args : args. unwrap_or_default ( ) ,
3740 env : env. unwrap_or_default ( ) ,
3841 timeout_ms : timeout_ms. unwrap_or ( 300_000 ) ,
39- session_dir : PathBuf :: from ( "/tmp/acpx-sessions" ) ,
42+ session_dir : PathBuf :: from ( ACPX_SESSION_DIR ) ,
4043 }
4144 }
4245
@@ -92,7 +95,9 @@ impl AcpxAdapter {
9295 let output = Command :: new ( "acpx" )
9396 . arg ( & self . agent_name )
9497 . arg ( "sessions" )
95- . arg ( "new" )
98+ . arg ( "ensure" )
99+ . arg ( "--name" )
100+ . arg ( session_name)
96101 . current_dir ( & self . session_dir )
97102 . envs ( & self . env )
98103 . output ( )
@@ -142,6 +147,34 @@ impl AcpxAdapter {
142147 . to_string ( )
143148 }
144149
150+ async fn run_session_prompt (
151+ & self ,
152+ message : & str ,
153+ session : Option < & str > ,
154+ ) -> Result < std:: process:: Output , AdapterError > {
155+ let mut cmd = Command :: new ( "acpx" ) ;
156+ cmd. arg ( "--format" ) . arg ( "text" ) . arg ( & self . agent_name ) ;
157+ if let Some ( session) = session {
158+ cmd. arg ( "--session" ) . arg ( session) ;
159+ }
160+ cmd. arg ( "prompt" )
161+ . arg ( message)
162+ . current_dir ( & self . session_dir )
163+ . envs ( & self . env )
164+ . stdout ( Stdio :: piped ( ) )
165+ . stderr ( Stdio :: piped ( ) ) ;
166+
167+ let timeout = std:: time:: Duration :: from_millis ( self . timeout_ms ) ;
168+ let child = cmd
169+ . spawn ( )
170+ . map_err ( |e| AdapterError :: Unavailable ( format ! ( "Failed to spawn acpx: {}" , e) ) ) ?;
171+
172+ tokio:: time:: timeout ( timeout, child. wait_with_output ( ) )
173+ . await
174+ . map_err ( |_| AdapterError :: Unavailable ( "acpx prompt timed out" . to_string ( ) ) ) ?
175+ . map_err ( |e| AdapterError :: Unavailable ( format ! ( "Failed to run acpx: {}" , e) ) )
176+ }
177+
145178 /// Execute one-shot prompt (no session persistence)
146179 async fn exec_prompt ( & self , message : & str ) -> Result < String , AdapterError > {
147180 self . ensure_session_dir ( ) . await ?;
@@ -190,40 +223,38 @@ impl AcpxAdapter {
190223 }
191224
192225 /// Send prompt to persistent session
193- async fn session_prompt ( & self , message : & str ) -> Result < String , AdapterError > {
226+ async fn session_prompt (
227+ & self ,
228+ message : & str ,
229+ session : Option < & str > ,
230+ ) -> Result < String , AdapterError > {
194231 self . ensure_session_dir ( ) . await ?;
195232
196233 // Use cwd session (default session name)
197- info ! ( agent = %self . agent_name, "Running acpx prompt with session" ) ;
198-
199- let mut cmd = Command :: new ( "acpx" ) ;
200- cmd. arg ( "--format" )
201- . arg ( "text" )
202- . arg ( & self . agent_name )
203- . arg ( "prompt" )
204- . arg ( message)
205- . current_dir ( & self . session_dir )
206- . envs ( & self . env )
207- . stdout ( Stdio :: piped ( ) )
208- . stderr ( Stdio :: piped ( ) ) ;
209-
210- let timeout = std:: time:: Duration :: from_millis ( self . timeout_ms ) ;
211- let child = cmd
212- . spawn ( )
213- . map_err ( |e| AdapterError :: Unavailable ( format ! ( "Failed to spawn acpx: {}" , e) ) ) ?;
214-
215- let result = tokio:: time:: timeout ( timeout, child. wait_with_output ( ) ) . await ;
234+ info ! (
235+ agent = %self . agent_name,
236+ session = ?session,
237+ "Running acpx prompt with session"
238+ ) ;
216239
217- match result {
218- Ok ( Ok ( output) ) => {
240+ match self . run_session_prompt ( message , session ) . await {
241+ Ok ( output) => {
219242 if !output. status . success ( ) {
220243 let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
221244 // Session might not exist — try creating it
222245 if stderr. contains ( "session" ) || stderr. contains ( "not found" ) {
223246 info ! ( "Session not found, creating..." ) ;
224- self . ensure_session ( "cwd" ) . await ?;
225- // Retry
226- return self . exec_prompt ( message) . await ;
247+ self . ensure_session ( session. unwrap_or ( "cwd" ) ) . await ?;
248+ let retry = self . run_session_prompt ( message, session) . await ?;
249+ if retry. status . success ( ) {
250+ let stdout = String :: from_utf8_lossy ( & retry. stdout ) ;
251+ return Ok ( Self :: strip_acpx_noise ( & stdout) ) ;
252+ }
253+ let retry_stderr = String :: from_utf8_lossy ( & retry. stderr ) ;
254+ return Err ( AdapterError :: Protocol ( format ! (
255+ "acpx prompt failed: {}" ,
256+ retry_stderr
257+ ) ) ) ;
227258 }
228259 return Err ( AdapterError :: Protocol ( format ! (
229260 "acpx prompt failed: {}" ,
@@ -233,13 +264,7 @@ impl AcpxAdapter {
233264 let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
234265 Ok ( Self :: strip_acpx_noise ( & stdout) )
235266 }
236- Ok ( Err ( e) ) => Err ( AdapterError :: Unavailable ( format ! (
237- "Failed to run acpx: {}" ,
238- e
239- ) ) ) ,
240- Err ( _) => Err ( AdapterError :: Unavailable (
241- "acpx prompt timed out" . to_string ( ) ,
242- ) ) ,
267+ Err ( e) => Err ( e) ,
243268 }
244269 }
245270}
@@ -262,8 +287,9 @@ impl AgentAdapter for AcpxAdapter {
262287 } ;
263288
264289 // Try session mode first, fall back to exec
265- match self . session_prompt ( & message) . await {
290+ match self . session_prompt ( & message, ctx . session ) . await {
266291 Ok ( response) => Ok ( response) ,
292+ Err ( e @ AdapterError :: Protocol ( _) ) if ctx. session . is_some ( ) => Err ( e) ,
267293 Err ( AdapterError :: Protocol ( _) ) => {
268294 // Session error — try one-shot exec
269295 self . exec_prompt ( & message) . await
0 commit comments