Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
b782fa0
feat(webui-v2): port static SPA to Reborn WebChat ingress (#3886)
italic-jinxin May 24, 2026
dc5eda6
fix(webui-v2): wire typed SSE events + projection items + raise strea…
italic-jinxin May 25, 2026
14183f7
Merge branch 'reborn-integration' into feat/webui-v2-static-port-3886
italic-jinxin May 25, 2026
7073ebd
fix(webui-v2): unblock chat flow after reborn-integration merge
italic-jinxin May 25, 2026
c1045a0
fix(webui-v2): hide unsupported pages + auto-load .env in Reborn binary
italic-jinxin May 25, 2026
bef36a2
Merge branch 'reborn-integration' into feat/webui-v2-static-port-3886
italic-jinxin May 26, 2026
0028b5b
fix(webui-v2): address PR review — CSP fail-closed, drop delete affor…
italic-jinxin May 26, 2026
f07faf2
fix(webui-v2-static): annotate build.rs panics for the no-panic check
italic-jinxin May 26, 2026
89d0249
Merge branch 'reborn-integration' into feat/webui-v2-static-port-3886
italic-jinxin May 26, 2026
f2e70d1
fix(loop-support): satisfy clippy unwrap_or_default in subagent_spawn…
italic-jinxin May 26, 2026
0786039
fix(tests): align harness composition type with type-erased thread se…
italic-jinxin May 26, 2026
cdd1267
Merge branch 'reborn-integration' into feat/webui-v2-static-port-3886
italic-jinxin May 26, 2026
9517a38
fix(webui-v2-static): address review findings on the static SPA port
italic-jinxin May 26, 2026
c99568b
fix(webui-v2-static): address second-pass review findings
italic-jinxin May 26, 2026
9dbb037
Merge remote-tracking branch 'origin/reborn-integration' into feat/we…
serrrfirat May 27, 2026
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
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = [".", "crates/ironclaw_common", "crates/ironclaw_host_api", "crates/ironclaw_filesystem", "crates/ironclaw_memory", "crates/ironclaw_events", "crates/ironclaw_event_projections", "crates/ironclaw_event_streams", "crates/ironclaw_reborn_event_store", "crates/ironclaw_extensions", "crates/ironclaw_processes", "crates/ironclaw_dispatcher", "crates/ironclaw_scripts", "crates/ironclaw_process_sandbox", "crates/ironclaw_mcp", "crates/ironclaw_wasm", "crates/ironclaw_wasm_sandbox_core", "crates/ironclaw_wasm_limiter", "crates/ironclaw_capabilities", "crates/ironclaw_secrets", "crates/ironclaw_network", "crates/ironclaw_host_runtime", "crates/ironclaw_runtime_policy", "crates/ironclaw_authorization", "crates/ironclaw_run_state", "crates/ironclaw_approvals", "crates/ironclaw_resources", "crates/ironclaw_auth", "crates/ironclaw_trust", "crates/ironclaw_turns", "crates/ironclaw_agent_loop", "crates/ironclaw_threads", "crates/ironclaw_prompt_envelope", "crates/ironclaw_hooks", "crates/ironclaw_loop_support", "crates/ironclaw_reborn", "crates/ironclaw_reborn_config", "crates/ironclaw_reborn_composition", "crates/ironclaw_first_party_extensions", "crates/ironclaw_reborn_cli", "crates/ironclaw_reborn_traces", "crates/ironclaw_reborn_webui_ingress", "crates/ironclaw_conversations", "crates/ironclaw_product_adapters", "crates/ironclaw_product_workflow", "crates/ironclaw_product_workflow_storage", "crates/ironclaw_product_adapter_registry", "crates/ironclaw_wasm_product_adapters", "crates/ironclaw_telegram_v2_adapter", "crates/ironclaw_outbound", "crates/ironclaw_architecture", "crates/ironclaw_safety", "crates/ironclaw_skills", "crates/ironclaw_oauth", "crates/ironclaw_llm", "crates/ironclaw_embeddings", "crates/ironclaw_engine", "crates/ironclaw_gateway", "crates/ironclaw_tui", "crates/ironclaw_webui_v2"]
members = [".", "crates/ironclaw_common", "crates/ironclaw_host_api", "crates/ironclaw_filesystem", "crates/ironclaw_memory", "crates/ironclaw_events", "crates/ironclaw_event_projections", "crates/ironclaw_event_streams", "crates/ironclaw_reborn_event_store", "crates/ironclaw_extensions", "crates/ironclaw_processes", "crates/ironclaw_dispatcher", "crates/ironclaw_scripts", "crates/ironclaw_process_sandbox", "crates/ironclaw_mcp", "crates/ironclaw_wasm", "crates/ironclaw_wasm_sandbox_core", "crates/ironclaw_wasm_limiter", "crates/ironclaw_capabilities", "crates/ironclaw_secrets", "crates/ironclaw_network", "crates/ironclaw_host_runtime", "crates/ironclaw_runtime_policy", "crates/ironclaw_authorization", "crates/ironclaw_run_state", "crates/ironclaw_approvals", "crates/ironclaw_resources", "crates/ironclaw_auth", "crates/ironclaw_trust", "crates/ironclaw_turns", "crates/ironclaw_agent_loop", "crates/ironclaw_threads", "crates/ironclaw_prompt_envelope", "crates/ironclaw_hooks", "crates/ironclaw_loop_support", "crates/ironclaw_reborn", "crates/ironclaw_reborn_config", "crates/ironclaw_reborn_composition", "crates/ironclaw_first_party_extensions", "crates/ironclaw_reborn_cli", "crates/ironclaw_reborn_traces", "crates/ironclaw_reborn_webui_ingress", "crates/ironclaw_conversations", "crates/ironclaw_product_adapters", "crates/ironclaw_product_workflow", "crates/ironclaw_product_workflow_storage", "crates/ironclaw_product_adapter_registry", "crates/ironclaw_wasm_product_adapters", "crates/ironclaw_telegram_v2_adapter", "crates/ironclaw_outbound", "crates/ironclaw_architecture", "crates/ironclaw_safety", "crates/ironclaw_skills", "crates/ironclaw_oauth", "crates/ironclaw_llm", "crates/ironclaw_embeddings", "crates/ironclaw_engine", "crates/ironclaw_gateway", "crates/ironclaw_tui", "crates/ironclaw_webui_v2", "crates/ironclaw_webui_v2_static"]
exclude = [
"channels-src/discord",
"channels-src/feishu",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3197,8 +3197,15 @@ fn facade_source_avoids_forbidden_runtime_dependencies() {
// callers.
#[tokio::test]
async fn list_threads_unimplemented_backend_returns_service_unavailable() {
// `ScopeMismatchThreadStub` is reused here because it
// intentionally does NOT override the trait's default
// `list_threads_for_scope` impl, so the facade sees the
// unimplemented-enumeration error path. The in-memory backend
// grew a real enumeration impl (local-dev needed working
// sidebar listing), so it can no longer stand in for a backend
// without enumeration support.
let services = RebornServices::new(
Arc::new(InMemorySessionThreadService::default()),
Arc::new(ScopeMismatchThreadStub),
Arc::new(FakeTurnCoordinator::default()),
);

Expand Down
1 change: 1 addition & 0 deletions crates/ironclaw_reborn_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ chrono = { version = "0.4", features = ["serde"] }
hex = "0.4.3"
clap = { version = "4", features = ["derive", "env"] }
clap_complete = "4.5.0"
dotenvy = "0.15"
ironclaw_reborn_composition = { path = "../ironclaw_reborn_composition", version = "0.1.0" }
ironclaw_reborn_config = { path = "../ironclaw_reborn_config", version = "0.1.0" }
ironclaw_reborn_traces = { path = "../ironclaw_reborn_traces", version = "0.1.0" }
Expand Down
13 changes: 13 additions & 0 deletions crates/ironclaw_reborn_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,18 @@ mod context;
mod runtime;

fn main() -> anyhow::Result<()> {
// Mirror the v1 binary's behavior so dev workflows can keep LLM
// keys / base URLs in `.env`. Silent on missing file — production
// hosts use shell-exported env or systemd unit env, not `.env` —
// but any other error (parse failure, permission denied) is
// surfaced to stderr so a malformed file does not boot the host
// with stale env. The boot itself still proceeds because
// operators may have already exported the same keys in their
// shell.
if let Err(error) = dotenvy::dotenv()
&& !error.not_found()
{
eprintln!("warning: failed to load .env: {error}");
}
cli::run()
}
2 changes: 1 addition & 1 deletion crates/ironclaw_reborn_composition/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ rows are inventoried here, not implemented in the current PR.
is kept as defense in depth for paths that don't match any v2
descriptor.
- **Rate limit** — descriptor-driven; the v2 crate declares mutation
60/60, read 120/60, stream 12/60 per `(tenant, user)`. Reading and
60/60, read 120/60, stream 30/60 per `(tenant, user)`. Reading and
enforcing happens in `webui_rate_limit::build_rate_limit_state`.
- **Static security headers** — `nosniff`, `DENY`, CSP applied via
outer `SetResponseHeaderLayer`s; default CSP is
Expand Down
4 changes: 4 additions & 0 deletions crates/ironclaw_reborn_composition/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ root-llm-provider = [
webui-v2-beta = [
"dep:ironclaw_webui_v2",
"ironclaw_webui_v2/webui-v2-beta",
"dep:ironclaw_webui_v2_static",
"ironclaw_webui_v2_static/webui-v2-beta",
"libsql",
"dep:axum",
"dep:tower",
Expand Down Expand Up @@ -93,6 +95,8 @@ ironclaw_trust = { path = "../ironclaw_trust" }
ironclaw_turns = { path = "../ironclaw_turns" }
# WebUI v2 route surface — feature-gated; only used by `webui_serve`.
ironclaw_webui_v2 = { path = "../ironclaw_webui_v2", optional = true }
# WebUI v2 static SPA bundle — feature-gated; mounted under `/v2`.
ironclaw_webui_v2_static = { path = "../ironclaw_webui_v2_static", optional = true }
libsql = { version = "0.6", optional = true, default-features = false, features = ["core", "replication", "remote", "tls"] }
# axum + tower middleware stack used by the `webui_serve` module. Each
# entry is feature-gated so non-beta builds carry no HTTP surface code.
Expand Down
2 changes: 1 addition & 1 deletion crates/ironclaw_reborn_composition/src/webui_rate_limit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//!
//! `ironclaw_webui_v2::webui_v2_routes()` returns an
//! [`IngressRouteDescriptor`] per route, each carrying a
//! [`RateLimitPolicy`] (mutation 60/60, read 120/60, stream 12/60 in
//! [`RateLimitPolicy`] (mutation 60/60, read 120/60, stream 30/60 in
//! the current beta). The v2 crate's CLAUDE.md explicitly designates
//! enforcement of these policies as a host-composition responsibility;
//! this module is that enforcement.
Expand Down
18 changes: 17 additions & 1 deletion crates/ironclaw_reborn_composition/src/webui_serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ pub enum WebuiServeError {
/// [`WebUiAuthenticatedCaller`]
/// - per-route rate limit, resolved from the
/// `ironclaw_webui_v2::webui_v2_routes()` descriptors (mutation
/// 60/60, read 120/60, stream 12/60 per `(tenant, user)` today)
/// 60/60, read 120/60, stream 30/60 per `(tenant, user)` today)
/// - WebChat v2 route set from `ironclaw_webui_v2::webui_v2_router`
///
/// The returned [`Router`] is the seam between this composition crate
Expand Down Expand Up @@ -356,6 +356,22 @@ pub fn webui_v2_app(
ws_origin_state,
enforce_websocket_origin,
))
// SPA static assets served from the embedded
// `ironclaw_webui_v2_static` bundle. Routed AFTER the
// route_layer stack above so the SPA does not require bearer
// auth or burn rate-limit slots — anonymous fetches of
// HTML/JS/CSS/images are expected. Outer security headers,
// CORS, panic boundary, and the global body-limit
// (`.layer(...)` calls below) still apply, defense in depth.
//
// The static crate's `mount_at_prefix` factory owns the
// routing surface (root, trailing-slash, wildcard, and any
// future routes it adds) so the composition layer never
// enumerates individual handlers. `merge` (not `nest`) is
// used because the factory already returns fully prefixed
// routes — `nest` in axum 0.8 has quirky dispatch for the
// exact prefix with/without trailing slash.
.merge(ironclaw_webui_v2_static::mount_at_prefix("/v2"))
// Outer global cap: applies to unmatched paths (e.g. 404 fallback)
// as defense in depth. v2 routes are tighter via the per-route
// body-limit middleware above.
Comment thread
italic-jinxin marked this conversation as resolved.
Expand Down
Loading
Loading