supercode-cli 0.4.18

supercode — a lightweight, fully-customizable AI coding agent CLI in Rust. Any model via OpenRouter; natively continues Claude Code and Codex sessions.
//! Process-level proof that the public human `attach` command can consume the
//! negotiated ACP frontend extension through the same line/TUI dispatcher as
//! HTTP, while the separately owned SDK runtime survives detachment.

use std::process::Stdio;
use std::sync::Arc;
use std::time::Duration;

use anyhow::Result;
use async_trait::async_trait;
use supercode::server::{run_http, RpcEngine};
use supercode::{Agent, ChatMessage, ChatRequest, Config, Provider, Usage};
use tokio::io::AsyncWriteExt;

fn bin() -> &'static str {
    env!("CARGO_BIN_EXE_supercode")
}

struct UnusedProvider;

#[async_trait]
impl Provider for UnusedProvider {
    async fn complete(
        &self,
        _request: &ChatRequest,
        _on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
    ) -> supercode::Result<(ChatMessage, Usage)> {
        panic!("detaching an ACP frontend must not call the model")
    }
}

#[tokio::test]
async fn public_attach_uses_acp_bridge_without_stopping_the_runtime() -> Result<()> {
    let cwd = std::env::temp_dir().join(format!("supercode-acp-attach-cli-{}", std::process::id()));
    std::fs::create_dir_all(&cwd)?;
    let agent = Agent::with_provider(
        Config::builder().cwd(cwd.clone()).build(),
        Box::new(UnusedProvider),
    );
    let runtime = RpcEngine::new_named(agent, "public-acp-attach", None);
    let token: Arc<str> = "acp-attach-test-token".into();
    let address = run_http(runtime.clone(), "127.0.0.1:0", token.clone()).await?;
    let url = format!("http://{address}");
    let supercode_home = cwd.join("supercode-home");

    for invocation in 1..=2 {
        let mut child = tokio::process::Command::new(bin())
            .current_dir(&cwd)
            .env("SUPERCODE_HOME", &supercode_home)
            .env("SUPERCODE_SERVER_TOKEN", token.as_ref())
            .arg("--bare")
            .arg("attach")
            .arg("--connect")
            .arg(&url)
            .arg("--acp")
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()?;
        let mut stdin = child.stdin.take().expect("attach stdin must be piped");
        stdin.write_all(b"/detach\n").await?;
        drop(stdin);

        let output = tokio::time::timeout(Duration::from_secs(30), child.wait_with_output())
            .await
            .expect("ACP attach command timed out")?;
        assert!(
            output.status.success(),
            "attach invocation {invocation} failed: stdout={} stderr={}",
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr)
        );
        let stdout = String::from_utf8_lossy(&output.stdout);
        assert!(stdout.contains("attached to public-acp-attach"), "{stdout}");
        assert!(
            stdout.contains("system>"),
            "bounded history was not rendered: {stdout}"
        );
        assert!(
            !runtime.is_shutting_down(),
            "ACP detach invocation {invocation} stopped its SDK-owned runtime"
        );
    }

    let checkpoint_dir = supercode_home.join("frontend-acp");
    let checkpoints = std::fs::read_dir(&checkpoint_dir)?.collect::<Result<Vec<_>, _>>()?;
    assert_eq!(checkpoints.len(), 1);
    let checkpoint: supercode::AcpFrontendCheckpoint =
        serde_json::from_slice(&std::fs::read(checkpoints[0].path())?)?;
    assert_eq!(checkpoint.session_id, "public-acp-attach");

    runtime.shutdown().await;
    std::fs::remove_dir_all(cwd).ok();
    Ok(())
}