Document process monitoring handoff

Add ACP discovery and configuration support
This commit is contained in:
Ryan Ward
2026-07-30 11:53:33 -05:00
parent ad24374f6d
commit 1a0aac51b6
18 changed files with 1280 additions and 664 deletions
+16
View File
@@ -7,6 +7,22 @@ pub(crate) fn acp_model_id(agent_id: &str) -> String {
format!("acp:{}", agent_id.trim().to_ascii_lowercase())
}
pub(crate) fn acp_selection_model_id(
agent_id: &str,
values: &std::collections::BTreeMap<String, serde_json::Value>,
) -> String {
let suffix = values
.iter()
.map(|(key, value)| format!("{key}={value}"))
.collect::<Vec<_>>()
.join(";");
if suffix.is_empty() {
acp_model_id(agent_id)
} else {
format!("{}:{suffix}", acp_model_id(agent_id))
}
}
pub(crate) fn acp_launch_fingerprint(
agent_id: &str,
custom_command: &str,
+2 -2
View File
@@ -12,8 +12,8 @@ mod runtime_model;
mod transport;
pub(crate) use launch::{
acp_launch_fingerprint, acp_model_id, resolve_acp_launch, validate_acp_dispatch,
validate_acp_launch_identity,
acp_launch_fingerprint, acp_model_id, acp_selection_model_id, resolve_acp_launch,
validate_acp_dispatch, validate_acp_launch_identity,
};
pub(crate) use permissions::resolve_acp_permissions;
pub(crate) use runtime_model::AcpRuntimeModel;
+67
View File
@@ -1,3 +1,6 @@
use crate::settings::{
AISettings, AcpAgentSettings, AcpConfigOptionSettings, AcpConfigValueSettings,
};
use galaxy_acp::{AcpLaunchConfig, AcpManagerConfig, AcpSessionManager};
use galaxyui::{Entity, ModelContext, SingletonEntity};
@@ -33,6 +36,70 @@ impl AcpRuntimeModel {
});
Ok(manager)
}
pub(crate) fn normalize_config_options(
options: Vec<galaxy_acp::SessionConfigOption>,
) -> Vec<AcpConfigOptionSettings> {
options
.into_iter()
.map(|option| {
let kind = match &option.kind {
galaxy_acp::SessionConfigOptionType::Select => "select",
galaxy_acp::SessionConfigOptionType::Boolean => "boolean",
}
.to_owned();
let current_value = serde_json::to_value(&option.current_value).unwrap_or_default();
let values = option
.options
.into_iter()
.map(|value| AcpConfigValueSettings {
value: serde_json::to_value(value.value).unwrap_or_default(),
name: value.name,
description: value.description,
})
.collect();
AcpConfigOptionSettings {
id: option.id,
name: option.name,
description: option.description,
category: option
.category
.map(|category| format!("{category:?}").to_lowercase()),
kind,
current_value,
options: values,
}
})
.collect()
}
pub(crate) fn upsert_agent_settings(
settings: &mut AISettings,
agent_id: &str,
options: Vec<galaxy_acp::SessionConfigOption>,
) -> Result<(), String> {
let config_options = Self::normalize_config_options(options);
let mut agents = settings.acp_agents.value().clone();
if let Some(agent) = agents
.iter_mut()
.find(|agent| agent.id.eq_ignore_ascii_case(agent_id))
{
agent.config_options = config_options;
} else {
agents.push(AcpAgentSettings {
id: agent_id.to_owned(),
name: agent_id.to_owned(),
version: None,
description: None,
icon_url: None,
capabilities: Vec::new(),
config_options,
});
}
settings
.acp_agents
.set_value(agents, &mut settings.context())
}
}
impl Entity for AcpRuntimeModel {
+20 -4
View File
@@ -8,11 +8,11 @@ use futures::stream::FusedStream as _;
use futures::{FutureExt as _, StreamExt as _};
use galaxy_acp::{
AcpEvent, AcpPermissionPolicy, AcpRuntimeError, AcpSessionHandle, AcpSessionManager,
AcpSteeringOutcome, AcpTurnRequest, ContentBlock, McpServer, McpServerStdio, SessionId,
TextContent,
AcpSteeringOutcome, AcpTurnRequest, ContentBlock, McpServer, McpServerStdio,
SessionConfigOptionValue, SessionId, TextContent,
};
use super::launch::acp_model_id;
use super::launch::acp_selection_model_id;
use super::prompt::{prompt_content, GalaxyTerminalTools};
use super::response_translator::AcpResponseTranslator;
use crate::ai::agent::api::{self, RequestParams};
@@ -25,6 +25,7 @@ pub(crate) struct AcpSessionMetadata {
pub(crate) session_id: Option<String>,
pub(crate) can_load: bool,
pub(crate) can_steer: bool,
pub(crate) config_options: Vec<galaxy_acp::SessionConfigOption>,
}
#[derive(Clone, Debug)]
@@ -121,6 +122,15 @@ pub(crate) async fn acp_output_stream(
mcp_servers.push(server);
}
let request = AcpTurnRequest {
config_values: backend
.config_values
.into_iter()
.filter_map(|(key, value)| {
serde_json::from_value::<SessionConfigOptionValue>(value)
.ok()
.map(|value| (key, value))
})
.collect(),
conversation_key: conversation_id,
session_id: backend.session_id.map(SessionId::from),
cwd,
@@ -222,6 +232,7 @@ pub(crate) async fn acp_output_stream(
session_id,
can_load,
can_steer,
..
} = &event
{
if let Ok(mut metadata) = session_metadata.lock() {
@@ -230,6 +241,11 @@ pub(crate) async fn acp_output_stream(
metadata.can_steer = *can_steer;
}
}
if let AcpEvent::ConfigOptions { options } = &event {
if let Ok(mut metadata) = session_metadata.lock() {
metadata.config_options = options.clone();
}
}
match translator.translate(event) {
Ok(response_events) => {
for response_event in response_events {
@@ -272,7 +288,7 @@ fn response_translator(
task_id,
params.tasks.is_empty(),
user_query,
acp_model_id(&backend.agent_id),
acp_selection_model_id(&backend.agent_id, &backend.config_values),
)
}