Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
# Windows excluded: aws-lc-sys build script has include path issues
# in Bazel sandbox (upstream compatibility). Tracked separately.
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4

Expand Down
8 changes: 3 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@ jobs:
- target: aarch64-unknown-linux-gnu
os: ubuntu-24.04-arm
archive: tar.gz
# Windows disabled: rules_rust#3767 — rustc PATH env var exceeds
# 32,767 char limit with many transitive deps. Pending upstream fix.
# - target: x86_64-pc-windows-msvc
# os: windows-latest
# archive: zip
- target: x86_64-pc-windows-msvc
os: windows-latest
archive: zip

steps:
- uses: actions/checkout@v4
Expand Down
13 changes: 13 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ bazel_dep(name = "platforms", version = "1.0.0")
# Rust rules
bazel_dep(name = "rules_rust", version = "0.69.0")

# Patch rules_rust to fix Windows PATH length overflow (bazelbuild/rules_rust#3767).
# The process_wrapper consolidates hundreds of -Ldependency= paths into a single
# temporary directory via hard links, keeping PATH under the 32,767-char Win32 limit.
# Safe to remove once upstream merges commits eb70659c + 22830c79.
single_version_override(
module_name = "rules_rust",
patch_strip = 1,
patches = [
"//patches:rules_rust_windows_consolidate_deps.patch",
],
version = "0.69.0",
)

# Rust toolchain
rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(
Expand Down
1 change: 1 addition & 0 deletions crates/loopal-agent-hub/tests/suite/e2e_bootstrap_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use serde_json::json;

/// Full bootstrap e2e: Hub spawns real agent process with mock provider,
/// agent starts, emits AwaitingInput, TUI sends message, agent responds.
#[cfg(not(target_os = "windows"))]
#[tokio::test]
async fn full_bootstrap_hub_to_agent_roundtrip() {
// 1. Create mock provider JSON file
Expand Down
28 changes: 15 additions & 13 deletions crates/loopal-backend/tests/suite/resolve_checked_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ fn make_readonly_backend(cwd: &std::path::Path) -> Arc<LocalBackend> {

// ── check_sandbox_path ───────────────────────────────────────────

/// An absolute path guaranteed to be outside any tempdir on all platforms.
fn outside_cwd_path() -> &'static str {
if cfg!(windows) {
r"C:\Windows\System32\evil.exe"
} else {
"/usr/local/bin/evil"
}
}

#[test]
fn check_sandbox_path_returns_none_for_allowed() {
let dir = tempfile::tempdir().unwrap();
Expand All @@ -48,7 +57,7 @@ fn check_sandbox_path_returns_none_for_allowed() {
fn check_sandbox_path_returns_reason_for_outside_cwd() {
let dir = tempfile::tempdir().unwrap();
let backend = make_backend(dir.path());
let reason = backend.check_sandbox_path("/usr/local/bin/evil", true);
let reason = backend.check_sandbox_path(outside_cwd_path(), true);
assert!(reason.is_some());
assert!(reason.unwrap().contains("outside writable"));
}
Expand All @@ -66,22 +75,15 @@ fn check_sandbox_path_returns_reason_for_deny_glob() {
fn check_sandbox_path_returns_none_after_approve() {
let dir = tempfile::tempdir().unwrap();
let backend = make_backend(dir.path());
let path = std::path::PathBuf::from("/usr/local/bin/evil");
let evil = outside_cwd_path();
let path = std::path::PathBuf::from(evil);

// Before approval: needs approval
assert!(
backend
.check_sandbox_path("/usr/local/bin/evil", true)
.is_some()
);
assert!(backend.check_sandbox_path(evil, true).is_some());

// After approval: no longer needs approval
backend.approve_path(&path);
assert!(
backend
.check_sandbox_path("/usr/local/bin/evil", true)
.is_none()
);
assert!(backend.check_sandbox_path(evil, true).is_none());
}

// ── resolve_checked (via Backend methods) ────────────────────────
Expand All @@ -99,7 +101,7 @@ async fn write_to_allowed_path_succeeds() {
async fn write_outside_cwd_returns_requires_approval() {
let dir = tempfile::tempdir().unwrap();
let backend = make_backend(dir.path());
let result = backend.write("/usr/local/bin/evil", "bad").await;
let result = backend.write(outside_cwd_path(), "bad").await;
assert!(matches!(result, Err(ToolIoError::RequiresApproval(_))));
}

Expand Down
3 changes: 3 additions & 0 deletions crates/loopal-meta-hub/tests/e2e/e2e_cluster_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use serde_json::json;
use cluster_harness::{HubHandle, MetaHubHandle};

/// Two-hub cluster: both agents become ready via real IPC.
#[cfg(not(target_os = "windows"))]
#[tokio::test]
async fn cluster_boots_two_hubs_with_agents() {
let meta = MetaHubHandle::boot().await;
Expand Down Expand Up @@ -53,6 +54,7 @@ async fn cluster_boots_two_hubs_with_agents() {
}

/// ListHubs from hub-a sees hub-b (and vice versa) via MetaHub.
#[cfg(not(target_os = "windows"))]
#[tokio::test]
async fn cluster_list_hubs_via_agent() {
let meta = MetaHubHandle::boot().await;
Expand Down Expand Up @@ -85,6 +87,7 @@ async fn cluster_list_hubs_via_agent() {
}

/// Cross-hub message routing: hub-a sends to hub-b's agent via MetaHub.
#[cfg(not(target_os = "windows"))]
#[tokio::test]
async fn cluster_cross_hub_message_delivery() {
let meta = MetaHubHandle::boot().await;
Expand Down
11 changes: 8 additions & 3 deletions crates/loopal-runtime/tests/suite/plan_file_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ use loopal_runtime::plan_file::{PlanFile, build_plan_mode_filter, wrap_plan_remi
fn new_creates_path_under_plans_dir() {
let tmp = tempfile::tempdir().unwrap();
let pf = PlanFile::new(tmp.path());
let path = pf.path().to_string_lossy();
assert!(path.contains(".loopal/plans/"));
assert!(path.ends_with(".md"));
let expected_segment: &std::path::Path = &std::path::PathBuf::from(".loopal").join("plans");
let path = pf.path();
assert!(
path.to_string_lossy()
.contains(expected_segment.to_string_lossy().as_ref()),
"path {path:?} should contain {expected_segment:?}"
);
assert!(path.extension().is_some_and(|e| e == "md"));
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions patches/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports_files(glob(["*.patch"]))
Loading
Loading