Skip to content
Open
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
114 changes: 114 additions & 0 deletions desktop/scripts/check-registered-agent-boundary.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env node
import { readFileSync, readdirSync, statSync } from "node:fs";
import { join, relative } from "node:path";

const root = process.cwd();
const src = join(root, "src");
// Registered references are a display/navigation-only data source. Keep the
// complete consumer list explicit: a new integration file must be reviewed
// and added here instead of evading the check because its filename does not
// happen to contain "registeredAgent".
const registeredAgentDataFiles = new Set([
"src/features/agents/hooks.ts",
"src/features/agents/hooksRegistered.test.mjs",
"src/features/agents/lib/registeredAgentCards.test.mjs",
"src/features/agents/lib/registeredAgentCards.ts",
"src/features/agents/lib/useAgentsDataRefresh.ts",
"src/features/agents/registeredAgentBoundary.test.mjs",
"src/features/agents/ui/AgentsView.tsx",
"src/features/agents/ui/RegisterExistingAgentDialog.tsx",
"src/features/agents/ui/RegisteredAgentIdentityCard.tsx",
"src/features/agents/ui/RemoveRegisteredAgentDialog.tsx",
"src/features/agents/ui/UnifiedAgentsSection.tsx",
"src/features/agents/ui/UnifiedAgentsSectionCardTarget.test.mjs",
"src/shared/api/registeredAgents.test.mjs",
"src/shared/api/tauriRegisteredAgents.ts",
"src/testing/e2eBridge.ts",
]);
const registeredAgentDisplayFiles = new Set([
"src/features/agents/lib/registeredAgentCards.ts",
"src/features/agents/ui/RegisterExistingAgentDialog.tsx",
"src/features/agents/ui/RegisteredAgentIdentityCard.tsx",
"src/features/agents/ui/RemoveRegisteredAgentDialog.tsx",
"src/shared/api/tauriRegisteredAgents.ts",
]);
const registeredAgentDataMarkers = [
"RegisteredAgentReference",
"listRegisteredAgentReferences",
"registerExistingAgentReference",
"registeredAgentsQueryKey",
"registeredReferences",
"unregisterExistingAgentReference",
"useRegisteredAgentsQuery",
];
const forbiddenTrustMarkers = [
"KnownAgentPubkeys",
"configNudgeAuthPubkey",
"mergeKnownAgentPubkeys",
"mentionableAgentPubkeys",
"useKnownAgentPubkeys",
];
const forbiddenInRegisteredAgentFiles = [
"createManagedAgent",
"startManagedAgent",
"stopManagedAgent",
"deleteManagedAgent",
"managedAgentRuntime",
"privateKeyNsec",
"private_key_nsec",
"envVars",
"agentCommand",
"agent_command",
"pid",
];

function walk(dir) {
return readdirSync(dir).flatMap((entry) => {
const path = join(dir, entry);
if (entry === "node_modules" || entry === "dist") return [];
if (statSync(path).isDirectory()) return walk(path);
return /\.(ts|tsx|mjs)$/.test(path) ? [path] : [];
});
}

const offenders = [];
for (const path of walk(src)) {
const rel = relative(root, path);
const text = readFileSync(path, "utf8");
const dataHits = registeredAgentDataMarkers.filter((needle) =>
text.includes(needle),
);
if (dataHits.length > 0 && !registeredAgentDataFiles.has(rel)) {
offenders.push(
`${rel}: registered-reference data (${dataHits.join(", ")})`,
);
}
if (
dataHits.length > 0 &&
registeredAgentDataFiles.has(rel) &&
rel !== "src/features/agents/registeredAgentBoundary.test.mjs"
) {
const trustHits = forbiddenTrustMarkers.filter((needle) =>
text.includes(needle),
);
if (trustHits.length > 0) {
offenders.push(
`${rel}: registered-reference trust leak (${trustHits.join(", ")})`,
);
}
}
if (!registeredAgentDisplayFiles.has(rel)) continue;
const forbiddenHits = forbiddenInRegisteredAgentFiles.filter((needle) =>
text.includes(needle),
);
if (forbiddenHits.length > 0) {
offenders.push(`${rel}: ${forbiddenHits.join(", ")}`);
}
}

if (offenders.length > 0) {
console.error("Registered agent boundary violations:");
for (const offender of offenders) console.error(`- ${offender}`);
process.exit(1);
}
console.log("Registered agent boundary OK");
9 changes: 8 additions & 1 deletion desktop/src-tauri/src/commands/agent_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -699,9 +699,16 @@ use databricks::{discover_databricks_models, DatabricksAuthIntent};

#[path = "agent_models_update.rs"]
mod update;
pub use update::update_managed_agent;
pub(super) use update::{flush_managed_agent_policy, managed_agent_access_policy_changed};

pub(super) async fn update_managed_agent_unchecked(
input: UpdateManagedAgentRequest,
app: AppHandle,
state: State<'_, AppState>,
) -> Result<UpdateManagedAgentResponse, String> {
update::update_managed_agent_impl(input, app, state).await
}

// ── Model normalization ───────────────────────────────────────────────────────

/// Normalize raw `buzz-acp models --json` output into a typed DTO for the frontend.
Expand Down
3 changes: 1 addition & 2 deletions desktop/src-tauri/src/commands/agent_models_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ pub(crate) async fn flush_managed_agent_policy(
/// Most runtime config changes take effect on the next agent spawn. Access
/// policy changes stop active local pairs before saving and restart those exact
/// pairs after the relay policy is flushed.
#[tauri::command]
pub async fn update_managed_agent(
pub(super) async fn update_managed_agent_impl(
input: UpdateManagedAgentRequest,
app: AppHandle,
state: State<'_, AppState>,
Expand Down
60 changes: 60 additions & 0 deletions desktop/src-tauri/src/commands/agent_registered_targets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use tauri::{AppHandle, State};

use crate::{
app_state::AppState,
managed_agents::{
reject_registered_reference_target, ManagedAgentSummary, UpdateManagedAgentRequest,
UpdateManagedAgentResponse,
},
};

/// Validate ownership before dispatching commands whose implementation lives in
/// oversized legacy modules. Registered references and unknown pubkeys fail
/// before any lifecycle, config, or delete side effect.
#[tauri::command]
pub async fn update_managed_agent(
input: UpdateManagedAgentRequest,
app: AppHandle,
state: State<'_, AppState>,
) -> Result<UpdateManagedAgentResponse, String> {
reject_registered_reference_target(&app, &input.pubkey)?;
super::agent_models::update_managed_agent_unchecked(input, app, state).await
}

#[tauri::command]
pub async fn start_managed_agent(
pubkey: String,
expected_relay_url: Option<String>,
expected_signer_pubkey: Option<String>,
app: AppHandle,
state: State<'_, AppState>,
) -> Result<ManagedAgentSummary, String> {
reject_registered_reference_target(&app, &pubkey)?;
super::agents::start_managed_agent_unchecked(
pubkey,
expected_relay_url,
expected_signer_pubkey,
app,
state,
)
.await
}

#[tauri::command]
pub async fn stop_managed_agent(
pubkey: String,
app: AppHandle,
) -> Result<ManagedAgentSummary, String> {
reject_registered_reference_target(&app, &pubkey)?;
super::agents::stop_managed_agent_unchecked(pubkey, app).await
}

#[tauri::command]
pub async fn delete_managed_agent(
pubkey: String,
force_remote_delete: Option<bool>,
app: AppHandle,
) -> Result<(), String> {
reject_registered_reference_target(&app, &pubkey)?;
super::agents::delete_managed_agent_unchecked(pubkey, force_remote_delete, app).await
}
2 changes: 2 additions & 0 deletions desktop/src-tauri/src/commands/agent_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub async fn set_managed_agent_start_on_app_launch(
start_on_app_launch: bool,
app: AppHandle,
) -> Result<ManagedAgentSummary, String> {
crate::managed_agents::reject_registered_reference_target(&app, &pubkey)?;
tokio::task::spawn_blocking(move || {
let state = app.state::<AppState>();
let _store_guard = state
Expand Down Expand Up @@ -67,6 +68,7 @@ pub async fn set_managed_agent_auto_restart(
auto_restart_on_config_change: bool,
app: AppHandle,
) -> Result<ManagedAgentSummary, String> {
crate::managed_agents::reject_registered_reference_target(&app, &pubkey)?;
tokio::task::spawn_blocking(move || {
let state = app.state::<AppState>();
let _store_guard = state
Expand Down
9 changes: 3 additions & 6 deletions desktop/src-tauri/src/commands/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -857,8 +857,7 @@ pub async fn create_managed_agent(
}

/// Data needed for background profile reconciliation after agent start.
#[tauri::command]
pub async fn start_managed_agent(
pub(super) async fn start_managed_agent_unchecked(
pubkey: String,
expected_relay_url: Option<String>,
expected_signer_pubkey: Option<String>,
Expand Down Expand Up @@ -1036,8 +1035,7 @@ pub async fn start_managed_agent(
result
}

#[tauri::command]
pub async fn stop_managed_agent(
pub(super) async fn stop_managed_agent_unchecked(
pubkey: String,
app: AppHandle,
) -> Result<ManagedAgentSummary, String> {
Expand Down Expand Up @@ -1089,8 +1087,7 @@ pub async fn stop_managed_agent(

// Async so the blocking body (disk reads/writes, process termination, keyring
// delete, nest regeneration) runs off the main UI thread via spawn_blocking.
#[tauri::command]
pub async fn delete_managed_agent(
pub(super) async fn delete_managed_agent_unchecked(
pubkey: String,
force_remote_delete: Option<bool>,
app: AppHandle,
Expand Down
2 changes: 2 additions & 0 deletions desktop/src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod agent_model_process;
mod agent_models;
mod agent_models_env;
mod agent_providers;
mod agent_registered_targets;
mod agent_settings;
mod agent_update_rollback;
mod agents;
Expand Down Expand Up @@ -76,6 +77,7 @@ pub use agent_logs::*;
pub use agent_metric_archive::*;
pub use agent_models::*;
pub use agent_providers::*;
pub use agent_registered_targets::*;
pub use agent_settings::*;
pub use agents::*;
pub use canvas::*;
Expand Down
8 changes: 6 additions & 2 deletions desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ use huddle::{
use initial_window::*;
use managed_agents::{
backfill_persona_snapshots, ensure_nest, list_managed_agent_runtimes,
put_managed_agent_runtime_lifecycle, reconcile_managed_agent_runtimes,
list_registered_agent_references, put_managed_agent_runtime_lifecycle,
reconcile_managed_agent_runtimes, register_existing_agent_reference,
restart_managed_agent_runtime, start_managed_agent_runtime, stop_managed_agent_runtime,
try_regenerate_nest,
try_regenerate_nest, unregister_existing_agent_reference,
};
#[cfg(not(feature = "mesh-llm"))]
use mesh_llm_stubs::*;
Expand Down Expand Up @@ -795,6 +796,9 @@ pub fn run() {
mesh_installed_models,
mesh_model_catalog,
update_managed_agent,
list_registered_agent_references,
register_existing_agent_reference,
unregister_existing_agent_reference,
discover_backend_providers,
probe_backend_provider,
list_personas,
Expand Down
5 changes: 5 additions & 0 deletions desktop/src-tauri/src/managed_agents/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ mod personas;
mod process_lifecycle;
pub(crate) mod readiness;
pub(crate) mod reconcile;
mod registered_references;
mod relay_mesh;
mod repos;
mod restore;
Expand Down Expand Up @@ -76,6 +77,10 @@ pub(crate) use readiness::{
agent_readiness, resolve_effective_agent_env, resolve_effective_harness_descriptor,
AgentReadiness, Requirement,
};
pub(crate) use registered_references::{
list_registered_agent_references, register_existing_agent_reference,
reject_registered_reference_target, unregister_existing_agent_reference,
};
pub use relay_mesh::*;
pub use repos::{
effective_repos_dir, ensure_repos_symlink, resolve_repos_at_boot, validate_repos_dir,
Expand Down
Loading