a3s 0.10.3

a3s — A3S coding agent CLI; `a3s code` launches the interactive TUI
Documentation
#![cfg(all(windows, target_arch = "x86_64"))]

mod support;

use std::io::{Cursor, Write};
use std::path::{Path, PathBuf};
use std::process::Command;

use support::{a3s_bin, FakeReleaseServer, TempWorkspace};

const WEBVIEW_VERSION: &str = "0.1.3";
const WEBVIEW_ARCHIVE: &str = "a3s-webview-v0.1.3-x86_64-pc-windows-msvc.zip";
const CLI_VERSION: &str = env!("CARGO_PKG_VERSION");

#[test]
fn native_windows_profile_supplies_the_user_config_path_without_home() {
    let workspace = TempWorkspace::new("config-userprofile");
    let profile = workspace.path("profile");
    let project = workspace.path("project");
    std::fs::create_dir_all(&profile).unwrap();
    std::fs::create_dir_all(&project).unwrap();

    let output = Command::new(a3s_bin())
        .current_dir(&project)
        .arg("config")
        .arg("paths")
        .env_remove("HOME")
        .env("USERPROFILE", &profile)
        .env("LOCALAPPDATA", workspace.path("local-app-data"))
        .env("A3S_DATA_HOME", workspace.path("data"))
        .env("A3S_STATE_HOME", workspace.path("state"))
        .env("A3S_CACHE_HOME", workspace.path("cache"))
        .env("A3S_RUNTIME_HOME", workspace.path("runtime"))
        .env_remove("A3S_CONFIG_FILE")
        .output()
        .expect("resolve config paths without HOME");

    assert!(
        output.status.success(),
        "stdout={}\nstderr={}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    let stdout = String::from_utf8(output.stdout).unwrap();
    let expected = profile.join(".a3s/config.acl");
    assert!(stdout.contains(&expected.display().to_string()), "{stdout}");
}

#[test]
fn component_install_uses_native_windows_profile_when_home_is_unset() {
    let workspace = TempWorkspace::new("webview-userprofile-first-use");
    let release = start_fake_webview_release();
    let profile = workspace.path("profile");
    let project = workspace.path("project");
    std::fs::create_dir_all(&profile).unwrap();
    std::fs::create_dir_all(&project).unwrap();

    let output = Command::new(a3s_bin())
        .current_dir(&project)
        .arg("--no-progress")
        .arg("install")
        .arg("webview")
        .env_remove("HOME")
        .env("USERPROFILE", &profile)
        .env("LOCALAPPDATA", workspace.path("local-app-data"))
        .env("A3S_DATA_HOME", workspace.path("data"))
        .env("A3S_STATE_HOME", workspace.path("state"))
        .env("A3S_CACHE_HOME", workspace.path("cache"))
        .env("A3S_RUNTIME_HOME", workspace.path("runtime"))
        .env("A3S_UPDATER_GITHUB_API_BASE", release.api_base())
        .env("PATH", "")
        .env("NO_PROXY", "127.0.0.1,localhost")
        .env_remove("A3S_CONFIG_FILE")
        .env_remove("A3S_OFFLINE")
        .env_remove("A3S_NO_AUTO_INSTALL")
        .env_remove("HTTP_PROXY")
        .env_remove("HTTPS_PROXY")
        .env_remove("ALL_PROXY")
        .output()
        .expect("install WebView without HOME");

    assert!(
        output.status.success(),
        "stdout={}\nstderr={}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    let receipt_path = workspace.path("state/components/webview.json");
    let receipt: serde_json::Value =
        serde_json::from_slice(&std::fs::read(&receipt_path).unwrap()).unwrap();
    assert_eq!(receipt["componentId"], "webview");
    assert_eq!(receipt["version"], WEBVIEW_VERSION);
}

#[test]
fn code_tui_first_use_installs_webview_before_the_smoke_session() {
    let workspace = TempWorkspace::new("code-webview-first-use");
    install_ready_use_fixture(&workspace);
    let release = start_fake_webview_release();
    let project = workspace.path("project");
    let home = workspace.path("home");
    let config = workspace.path("config.acl");
    std::fs::create_dir_all(&project).unwrap();
    std::fs::create_dir_all(&home).unwrap();
    write_config(&config);

    let output = Command::new(a3s_bin())
        .arg("-C")
        .arg(&project)
        .arg("--config")
        .arg(&config)
        .arg("code")
        .env("HOME", &home)
        .env("USERPROFILE", &home)
        .env("A3S_DATA_HOME", workspace.path("data"))
        .env("A3S_STATE_HOME", workspace.path("state"))
        .env("A3S_CACHE_HOME", workspace.path("cache"))
        .env("A3S_RUNTIME_HOME", workspace.path("runtime"))
        .env("A3S_CODE_TUI_SMOKE", "1")
        .env("A3S_UPDATER_GITHUB_API_BASE", release.api_base())
        .env("PATH", "")
        .env("NO_PROXY", "127.0.0.1,localhost")
        .env_remove("A3S_OFFLINE")
        .env_remove("A3S_NO_AUTO_INSTALL")
        .env_remove("HTTP_PROXY")
        .env_remove("HTTPS_PROXY")
        .env_remove("ALL_PROXY")
        .output()
        .expect("run Code first-use smoke");

    assert!(
        output.status.success(),
        "stdout={}\nstderr={}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    let receipt_path = workspace.path("state/components/webview.json");
    let receipt: serde_json::Value =
        serde_json::from_slice(&std::fs::read(&receipt_path).unwrap()).unwrap();
    assert_eq!(receipt["componentId"], "webview");
    assert_eq!(receipt["version"], WEBVIEW_VERSION);
    let executable = PathBuf::from(receipt["executablePath"].as_str().unwrap());
    assert!(executable.is_file(), "{}", executable.display());

    let requests = release.requests();
    assert!(
        requests
            .iter()
            .any(|path| path == "/repos/A3S-Lab/WebView/releases/latest"),
        "{requests:?}"
    );
    assert!(
        requests
            .iter()
            .any(|path| path == &format!("/assets/{WEBVIEW_ARCHIVE}")),
        "{requests:?}"
    );
}

fn start_fake_webview_release() -> FakeReleaseServer {
    let mut writer = zip::ZipWriter::new(Cursor::new(Vec::new()));
    writer
        .start_file("a3s-webview.exe", zip::write::SimpleFileOptions::default())
        .unwrap();
    writer.write_all(&fake_webview_pe()).unwrap();
    let archive = writer.finish().unwrap().into_inner();
    FakeReleaseServer::start("WebView", WEBVIEW_VERSION, WEBVIEW_ARCHIVE, archive)
}

fn fake_webview_pe() -> Vec<u8> {
    let mut binary = vec![0_u8; 0x80];
    binary[..2].copy_from_slice(b"MZ");
    binary[0x3c..0x40].copy_from_slice(&0x40_u32.to_le_bytes());
    binary[0x40..0x44].copy_from_slice(b"PE\0\0");
    binary[0x44..0x46].copy_from_slice(&0x8664_u16.to_le_bytes());
    binary.extend_from_slice(
        b"usage: a3s-webview --agent-island --snapshot <absolute-path> --lock-file <absolute-path>",
    );
    binary.extend_from_slice(b"a3s.system_agent_snapshot.v1");
    binary
}

fn install_ready_use_fixture(workspace: &TempWorkspace) {
    let install_root = workspace.path(&format!("data/components/use/{CLI_VERSION}"));
    let executable = install_root.join("a3s-use.exe");
    std::fs::create_dir_all(&install_root).unwrap();
    std::fs::copy(a3s_bin(), &executable).unwrap();
    let receipt_path = workspace.path("state/components/use.json");
    std::fs::create_dir_all(receipt_path.parent().unwrap()).unwrap();
    std::fs::write(
        receipt_path,
        serde_json::to_vec_pretty(&serde_json::json!({
            "schemaVersion": 1,
            "componentId": "use",
            "version": CLI_VERSION,
            "provenance": "github-release",
            "installRoot": install_root,
            "executablePath": executable,
            "ownedPaths": [install_root],
            "source": "fixture",
            "artifactChecksums": {},
            "installedAt": "2026-07-20T00:00:00Z"
        }))
        .unwrap(),
    )
    .unwrap();
}

fn write_config(path: &Path) {
    std::fs::write(
        path,
        r#"default_model = "openai/test"
providers "openai" {
  apiKey = "test"
  baseUrl = "http://127.0.0.1:1"
  models "test" {
    name = "Test"
    toolCall = true
  }
}
memory { llmExtraction = false }
"#,
    )
    .unwrap();
}