Skip to content
Closed
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
47 changes: 47 additions & 0 deletions desktop/fixtures/relay-url-normalization.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[
{
"input": "wss://relay.example.com",
"canonical": "wss://relay.example.com",
"note": "already canonical"
},
{
"input": " wss://relay.example.com// ",
"canonical": "wss://relay.example.com",
"note": "surrounding whitespace and trailing slashes are cosmetic"
},
{
"input": "WSS://Relay.Example.COM:3000/Path",
"canonical": "wss://relay.example.com:3000/Path",
"note": "scheme and authority are case-insensitive (RFC 3986); the path is not"
},
{
"input": "WS://RELAY-A.example.com:3000/",
"canonical": "ws://relay-a.example.com:3000",
"note": "ws scheme and port survive; case and trailing slash fold away"
},
{
"input": "wss://Relay.Example.com/Path?Query=Value",
"canonical": "wss://relay.example.com/Path?Query=Value",
"note": "path and query are preserved case-sensitively"
},
{
"input": "wss://relay.example.com/path//",
"canonical": "wss://relay.example.com/path",
"note": "trailing slashes after a path are cosmetic too"
},
{
"input": "not-a-url/",
"canonical": "not-a-url",
"note": "schemeless values pass through trim/slash cleanup only"
},
{
"input": "",
"canonical": "",
"note": "empty stays empty"
},
{
"input": " ",
"canonical": "",
"note": "whitespace-only trims to empty"
}
]
6 changes: 5 additions & 1 deletion desktop/scripts/check-file-sizes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,11 @@ const overrides = new Map([
// +5 (1068 -> 1073): merge with main, which independently added the
// managed_agent_profile_reconcile_enabled flag (field + doc + init) under
// its own 1042-line override. Union of two separately approved additions.
["src-tauri/src/app_state.rs", 1073],
["src-tauri/src/app_state.rs", 1090],
// lazy-workspace-activation adds seven command/state wiring lines to lib.rs.
// Keeping the registration and safety-gate setup together is clearer than a
// one-off extraction whose only purpose would be satisfying the line cap.
["src-tauri/src/lib.rs", 1007],
// multi-slot splitting + no-op suppression (#1309): the ReadStateManager
// class grew from ~700 lines to ~1019 with the addition of
// splitContextsIntoBudgetedSlots (pure fn + 5 tests), publishSplitSlots,
Expand Down
25 changes: 21 additions & 4 deletions desktop/src-tauri/src/app_state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::{
collections::HashMap,
collections::{HashMap, HashSet},
io::Write,
sync::{
atomic::{AtomicBool, AtomicU16},
Expand Down Expand Up @@ -30,14 +30,28 @@ pub struct AppState {
/// Workspace-provided relay URL override. Set by `apply_workspace` on app
/// init and takes priority over env vars and compile-time 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.
/// One-shot gate consumed by the first `apply_workspace` after boot to
/// restore Share Compute (mesh-llm). Agent activation itself is governed
/// by `managed_agent_activation_enabled` + `activated_agent_relays` and
/// runs on every `apply_workspace`.
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.
pub managed_agent_profile_reconcile_enabled: AtomicBool,
/// Session-wide gate for lazy agent activation. Set during backend setup
/// only when the boot-time repos-dir and identity-recovery safety checks
/// allow agents to start; when false, no `apply_workspace` may auto-start
/// agents this session.
pub managed_agent_activation_enabled: AtomicBool,
/// Normalized relay URLs whose workspaces were already activated this app
/// session. A workspace's agents lazily start on the first visit and at
/// most once per session — bouncing A→B→A must not resurrect agents the
/// user manually stopped in A. Never stops agents: switching away leaves
/// the other workspace's agents running against their own relay.
pub activated_agent_relays: Mutex<HashSet<String>>,
/// One-shot gate: first `apply_workspace` runs `pin_blank_agent_relays`.
pub agent_relay_stamp_pending: AtomicBool,
/// Shared shutdown signal checked by launch-time agent restoration.
pub shutdown_started: AtomicBool,
/// Serializes the restore spawn/register transition with shutdown cleanup,
Expand Down Expand Up @@ -201,6 +215,9 @@ pub fn build_app_state() -> AppState {
relay_url_override: Mutex::new(None),
managed_agent_restore_pending: AtomicBool::new(false),
managed_agent_profile_reconcile_enabled: AtomicBool::new(true),
managed_agent_activation_enabled: AtomicBool::new(false),
activated_agent_relays: Mutex::new(HashSet::new()),
agent_relay_stamp_pending: AtomicBool::new(true),
shutdown_started: AtomicBool::new(false),
managed_agent_restore_transition: Mutex::new(()),
identity_mutation: Mutex::new(()),
Expand Down
13 changes: 9 additions & 4 deletions desktop/src-tauri/src/commands/agent_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -819,11 +819,16 @@ pub async fn update_managed_agent(
// turn_timeout_seconds is intentionally not applied here —
// BUZZ_ACP_TURN_TIMEOUT is deprecated and ignored by the harness.
// Use idle_timeout_seconds or max_turn_duration_seconds instead.
// Store the relay override exactly as supplied (trimmed). An explicit
// value pins the agent; empty falls back to the workspace relay at
// read-time. A name-only edit (relay_url == None) leaves the pin intact.
// Re-pin the relay on an explicit edit. An explicit value pins the
// agent there (trimmed, as supplied); clearing the field re-pins to
// the active workspace relay — blank is never persisted, so a record
// can't float to whichever workspace is active at a later read. A
// name-only edit (relay_url == None) leaves the pin intact.
if let Some(relay_url) = input.relay_url {
record.relay_url = relay_url.trim().to_string();
record.relay_url = crate::relay::effective_agent_relay_url(
&relay_url,
&relay_ws_url_with_override(&state),
);
}
if let Some(acp_command) = input.acp_command {
record.acp_command = acp_command;
Expand Down
19 changes: 10 additions & 9 deletions desktop/src-tauri/src/commands/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,15 +468,16 @@ pub async fn create_managed_agent(
.to_bech32()
.map_err(|error| format!("failed to encode private key: {error}"))?;

// Store the relay override exactly as supplied (trimmed). An explicit
// value pins the agent; empty stays empty and resolves to the active
// workspace relay at read-time. Uniform for Local and Provider.
let resolved_relay_url = input
.relay_url
.as_deref()
.map(str::trim)
.unwrap_or("")
.to_string();
// Pin the agent to its home relay at create. An explicit value wins
// (trimmed, as supplied); a blank input is stamped with the active
// workspace relay so the record never floats to whichever workspace
// is active at a later read. Uniform for Local and Provider.
// (`effective_agent_relay_url`'s blank fallback remains as
// defense-in-depth for legacy records that predate stamping.)
let resolved_relay_url = crate::relay::effective_agent_relay_url(
input.relay_url.as_deref().unwrap_or(""),
&relay_ws_url_with_override(&state),
);

(keys, private_key_nsec, pubkey, resolved_relay_url, input)
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ pub async fn confirm_agent_snapshot_import(
persona_id: Some(persona_id.clone()),
private_key_nsec: private_key_nsec.clone(),
auth_tag: auth_tag.clone(),
relay_url: String::new(), // resolves to workspace relay at runtime
relay_url: relay_ws_url_with_override(&state), // pinned to the importing workspace
avatar_url: effective_avatar.clone(),
// Machine-local commands: derive from the runtime catalog at
// spawn time — never manufacture from snapshot data.
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/commands/team_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ pub async fn confirm_team_snapshot_import(
persona_id: Some(definition.id.clone()),
private_key_nsec: private_key_nsec.clone(),
auth_tag: auth_tag.clone(),
relay_url: String::new(),
relay_url: relay_ws_url_with_override(&state), // pinned to the importing workspace
avatar_url: effective_avatar_url.clone(),
acp_command: crate::managed_agents::DEFAULT_ACP_COMMAND.to_string(),
agent_command: String::new(),
Expand Down
Loading
Loading