mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
feat(desktop): allow agents to manage relay profiles (#2009)
Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -25,6 +25,10 @@ pub struct AppState {
|
||||
/// restore. `apply_workspace` consumes it after installing the workspace
|
||||
/// relay and identity, so agents never start against the fallback relay.
|
||||
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,
|
||||
/// Shared shutdown signal checked by launch-time agent restoration.
|
||||
pub shutdown_started: AtomicBool,
|
||||
/// Serializes the restore spawn/register transition with shutdown cleanup,
|
||||
@@ -161,6 +165,7 @@ pub fn build_app_state() -> AppState {
|
||||
.unwrap_or_else(|_| reqwest::Client::new()),
|
||||
relay_url_override: Mutex::new(None),
|
||||
managed_agent_restore_pending: AtomicBool::new(false),
|
||||
managed_agent_profile_reconcile_enabled: AtomicBool::new(true),
|
||||
shutdown_started: AtomicBool::new(false),
|
||||
managed_agent_restore_transition: Mutex::new(()),
|
||||
identity_mutation: Mutex::new(()),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use tauri::{AppHandle, Manager};
|
||||
use std::sync::atomic::Ordering;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
|
||||
use crate::{
|
||||
app_state::AppState,
|
||||
@@ -10,6 +11,13 @@ use crate::{
|
||||
util::now_iso,
|
||||
};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_agent_managed_profiles(enabled: bool, state: State<'_, AppState>) {
|
||||
state
|
||||
.managed_agent_profile_reconcile_enabled
|
||||
.store(!enabled, Ordering::Release);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_managed_agent_start_on_app_launch(
|
||||
pubkey: String,
|
||||
|
||||
@@ -991,7 +991,11 @@ pub async fn start_managed_agent(
|
||||
// profile is published on the relay. This self-heals cases where the initial
|
||||
// profile sync at creation time failed silently. For legacy records (pre-PR-921)
|
||||
// with no persisted avatar, this also backfills the avatar from the relay.
|
||||
if result.is_ok() {
|
||||
if result.is_ok()
|
||||
&& state
|
||||
.managed_agent_profile_reconcile_enabled
|
||||
.load(std::sync::atomic::Ordering::Acquire)
|
||||
{
|
||||
let reconcile_pubkey = pubkey.clone();
|
||||
let reconcile_app = app.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
|
||||
@@ -81,6 +81,13 @@ pub(crate) async fn reconcile_agent_profile(
|
||||
&relay_ws_url_with_override(state),
|
||||
);
|
||||
|
||||
if !state
|
||||
.managed_agent_profile_reconcile_enabled
|
||||
.load(std::sync::atomic::Ordering::Acquire)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Query the relay for the agent's existing kind:0 profile.
|
||||
let existing = query_agent_profile(state, &relay_url, agent_pubkey).await?;
|
||||
|
||||
@@ -136,6 +143,13 @@ 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)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
sync_managed_agent_profile(
|
||||
state,
|
||||
&relay_url,
|
||||
|
||||
@@ -103,6 +103,7 @@ pub async fn apply_workspace(
|
||||
relay_url: String,
|
||||
nsec: Option<String>,
|
||||
repos_dir: Option<String>,
|
||||
agent_managed_profiles: Option<bool>,
|
||||
app: AppHandle,
|
||||
) -> Result<(), String> {
|
||||
let restore_app = app.clone();
|
||||
@@ -148,6 +149,13 @@ pub async fn apply_workspace(
|
||||
*keys_guard = keys;
|
||||
}
|
||||
|
||||
// Keep the backend-side reconcile guard aligned with the frontend
|
||||
// experiment before launch-time restore can spawn any agents. Missing
|
||||
// means the stable behavior: desktop remains authoritative.
|
||||
state
|
||||
.managed_agent_profile_reconcile_enabled
|
||||
.store(!agent_managed_profiles.unwrap_or(false), Ordering::Release);
|
||||
|
||||
// ── Filesystem side-effect (non-fatal) ────────────────────────────────
|
||||
// Persist the *effective* repos_dir (None when the candidate failed
|
||||
// validation) for the backend to read at boot, then re-point REPOS to
|
||||
|
||||
@@ -847,6 +847,7 @@ pub fn run() {
|
||||
create_managed_agent,
|
||||
start_managed_agent,
|
||||
stop_managed_agent,
|
||||
set_agent_managed_profiles,
|
||||
set_managed_agent_start_on_app_launch,
|
||||
set_managed_agent_auto_restart,
|
||||
delete_managed_agent,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { relayClient } from "@/shared/api/relayClient";
|
||||
import { applyCommunity, getDefaultRelayUrl } from "@/shared/api/tauri";
|
||||
import { getIdentity } from "@/shared/api/tauriIdentity";
|
||||
import { getOverrides } from "@/shared/features";
|
||||
import { resetMediaCaches } from "@/shared/lib/mediaUrl";
|
||||
import { clearSearchHitEventCache } from "@/app/navigation/searchHitEventCache";
|
||||
import { initDraftStore } from "@/features/messages/lib/useDrafts";
|
||||
@@ -157,6 +158,7 @@ export function useCommunityInit(
|
||||
undefined,
|
||||
activeCommunity.token,
|
||||
activeCommunity.reposDir,
|
||||
getOverrides().agentManagedProfiles === true,
|
||||
);
|
||||
} catch (error) {
|
||||
// A bad `repos_dir` no longer reaches here — `apply_workspace` treats
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { setAgentManagedProfiles } from "@/shared/api/tauri";
|
||||
import { desktopFeatures, useFeatureToggle } from "@/shared/features";
|
||||
import type { FeatureDefinition } from "@/shared/features";
|
||||
import { Switch } from "@/shared/ui/switch";
|
||||
@@ -19,7 +20,17 @@ function FeatureRow({ feature }: { feature: FeatureDefinition }) {
|
||||
aria-labelledby={`${switchId}-label`}
|
||||
checked={enabled}
|
||||
data-testid={switchId}
|
||||
onCheckedChange={toggle}
|
||||
onCheckedChange={(value) => {
|
||||
toggle(value);
|
||||
if (feature.id === "agentManagedProfiles") {
|
||||
void setAgentManagedProfiles(value).catch((error) => {
|
||||
console.error(
|
||||
"Failed to apply agent-managed profiles setting:",
|
||||
error,
|
||||
);
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1061,12 +1061,14 @@ export async function applyCommunity(
|
||||
nsec?: string,
|
||||
token?: string,
|
||||
reposDir?: string,
|
||||
agentManagedProfiles?: boolean,
|
||||
): Promise<void> {
|
||||
await invokeTauri("apply_workspace", {
|
||||
relayUrl,
|
||||
nsec: nsec ?? null,
|
||||
token: token ?? null,
|
||||
reposDir: reposDir ?? null,
|
||||
agentManagedProfiles: agentManagedProfiles ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1079,6 +1081,9 @@ export async function validateReposDir(dir: string): Promise<void> {
|
||||
export const setPreventSleepActive = (active: boolean) =>
|
||||
invokeTauri("set_prevent_sleep_active", { active });
|
||||
|
||||
export const setAgentManagedProfiles = (enabled: boolean) =>
|
||||
invokeTauri("set_agent_managed_profiles", { enabled });
|
||||
|
||||
/** Returns true on macOS, Windows, and Linux AppImage installs.
|
||||
* Returns false on Linux non-AppImage packages (e.g. .deb) where
|
||||
* Tauri's updater cannot swap the binary. */
|
||||
|
||||
@@ -9267,6 +9267,8 @@ export function maybeInstallE2eTauriMocks() {
|
||||
return handleStopManagedAgent(
|
||||
payload as Parameters<typeof handleStopManagedAgent>[0],
|
||||
);
|
||||
case "set_agent_managed_profiles":
|
||||
return undefined;
|
||||
case "set_managed_agent_auto_restart":
|
||||
return handleSetManagedAgentAutoRestart(
|
||||
payload as Parameters<typeof handleSetManagedAgentAutoRestart>[0],
|
||||
|
||||
@@ -32,6 +32,14 @@
|
||||
"platforms": [
|
||||
"desktop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "agentManagedProfiles",
|
||||
"name": "Agent-managed profiles",
|
||||
"description": "Let agents manage their own relay name and avatar instead of restoring the desktop copy",
|
||||
"platforms": [
|
||||
"desktop"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user