Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e36aee6
fix(mobile): route rendered buzz message links
loganj Aug 4, 2026
57b45a7
fix(mobile): cover rendered Buzz link forms
loganj Aug 4, 2026
00da6d6
fix: route channel references across clients
loganj Aug 4, 2026
60c53b7
fix(desktop): preserve queued links across teardown
loganj Aug 4, 2026
e0e191a
chore: retrigger pull request checks
loganj Aug 4, 2026
bd6dc2f
fix(desktop): acknowledge links after navigation
loganj Aug 4, 2026
b5d1a63
Serialize desktop deep-link drains across remounts
loganj Aug 4, 2026
cd62bfb
fix(mobile): honor channel link callbacks
loganj Aug 4, 2026
ed4e028
fix(mobile): preserve emphasis around bare links
loganj Aug 4, 2026
c9f2685
fix(mobile): satisfy file size ratchet
loganj Aug 4, 2026
a94efe0
fix(mobile): meet message content size limit
loganj Aug 4, 2026
ec07e69
fix(mobile): pass message content ratchet
loganj Aug 4, 2026
7fc7e20
fix(desktop): reset deep-link drains on community switch
loganj Aug 4, 2026
f8e7de9
fix(desktop): clear deep links on community switch
loganj Aug 4, 2026
4399a93
fix: address deep-link review feedback
loganj Aug 4, 2026
b909caa
fix(mobile): satisfy deep-link file size gate
loganj Aug 4, 2026
f3db81f
fix(mobile): preserve multi-backtick code links
loganj Aug 10, 2026
da7ccd3
fix(mobile): distinguish inline code from fences
loganj Aug 10, 2026
685e5ee
fix(desktop): cancel superseded community resets
loganj Aug 12, 2026
d514fdb
fix(desktop): supersede in-flight workspace applies
loganj Aug 12, 2026
939b339
fix(mobile): avoid lookbehind in link normalization
loganj Aug 12, 2026
81cd9bf
fix(desktop): preserve workspace ownership across reloads
loganj Aug 12, 2026
15e7c4c
fix(desktop): retain superseded launch restore
loganj Aug 12, 2026
cdaf95b
fix(desktop): cancel superseded workspace restores
loganj Aug 12, 2026
4629fb1
fix(desktop): guard mesh teardown ownership
loganj Aug 12, 2026
5d66432
fix(desktop): preserve newer deep links during workspace reset
loganj Aug 12, 2026
f2f122d
fix(desktop): detach navigation drains after reset
loganj Aug 12, 2026
1f314a6
fix(desktop): scope restored profile reconciliation
loganj Aug 13, 2026
4ad8bda
fix(deep-links): preserve navigation ownership
loganj Aug 13, 2026
b283471
refactor(desktop): split deep-link tests
loganj Aug 13, 2026
bb99608
fix(mobile): exclude quotes from Buzz links
loganj Aug 13, 2026
173aa03
fix(mobile): close Buzz prose delimiter boundary
loganj Aug 13, 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
16 changes: 8 additions & 8 deletions desktop/src-tauri/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,15 @@ pub struct AppState {
/// response (surfaced as an error) so the auth token never leaves the
/// validated relay origin.
pub media_fetch_client: reqwest::Client,
/// Workspace-provided relay URL override. Set by `apply_workspace` on app
/// init and takes priority over env vars and compile-time defaults.
/// Workspace relay override set by `apply_workspace`; wins over defaults.
pub relay_url_override: Mutex<Option<String>>,
/// Set during backend setup when managed agents are eligible for launch
/// restore. `apply_workspace` consumes it after installing the workspace
/// relay and identity, so agents never start against the fallback relay.
/// Highest workspace transition generation plus synchronous commit lock.
pub workspace_transition: crate::commands::WorkspaceTransitionState,
/// Backend setup defers managed-agent launch restore until `apply_workspace`
/// installs the workspace relay and identity.
pub managed_agent_restore_pending: AtomicBool,
/// Whether desktop may repair managed-agent kind:0 profiles from its local
/// records. Disabled by the agent-managed profiles experiment so an agent's
/// own profile updates are not overwritten on start or restore.
/// Whether desktop may repair managed-agent kind:0 profiles. Disabled by
/// agent-managed profiles so agent updates are not overwritten on restore.
pub managed_agent_profile_reconcile_enabled: AtomicBool,
/// Shared shutdown signal checked by launch-time agent restoration.
pub shutdown_started: AtomicBool,
Expand Down Expand Up @@ -207,6 +206,7 @@ pub fn build_app_state() -> AppState {
header across origins (redirect-hop SSRF)",
),
relay_url_override: Mutex::new(None),
workspace_transition: Default::default(),
managed_agent_restore_pending: AtomicBool::new(false),
managed_agent_profile_reconcile_enabled: AtomicBool::new(true),
shutdown_started: AtomicBool::new(false),
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/commands/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1365,7 +1365,7 @@ use deploy::{deploy_payload_json, DeployProjections};
use deploy::{ensure_remote_provider_supported, resolve_deploy_model_provider};

#[path = "agents_profile.rs"]
mod profile;
pub(crate) mod profile;
#[cfg(test)]
use profile::{profile_needs_sync, resolve_legacy_avatar};
pub(crate) use profile::{reconcile_agent_profile, ProfileReconcileData};
Expand Down
43 changes: 31 additions & 12 deletions desktop/src-tauri/src/commands/agents_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,37 @@ pub(crate) async fn reconcile_agent_profile(
app: &AppHandle,
agent_pubkey: &str,
data: &ProfileReconcileData,
) -> Result<(), String> {
let workspace_relay = relay_ws_url_with_override(state);
reconcile_agent_profile_for_workspace(state, app, agent_pubkey, data, &workspace_relay, None)
.await
}

pub(crate) async fn reconcile_agent_profile_for_workspace(
state: &AppState,
app: &AppHandle,
agent_pubkey: &str,
data: &ProfileReconcileData,
workspace_relay: &str,
transition_generation: Option<u64>,
) -> Result<(), String> {
use crate::relay::{query_agent_profile, sync_managed_agent_profile};

// An explicit per-agent relay wins; an empty one falls back to the active
// workspace relay. Resolved once and used for both the read and write-back.
let relay_url = crate::relay::effective_agent_relay_url(
&data.relay_url,
&relay_ws_url_with_override(state),
);
let transition_is_current = || {
state
.workspace_transition
.allows_profile_reconcile(transition_generation)
};

// An explicit per-agent relay wins; an empty one falls back to the captured
// workspace relay. Restore callers also carry the transition generation so
// a superseded restore cannot publish into the winning workspace.
let relay_url = crate::relay::effective_agent_relay_url(&data.relay_url, workspace_relay);

if !state
.managed_agent_profile_reconcile_enabled
.load(std::sync::atomic::Ordering::Acquire)
if !transition_is_current()
|| !state
.managed_agent_profile_reconcile_enabled
.load(std::sync::atomic::Ordering::Acquire)
{
return Ok(());
}
Expand Down Expand Up @@ -143,9 +161,10 @@ pub(crate) async fn reconcile_agent_profile(
let agent_keys = Keys::parse(&data.private_key_nsec)
.map_err(|e| format!("failed to parse agent keys: {e}"))?;

if !state
.managed_agent_profile_reconcile_enabled
.load(std::sync::atomic::Ordering::Acquire)
if !transition_is_current()
|| !state
.managed_agent_profile_reconcile_enabled
.load(std::sync::atomic::Ordering::Acquire)
{
return Ok(());
}
Expand Down
78 changes: 65 additions & 13 deletions desktop/src-tauri/src/commands/mesh_llm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,31 +337,71 @@ async fn resolve_buzz_mesh_startup_at(
}
}

pub(crate) async fn restore_mesh_sharing(app: &AppHandle, state: &AppState) -> CmdResult<()> {
pub(crate) async fn restore_mesh_sharing(
app: &AppHandle,
state: &AppState,
owner: Option<crate::commands::WorkspaceTransitionOwner<'_>>,
) -> CmdResult<()> {
let Some(mut config) = load_mesh_sharing_config(app)? else {
return Ok(());
};
if (!config.enabled && !config.start_on_next_launch) || config.model_id.trim().is_empty() {
return Ok(());
}
config.model_id = mesh_llm::canonical_curated_model_id(&config.model_id).to_string();
if state.mesh_llm_runtime.lock().await.is_some() {
return Ok(());
}
let relay_url = config
.relay_url
.clone()
.unwrap_or_else(|| relay::relay_ws_url_with_override(state));
let runtime_matches = |runtime: &mesh_llm::DesktopMeshRuntime| {
runtime
.start_request()
.relay_url
.as_deref()
.is_some_and(|bound| bound == relay_url)
};
{
let mut runtime = state.mesh_llm_runtime.lock().await;
if runtime.as_ref().is_some_and(runtime_matches) {
return Ok(());
}
let stale = match owner {
Some(owner) => owner.take_if_current_and(&mut runtime, |_| true),
None => runtime.take(),
};
drop(runtime);
if let Some(stale) = stale {
stale.stop().await.map_err(|error| error.to_string())?;
}
}
if owner.is_some_and(|owner| !owner.is_current()) {
return Ok(());
}
let (trusted_owner_ids, join_token) = resolve_buzz_mesh_startup_at(state, &relay_url).await;
if owner.is_some_and(|owner| !owner.is_current()) {
return Ok(());
}
let mut runtime = state.mesh_llm_runtime.lock().await;
if runtime.is_some() {
if runtime.as_ref().is_some_and(runtime_matches) {
return Ok(());
}
let stale = match owner {
Some(owner) => owner.take_if_current_and(&mut runtime, |_| true),
None => runtime.take(),
};
drop(runtime);
if let Some(stale) = stale {
stale.stop().await.map_err(|error| error.to_string())?;
}
if owner.is_some_and(|owner| !owner.is_current()) {
return Ok(());
}
runtime = state.mesh_llm_runtime.lock().await;
if config.start_on_next_launch {
// Consume a role-switch request before doing any potentially long model
// work. If Buzz exits during that work, the next launch stays stopped.
// Keep the role-switch checkpoint armed until the still-current
// workspace owner has installed the runtime. A superseded restore must
// not consume another transition's retry authority.
config = pending_new_start_checkpoint(&config);
save_mesh_sharing_config(app, &config)?;
}
// This is restoration of a previously inference-ready serving node. Keep
// the enabled checkpoint armed while restoring so a transient startup
Expand All @@ -378,6 +418,22 @@ pub(crate) async fn restore_mesh_sharing(app: &AppHandle, state: &AppState) -> C
let started = mesh_llm::DesktopMeshRuntime::start(request)
.await
.map_err(|error| format!("failed to restore Share Compute: {error:#}"))?;
if let Some(owner) = owner {
let Some(_transition_guard) = owner.lock_if_current() else {
drop(runtime);
started.stop().await.map_err(|error| error.to_string())?;
return Ok(());
};
*runtime = Some(started);
config.enabled = true;
config.start_on_next_launch = false;
save_mesh_sharing_config(app, &config)?;
} else {
*runtime = Some(started);
config.enabled = true;
config.start_on_next_launch = false;
save_mesh_sharing_config(app, &config)?;
}
// Install the restored runtime immediately: it is tracked by AppState from
// here on, so it can never be orphaned. Restoring a previously
// inference-ready node still has to load ~tens of GB of weights and may
Expand All @@ -387,10 +443,6 @@ pub(crate) async fn restore_mesh_sharing(app: &AppHandle, state: &AppState) -> C
// tore down a node that was simply still warming up. The checkpoint stays
// armed (`enabled`), so a genuinely broken restore is retried next launch
// rather than silently turning Share Compute off.
*runtime = Some(started);
config.enabled = true;
config.start_on_next_launch = false;
save_mesh_sharing_config(app, &config)?;
drop(runtime);
if let Err(error) = wait_for_mesh_inference(&config.model_id).await {
eprintln!(
Expand Down Expand Up @@ -739,7 +791,7 @@ pub(crate) async fn ensure_relay_mesh_for_record(
if load_mesh_sharing_config(app)?
.is_some_and(|config| config.enabled && !config.model_id.trim().is_empty())
{
restore_mesh_sharing(app, &state).await?;
restore_mesh_sharing(app, &state, None).await?;
return wait_for_mesh_inference(model_id).await;
}

Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod agent_models_env;
mod agent_providers;
mod agent_settings;
mod agent_update_rollback;
mod agents;
pub(crate) mod agents;
mod canvas;
mod channel_templates;
mod channel_window;
Expand Down
Loading
Loading