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
2 changes: 2 additions & 0 deletions apps/maple-agent/Cargo.lock

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

18 changes: 18 additions & 0 deletions apps/maple-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ Goose, with an independent dependency graph.
(detected built-ins and custom MCP servers), Usage (the plan meter from
the billing API), and About.
- Dark and light themes; the default follows the system.
On Linux, the protected GUI starts a portal-only child of the same executable
(`--internal-theme-watch`). The child receives only desktop-bus environment,
never opens Maple state, disables core dumps, and exits when its parent pipe
closes. Only the child remains debugger-accessible for portal identification.
Setup has a two-second background deadline; unavailable portals do not block
launch. Explicit Dark/Light settings override system updates.
- Billing status from the Maple billing API.
- Desktop notifications when a task finishes or asks a question while the
window is not focused.
Expand Down Expand Up @@ -593,6 +599,18 @@ cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --check
```

On a Linux desktop with a live Settings portal, run the opt-in helper lifecycle
regression from the same component shell:

```sh
cargo test --locked -p maple-agent-app --test system_theme_helper -- --ignored
```

It launches the real executable in helper mode and verifies theme output,
parent-pipe shutdown, and absence of application-state writes. It does not
change the desktop theme. Parser, environment-scrubbing and palette precedence
tests run in the ordinary app test suite.

Root `.github/workflows/agent-ci.yml` selects this component when Agent or
its shared Rust dependencies change. It builds and tests Linux, macOS, and
Windows; Linux additionally runs the feature-matrix lint/headless checks and
Expand Down
5 changes: 5 additions & 0 deletions apps/maple-agent/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ desktop = [
"dep:wayland-client",
"dep:tray-icon",
"dep:zbus",
"dep:ashpd",
"dep:futures-lite",
"dep:objc",
"dep:raw-window-handle",
]
Expand Down Expand Up @@ -90,6 +92,9 @@ tower-http = { version = "0.6", features = ["cors"], optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
wayland-client = { version = "0.31", optional = true }
zbus = { version = "=5.19.0", optional = true }
# Used only in the credential-free desktop theme helper, not the GUI process.
ashpd = { version = "=0.13.13", default-features = false, features = ["async-io", "settings"], optional = true }
futures-lite = { version = "2", optional = true }

[target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies]
tray-icon = { version = "=0.26.1", default-features = false, features = ["ksni"], optional = true }
Expand Down
2 changes: 2 additions & 0 deletions apps/maple-agent/app/src/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3014,6 +3014,8 @@ pub fn run() {
cx,
);
ui::theme::apply_preference(ui::theme::Preference::parse(&startup_settings.theme), cx);
#[cfg(target_os = "linux")]
crate::platform::portal::start(&backend, cx);
ui::typography::apply(
ui::typography::ChatFontFamily::parse(&startup_settings.chat_font_family),
startup_settings.chat_font_size,
Expand Down
24 changes: 14 additions & 10 deletions apps/maple-agent/app/src/host/hardening.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,7 @@ pub(crate) fn apply() -> Result<(), String> {
return Ok(());
}
#[cfg(unix)]
{
let limit = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
// SAFETY: a valid, initialized rlimit pointer; process-local policy.
if unsafe { libc::setrlimit(libc::RLIMIT_CORE, &limit) } != 0 {
return Err("Could not disable Maple crash dumps.".into());
}
}
disable_core_dumps()?;
#[cfg(target_os = "linux")]
{
// SAFETY: PR_SET_DUMPABLE takes the integer zero, no pointer arguments.
Expand Down Expand Up @@ -64,6 +55,19 @@ pub(crate) fn apply() -> Result<(), String> {
Ok(())
}

#[cfg(unix)]
pub(crate) fn disable_core_dumps() -> Result<(), String> {
let limit = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
// SAFETY: a valid, initialized rlimit pointer; process-local policy.
if unsafe { libc::setrlimit(libc::RLIMIT_CORE, &limit) } != 0 {
return Err("Could not disable Maple crash dumps.".into());
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
18 changes: 16 additions & 2 deletions apps/maple-agent/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ struct Cli {
conflicts_with = "build_info"
)]
internal_store_integrity_check: Option<std::path::PathBuf>,
/// Internal Linux desktop-portal watcher; never opens application state.
#[cfg(all(feature = "desktop", target_os = "linux"))]
#[arg(long, hide = true, conflicts_with_all = ["build_info", "internal_store_integrity_check", "desktop_args"])]
internal_theme_watch: bool,
#[command(subcommand)]
mode: Option<Mode>,
/// Arguments a desktop launcher may append (file paths, `%u` expansions).
Expand Down Expand Up @@ -234,6 +238,16 @@ fn main() {
);
return;
}
#[cfg(all(feature = "desktop", target_os = "linux"))]
if cli.internal_theme_watch {
// The helper is a separate, credential-free process. It must remain
// identifiable to the portal, but never reaches normal app startup.
if platform::portal::run_helper().is_err() {
eprintln!("Desktop theme watcher unavailable.");
std::process::exit(1);
}
return;
}
if let Err(error) = host::hardening::apply() {
eprintln!("{error}");
std::process::exit(1);
Expand All @@ -246,8 +260,8 @@ fn main() {
}
return;
}
// SAFETY: argument parsing starts no threads. No other thread exists
// yet, so mutating the process environment here cannot race a reader.
// SAFETY: argument parsing and hardening start no threads. No other
// thread exists yet, so environment mutation cannot race a reader.
unsafe { maple_agent::prepare_process_environment() };
#[cfg(feature = "desktop")]
let _ = PROCESS_START.set(std::time::Instant::now());
Expand Down
3 changes: 3 additions & 0 deletions apps/maple-agent/app/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

use std::process::Command;

#[cfg(target_os = "linux")]
pub(crate) mod portal;

/// Open a folder in the platform file manager. Runs the launcher without
/// waiting for it; call from a blocking task, not the UI thread.
pub fn reveal_folder(path: &str) -> Result<(), String> {
Expand Down
Loading
Loading