feat: stream real-time progress for agent SDK install/upgrade/uninstall
Replace the spinner-only UX with live log output during agent SDK operations, matching the existing OpenCode plugin install experience. Backend: emit structured events (started/log/completed/failed) via EventEmitter during npm install and binary download. npm commands now run with piped stdio for line-by-line streaming; binary downloads report chunked progress every 1 MB. Frontend: subscribe to `app://agent-install` events through a new `useAgentInstallStream` hook and render a theme-aware log terminal below the preflight checks panel. Also fixes the install log container in both agent settings and the OpenCode plugins modal: auto-scroll no longer shifts the outer page, and colours now follow the active light/dark theme. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,9 @@ pub enum EventEmitter {
|
||||
#[cfg(feature = "tauri-runtime")]
|
||||
Tauri(tauri::AppHandle),
|
||||
WebOnly(Arc<WebEventBroadcaster>),
|
||||
/// Silent no-op emitter — drops all events. Used when streaming progress
|
||||
/// is not needed (e.g. legacy non-streaming call paths).
|
||||
Noop,
|
||||
}
|
||||
|
||||
/// Unified event emission: sends to both Tauri webview and Web clients (if applicable).
|
||||
@@ -70,5 +73,6 @@ pub fn emit_event(
|
||||
EventEmitter::WebOnly(broadcaster) => {
|
||||
broadcaster.send(event, &payload);
|
||||
}
|
||||
EventEmitter::Noop => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,12 +459,19 @@ pub async fn acp_update_agent_config(
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpDownloadAgentBinaryParams {
|
||||
pub agent_type: AgentType,
|
||||
pub task_id: String,
|
||||
}
|
||||
|
||||
pub async fn acp_download_agent_binary(
|
||||
Extension(state): Extension<Arc<AppState>>,
|
||||
Json(params): Json<AgentTypeParams>,
|
||||
Json(params): Json<AcpDownloadAgentBinaryParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let emitter = state.emitter.clone();
|
||||
acp_commands::acp_download_agent_binary_core(params.agent_type, &emitter)
|
||||
acp_commands::acp_download_agent_binary_core(params.agent_type, params.task_id, &emitter)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
@@ -487,6 +494,7 @@ pub async fn acp_detect_agent_local_version(
|
||||
pub struct AcpPrepareNpxAgentParams {
|
||||
pub agent_type: AgentType,
|
||||
pub registry_version: Option<String>,
|
||||
pub task_id: String,
|
||||
}
|
||||
|
||||
pub async fn acp_prepare_npx_agent(
|
||||
@@ -498,6 +506,7 @@ pub async fn acp_prepare_npx_agent(
|
||||
let result = acp_commands::acp_prepare_npx_agent_core(
|
||||
params.agent_type,
|
||||
params.registry_version,
|
||||
params.task_id,
|
||||
db,
|
||||
&emitter,
|
||||
)
|
||||
@@ -506,13 +515,20 @@ pub async fn acp_prepare_npx_agent(
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpUninstallAgentParams {
|
||||
pub agent_type: AgentType,
|
||||
pub task_id: String,
|
||||
}
|
||||
|
||||
pub async fn acp_uninstall_agent(
|
||||
Extension(state): Extension<Arc<AppState>>,
|
||||
Json(params): Json<AgentTypeParams>,
|
||||
Json(params): Json<AcpUninstallAgentParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let db = &state.db;
|
||||
let emitter = state.emitter.clone();
|
||||
acp_commands::acp_uninstall_agent_core(params.agent_type, db, &emitter)
|
||||
acp_commands::acp_uninstall_agent_core(params.agent_type, params.task_id, db, &emitter)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
|
||||
Reference in New Issue
Block a user