Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
114 changes: 112 additions & 2 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ tokio = { version = "1.36.0", features = ["full"] }
runtimelib = { version = "1.3.0", default-features = false }
jupyter-protocol = "1.2.1"
thiserror = "1"
schemars = "1"
notify = "8"
notify-debouncer-mini = "0.7"

[workspace.lints.rust]
dead_code = "warn"
Expand Down
24 changes: 21 additions & 3 deletions contributing/runtimed.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The runtime daemon manages prewarmed Python environments shared across notebook

| Task | Command |
|------|---------|
| Install daemon from source | `cargo xtask install-daemon` |
| Run daemon | `cargo run -p runtimed` |
| Run with debug logs | `RUST_LOG=debug cargo run -p runtimed` |
| Check status | `cargo run -p runt-cli -- pool status` |
Expand Down Expand Up @@ -65,15 +66,32 @@ The notebook app automatically tries to connect to or start the daemon on launch
runtimed::client::ensure_daemon_running(None).await
```

### Install daemon from source

When you change daemon code and want the installed service to pick it up:

```bash
cargo xtask install-daemon
```

This builds runtimed in release mode, stops the running service, replaces the binary, and restarts it. You can verify the version with:

```bash
cat ~/.cache/runt/daemon.json # check "version" field
```

### Manual: Run daemon separately

For debugging daemon-specific code, run it in a separate terminal:
For debugging daemon-specific code, stop the installed service and run from source:

```bash
# Terminal 1: Run daemon
# Stop the installed service first
launchctl bootout gui/$(id -u)/io.runtimed

# Run daemon with debug logs
RUST_LOG=debug cargo run -p runtimed

# Terminal 2: Test with runt CLI
# In another terminal, test with runt CLI
cargo run -p runt-cli -- pool ping
cargo run -p runt-cli -- pool status
cargo run -p runt-cli -- pool take uv
Expand Down
1 change: 1 addition & 0 deletions crates/notebook/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ toml = "0.8"
serde_yaml = "0.9"
pathdiff = "0.2"
pyproject-toml = "0.13"
schemars = { workspace = true }

# Conda environment support via rattler
rattler = "0.39"
Expand Down
2 changes: 1 addition & 1 deletion crates/notebook/src/conda_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@ pub async fn create_prewarmed_conda_environment(

// Build dependency list: ipykernel + ipywidgets + user-configured defaults
let mut deps_list = vec!["ipykernel".to_string(), "ipywidgets".to_string()];
let extra: Vec<String> = crate::settings::load_settings().default_conda_packages;
let extra: Vec<String> = crate::settings::load_settings().conda.default_packages;
if !extra.is_empty() {
info!("[prewarm] Including default packages: {:?}", extra);
deps_list.extend(extra);
Expand Down
39 changes: 29 additions & 10 deletions crates/notebook/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1849,7 +1849,7 @@ async fn start_default_python_kernel_impl(
// Include default uv packages from settings
let default_deps: Vec<String> = {
let s = settings::load_settings();
s.default_uv_packages
s.uv.default_packages
};
if !default_deps.is_empty() {
info!(
Expand Down Expand Up @@ -2050,7 +2050,7 @@ async fn start_default_python_kernel_impl(
let mut conda_deps_list = vec!["ipykernel".to_string()];
{
let s = settings::load_settings();
let extra = s.default_conda_packages;
let extra = s.conda.default_packages;
if !extra.is_empty() {
info!("[prewarm:conda] Including default packages: {:?}", extra);
conda_deps_list.extend(extra);
Expand Down Expand Up @@ -2914,18 +2914,38 @@ async fn set_default_python_env(env_type: String) -> Result<(), String> {
}

/// Get synced settings from the Automerge settings document via runtimed.
/// Returns an error if the daemon is unavailable — the frontend should
/// keep its local state (localStorage) rather than overwriting with defaults.
/// Falls back to reading settings.json when the daemon is unavailable,
/// so the frontend always gets real settings instead of hardcoded defaults.
#[tauri::command]
async fn get_synced_settings() -> Result<runtimed::settings_doc::SyncedSettings, String> {
runtimed::sync_client::try_get_synced_settings()
.await
.map_err(|e| e.to_string())
match runtimed::sync_client::try_get_synced_settings().await {
Ok(settings) => Ok(settings),
Err(e) => {
log::warn!(
"[settings] Daemon unavailable ({}), falling back to settings.json",
e
);
let local = settings::load_settings();
Ok(runtimed::settings_doc::SyncedSettings {
theme: local.theme,
default_runtime: local.default_runtime.to_string(),
default_python_env: local.default_python_env.to_string(),
uv: local.uv,
conda: local.conda,
})
}
}
}

/// Persist a setting to local settings.json (for keys that have local representation).
fn save_setting_locally(key: &str, value: &serde_json::Value) -> Result<(), String> {
match key {
"theme" => {
let value_str = value.as_str().ok_or("expected string")?;
let mut s = settings::load_settings();
s.theme = value_str.to_string();
settings::save_settings(&s).map_err(|e| e.to_string())
}
"default_runtime" => {
let value_str = value.as_str().ok_or("expected string")?;
let runtime: Runtime =
Expand All @@ -2948,16 +2968,15 @@ fn save_setting_locally(key: &str, value: &serde_json::Value) -> Result<(), Stri
"uv.default_packages" => {
let packages = json_value_to_string_vec(value);
let mut s = settings::load_settings();
s.default_uv_packages = packages;
s.uv.default_packages = packages;
settings::save_settings(&s).map_err(|e| e.to_string())
}
"conda.default_packages" => {
let packages = json_value_to_string_vec(value);
let mut s = settings::load_settings();
s.default_conda_packages = packages;
s.conda.default_packages = packages;
settings::save_settings(&s).map_err(|e| e.to_string())
}
// Theme has no local fallback in settings.json (handled by localStorage)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually theme should have a local fallback in settings.json. We can do local storage first but there is a theme key.

_ => Ok(()),
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/notebook/src/runtime.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! Runtime type for notebooks - Python or Deno

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

/// Supported notebook runtime environments
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum Runtime {
#[default]
Expand Down
Loading
Loading