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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.loopal/worktrees
.claude/worktrees
node_modules
apps/desktop/node_modules
1 change: 1 addition & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Common
common --enable_bzlmod
common:windows --enable_runfiles

# Build
build --jobs=auto
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,30 @@ jobs:

- name: Rustfmt check
run: bazel build //... --config=rustfmt

desktop-e2e:
name: Desktop E2E
runs-on: macos-latest
steps:
- uses: actions/checkout@v4

- name: Setup Bazel
uses: bazel-contrib/setup-bazel@0.14.0
with:
bazelisk-cache: true
repository-cache: true

- name: Electron UI and real Loopal Host E2E
run: >-
bazel test
//apps/desktop:e2e
//apps/desktop:e2e_host
//apps/desktop:e2e_llm_backend
--test_tag_filters=e2e
--test_output=errors

- name: Optimized sidecar staging smoke
run: bazel test //apps/desktop:staging_smoke -c opt --test_output=errors

- name: Build desktop distribution
run: bazel build //apps/desktop:dist -c opt
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,13 @@ benchmarks/terminal_bench/bin/
# Loopal memory runtime (auto-managed)
.loopal/memory-events/archive/
.loopal/memory-events/*.jsonl.gz

# Node / Electron desktop
node_modules/
apps/desktop/out/
apps/desktop/dist/
apps/desktop/release/
apps/desktop/coverage/
apps/desktop/playwright-report/
apps/desktop/test-results/
apps/desktop/.vite/
187 changes: 9 additions & 178 deletions BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,184 +1,15 @@
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
load("//build_defs/rust:platforms.bzl", "loopal_platforms")
load("//build_defs/rust:root_binary.bzl", "loopal_binary")
load("//build_defs/rust:root_tests.bzl", "loopal_root_tests")
load("//build_defs/web:npm_workspace.bzl", "desktop_npm_workspace")
load("//tools:version_env.bzl", "version_env")

# Cross-compilation platforms
platform(name = "linux-x86_64", constraint_values = ["@platforms//os:linux", "@platforms//cpu:x86_64"])
platform(name = "linux-aarch64", constraint_values = ["@platforms//os:linux", "@platforms//cpu:aarch64"])
platform(name = "macos-x86_64", constraint_values = ["@platforms//os:macos", "@platforms//cpu:x86_64"])
platform(name = "macos-aarch64", constraint_values = ["@platforms//os:macos", "@platforms//cpu:aarch64"])
platform(name = "windows-x86_64", constraint_values = ["@platforms//os:windows", "@platforms//cpu:x86_64"])
desktop_npm_workspace()

version_env(name = "version_env")

# Main binary
rust_binary(
name = "loopal",
srcs = glob(["src/**/*.rs"]),
edition = "2024",
rustc_env_files = [":version_env"],
stamp = 1,
deps = [
"//crates/loopal-acp",
"//crates/loopal-agent-client",
"//crates/loopal-agent-hub",
"//crates/loopal-agent-server",
"//crates/loopal-backend",
"//crates/loopal-config",
"//crates/loopal-context",
"//crates/loopal-decision-api",
"//crates/loopal-git",
"//crates/loopal-hub-vault",
"//crates/loopal-ipc",
"//crates/loopal-meta-hub",
"//crates/loopal-protocol",
"//crates/loopal-provider-api",
"//crates/loopal-runtime",
"//crates/loopal-secret-runtime",
"//crates/loopal-session",
"//crates/loopal-storage",
"//crates/loopal-telemetry",
"//crates/loopal-tool-api",
"//crates/loopal-tui",
"//crates/loopal-vault-age",
"//crates/loopal-vault-api",
"//crates/loopal-view-state",
"//crates/tools/process/background:loopal-tool-background",
"@crates//:anyhow",
"@crates//:chrono",
"@crates//:clap",
"@crates//:dirs",
"@crates//:secrecy",
"@crates//:serde",
"@crates//:serde_json",
"@crates//:tokio",
"@crates//:tracing",
"@crates//:tracing-appender",
"@crates//:tracing-subscriber",
"@crates//:uuid",
],
visibility = ["//visibility:public"],
)

# Root integration test (system IPC test) — needs subprocess spawn, bypass sandbox
rust_test(
name = "system_ipc_test",
srcs = ["tests/system_ipc_test.rs"],
edition = "2024",
data = [":loopal"],
env = {
"LOOPAL_BINARY": "$(rootpath :loopal)",
},
local = True,
deps = [
"//crates/loopal-ipc",
"//crates/loopal-protocol",
"@crates//:serde_json",
"@crates//:tokio",
"@crates//:tempfile",
],
)

# Hub lifecycle e2e — covers --hub-only handshake, discovery record,
# token-channel handoff, and shutdown cleanup.
rust_test(
name = "hub_lifecycle_test",
srcs = ["tests/hub_lifecycle_test.rs"],
edition = "2024",
data = [":loopal"],
env = {
"LOOPAL_BINARY": "$(rootpath :loopal)",
},
local = True,
deps = [
"@crates//:tokio",
"@crates//:tempfile",
"@crates//:anyhow",
"@crates//:serde_json",
],
)
loopal_platforms()

# Join-hub e2e — regression guard for the spawn-shim bug where
# `--join-hub` / `--hub-name` were silently dropped from the hub-only
# child argv. Verifies a real spawned hub TCP-connects to the address
# and sends `meta/register` with the env token + flag-supplied name.
rust_test(
name = "join_hub_e2e_test",
srcs = ["tests/join_hub_e2e_test.rs"],
edition = "2024",
data = [":loopal"],
env = {
"LOOPAL_BINARY": "$(rootpath :loopal)",
},
local = True,
deps = [
"@crates//:tokio",
"@crates//:tempfile",
"@crates//:anyhow",
"@crates//:serde_json",
],
)

# Hub-only deadlock regression — spawns `--hub-only` with a project settings
# file that configures an unresponsive stdio MCP server. Detects the
# reverse-IPC deadlock where hub_bootstrap awaits agent/start while
# session_start's synchronous path tries to call hub/mcp/snapshot back.
# `#[ignore]` until Phase 1 lands (run with `--ignored` to verify on main).
rust_test(
name = "hub_only_mcp_deadlock_test",
srcs = ["tests/hub_only_mcp_deadlock_test.rs"],
edition = "2024",
data = [":loopal"],
env = {
"LOOPAL_BINARY": "$(rootpath :loopal)",
},
local = True,
deps = [
"@crates//:tokio",
"@crates//:tempfile",
"@crates//:serde_json",
],
)

# Bootstrap typestate e2e — spawns `--hub-only` and asserts ALIVE/READY
# emit within the layered handshake budget (proxy 8s < start_agent 20s <
# handshake 30s). Skipping any state in the chain (bind_listener →
# register_handlers → spawn_agent_process → start_root_agent) breaks the
# guarantee and is caught here.
rust_test(
name = "bootstrap_typestate_e2e_test",
srcs = ["tests/bootstrap_typestate_e2e_test.rs"],
edition = "2024",
data = [":loopal"],
env = {
"LOOPAL_BINARY": "$(rootpath :loopal)",
},
local = True,
deps = [
"@crates//:tokio",
"@crates//:tempfile",
],
)
version_env(name = "version_env")

# Unit tests for #[cfg(test)] modules in src/ — currently covers the
# CLI passthrough invariants (ChildPassthroughArgs::to_args completeness,
# round-trip, parent-only-flag isolation, resume_intent, apply_overrides).
rust_test(
name = "loopal-unit-test",
crate = ":loopal",
edition = "2024",
)
loopal_binary()

# Architectural boundary enforcement — fails build if wire-format types
# leak outside the provider / context / display layer, or if
# compile-time-killed APIs (ContextStore::from_messages, TurnStore::turns_mut,
# loopal-message) try to come back.
rust_test(
name = "architecture_boundary_test",
srcs = ["tests/architecture_boundary_test.rs"],
edition = "2024",
local = True,
deps = [
"@crates//:regex",
"@crates//:walkdir",
],
)
loopal_root_tests()
Loading
Loading