From 0645546827e34034ab49982acfde278d35eb94bd Mon Sep 17 00:00:00 2001 From: Onelevenvy Date: Mon, 15 Jun 2026 13:50:59 +0800 Subject: [PATCH 1/2] feat: support editing custom models and automatically set first authenticated model as default --- .../src/config/settings/resolver.rs | 36 +++- flock-ui/src-tauri/src/commands/models.rs | 87 +++++++- flock-ui/src-tauri/src/lib.rs | 1 + .../ModelSettings/CustomModelSettings.tsx | 196 +++++++++++------- .../ModelSettings/components/ProviderCard.tsx | 37 ++-- .../ModelSettings/hooks/useModelSettings.ts | 29 ++- .../Settings/ModelSettings/index.tsx | 3 +- 7 files changed, 281 insertions(+), 108 deletions(-) diff --git a/crates/flock-core/src/config/settings/resolver.rs b/crates/flock-core/src/config/settings/resolver.rs index aba63a8d..c537bf69 100644 --- a/crates/flock-core/src/config/settings/resolver.rs +++ b/crates/flock-core/src/config/settings/resolver.rs @@ -141,7 +141,7 @@ impl Config { // Check active_model (set by UI) to override default provider/model let active_model: Option = db.get_config("active_model").await; - let (active_provider, active_model_name) = if let Some(am) = active_model { + let (mut active_provider, active_model_name) = if let Some(am) = active_model { let p = am.get("provider_id").and_then(|v| v.as_str()).map(|s| s.to_string()); let m = am.get("model_name").and_then(|v| v.as_str()).map(|s| s.to_string()); (p, m) @@ -149,6 +149,15 @@ impl Config { (None, None) }; + // If default model is not set, try to find the first authenticated provider + if active_provider.is_none() && (default_cfg.model.is_none() || default_cfg.model.as_ref().map(|s| s.is_empty()).unwrap_or(true)) { + if let Ok(providers) = db.list_providers().await { + if let Some(first_auth_prov) = providers.iter().find(|p| p.is_available) { + active_provider = Some(first_auth_prov.id.clone()); + } + } + } + // Determine provider: CLI > active_model > default let provider_str = cli.provider.as_deref() .or(active_provider.as_deref()) @@ -197,18 +206,27 @@ impl Config { }); // Determine model: CLI > active_model > provider config > default - let model = cli + let mut model_opt = cli .model .clone() .or(active_model_name.clone()) .or(provider_config.model.clone()) - .or(default_cfg.model.clone()) - .unwrap_or_else(|| match provider { - ProviderType::Anthropic => "claude-sonnet-4-20250514".into(), - ProviderType::OpenAI => "gpt-4o".into(), - ProviderType::Bedrock => "anthropic.claude-sonnet-4-20250514-v1:0".into(), - ProviderType::Vertex => "claude-sonnet-4@20250514".into(), - }); + .or(default_cfg.model.clone()); + + if model_opt.is_none() { + if let Ok(models) = db.list_models(&provider_label).await { + if let Some(first_online_model) = models.iter().find(|m| m.is_online) { + model_opt = Some(first_online_model.model_name.clone()); + } + } + } + + let model = model_opt.unwrap_or_else(|| match provider { + ProviderType::Anthropic => "claude-sonnet-4-20250514".into(), + ProviderType::OpenAI => "gpt-4o".into(), + ProviderType::Bedrock => "anthropic.claude-sonnet-4-20250514-v1:0".into(), + ProviderType::Vertex => "claude-sonnet-4@20250514".into(), + }); let max_tokens = cli.max_tokens.unwrap_or(default_cfg.max_tokens); let max_turns = cli.max_turns.or(default_cfg.max_turns); diff --git a/flock-ui/src-tauri/src/commands/models.rs b/flock-ui/src-tauri/src/commands/models.rs index 732b1c8f..a27ad0ad 100644 --- a/flock-ui/src-tauri/src/commands/models.rs +++ b/flock-ui/src-tauri/src/commands/models.rs @@ -288,18 +288,37 @@ pub async fn set_active_model( Ok(()) } -/// 创建自定义模型并加密其专属 API Key +/// 创建或更新自定义模型并加密其专属 API Key #[tauri::command] pub async fn upsert_custom_model( db: State<'_, SharedDbManager>, provider_id: String, model_name: String, base_url: String, - api_key: String, + mut api_key: String, capabilities: Option>, + original_model_name: Option, ) -> Result<(), String> { let db_inner = db.inner().clone(); + // If api_key is masked, retrieve the old one + if api_key == "••••••••" { + let old_name = original_model_name.as_ref().unwrap_or(&model_name); + if let Ok(Some(old_model)) = db_inner.get_model(&provider_id, old_name).await { + if let Some(meta) = old_model.meta { + let enc_key = meta.get("api_key_encrypted").and_then(|v| v.as_str()); + let nonce = meta.get("api_key_nonce").and_then(|v| v.as_str()); + if let (Some(ct), Some(n)) = (enc_key, nonce) { + let salt = db_inner.get_or_create_salt().await + .map_err(|e| format!("无法获取加密盐: {}", e))?; + if let Ok(decrypted) = flock_core::crypto::decrypt_value(ct, n, &salt) { + api_key = decrypted; + } + } + } + } + } + // Encrypt the custom API key let salt = db_inner.get_or_create_salt().await .map_err(|e| format!("无法获取加密盐: {}", e))?; @@ -313,6 +332,14 @@ pub async fn upsert_custom_model( "api_key_nonce": nonce, }); + // If the model name changed, delete the old model first + if let Some(ref old_name) = original_model_name { + if old_name != &model_name { + let old_id = format!("{}:{}", provider_id, old_name); + let _ = db_inner.delete_model(&old_id).await; + } + } + let model = flock_core::db::Model { id: format!("{}:{}", provider_id, model_name), provider_id: provider_id.clone(), @@ -346,10 +373,29 @@ pub async fn test_custom_model_connection( provider_id: String, model_name: String, base_url: String, - api_key: String, + mut api_key: String, + original_model_name: Option, ) -> Result { let db_inner = db.inner().clone(); + // If api_key is masked, retrieve the old one + if api_key == "••••••••" { + let old_name = original_model_name.as_ref().unwrap_or(&model_name); + if let Ok(Some(old_model)) = db_inner.get_model(&provider_id, old_name).await { + if let Some(meta) = old_model.meta { + let enc_key = meta.get("api_key_encrypted").and_then(|v| v.as_str()); + let nonce = meta.get("api_key_nonce").and_then(|v| v.as_str()); + if let (Some(ct), Some(n)) = (enc_key, nonce) { + let salt = db_inner.get_or_create_salt().await + .map_err(|e| format!("无法获取加密盐: {}", e))?; + if let Ok(decrypted) = flock_core::crypto::decrypt_value(ct, n, &salt) { + api_key = decrypted; + } + } + } + } + } + // 1. 获取 provider 以得知 provider_type let provider = db_inner.get_provider(&provider_id).await .map_err(|e| format!("数据库错误: {}", e))? @@ -381,3 +427,38 @@ pub async fn test_custom_model_connection( Err(e) => Err(format!("连接失败: {}", e)), } } + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct CustomModelDetails { + pub base_url: String, + pub api_key: String, +} + +/// 获取自定义模型解密配置详情 +#[tauri::command] +pub async fn get_custom_model_details( + db: State<'_, SharedDbManager>, + provider_id: String, + model_name: String, +) -> Result, String> { + let db_inner = db.inner().clone(); + let model = db_inner.get_model(&provider_id, &model_name).await + .map_err(|e| e.to_string())?; + + let Some(model) = model else { return Ok(None); }; + let Some(meta) = model.meta else { return Ok(None); }; + + let base_url = meta.get("base_url").and_then(|v| v.as_str()).unwrap_or_default().to_string(); + let enc_key = meta.get("api_key_encrypted").and_then(|v| v.as_str()); + let nonce = meta.get("api_key_nonce").and_then(|v| v.as_str()); + + let api_key = match (enc_key, nonce) { + (Some(_), Some(_)) => "••••••••".to_string(), + _ => String::new(), + }; + + Ok(Some(CustomModelDetails { + base_url, + api_key, + })) +} diff --git a/flock-ui/src-tauri/src/lib.rs b/flock-ui/src-tauri/src/lib.rs index 42ae8098..2dd7b4b1 100644 --- a/flock-ui/src-tauri/src/lib.rs +++ b/flock-ui/src-tauri/src/lib.rs @@ -128,6 +128,7 @@ pub fn run() { commands::set_active_model, commands::upsert_custom_model, commands::test_custom_model_connection, + commands::get_custom_model_details, // 系统配置 commands::get_app_config, commands::set_app_config, diff --git a/flock-ui/src/components/Settings/ModelSettings/CustomModelSettings.tsx b/flock-ui/src/components/Settings/ModelSettings/CustomModelSettings.tsx index 48dbb6df..4e7cb3fe 100644 --- a/flock-ui/src/components/Settings/ModelSettings/CustomModelSettings.tsx +++ b/flock-ui/src/components/Settings/ModelSettings/CustomModelSettings.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Modal, Stack, @@ -8,11 +8,14 @@ import { Group, Text, Switch, + Loader, + Box, } from '@mantine/core'; import { invoke } from '@tauri-apps/api/core'; import { notifications } from '@mantine/notifications'; import { useTranslation } from 'react-i18next'; import { ProviderIcon } from '@/components/Common/Icons'; +import { ModelItem } from './types'; interface ModelProvider { id: string; @@ -23,17 +26,40 @@ interface ModelProvider { interface Props { provider: ModelProvider; + model?: ModelItem; onClose: () => void; onSaved: () => void; } -export default function CustomModelSettings({ provider, onClose, onSaved }: Props) { +export default function CustomModelSettings({ provider, model, onClose, onSaved }: Props) { const { t } = useTranslation(); - const [modelName, setModelName] = useState(''); + const [modelName, setModelName] = useState(model ? model.model_name : ''); const [baseUrl, setBaseUrl] = useState(''); const [apiKey, setApiKey] = useState(''); - const [supportsVision, setSupportsVision] = useState(false); + const [supportsVision, setSupportsVision] = useState(model ? model.capabilities.includes('vision') : false); const [saving, setSaving] = useState(false); + const [loadingDetails, setLoadingDetails] = useState(!!model); + + useEffect(() => { + if (model) { + invoke<{ base_url: string; api_key: string }>('get_custom_model_details', { + providerId: provider.id, + modelName: model.model_name, + }) + .then((details) => { + if (details) { + setBaseUrl(details.base_url); + setApiKey(details.api_key); + } + }) + .catch((err) => { + console.error('Failed to load custom model details:', err); + }) + .finally(() => { + setLoadingDetails(false); + }); + } + }, [model, provider.id]); const handleSave = async () => { if (!modelName.trim()) { @@ -53,6 +79,7 @@ export default function CustomModelSettings({ provider, onClose, onSaved }: Prop modelName: modelName.trim(), baseUrl: baseUrl.trim(), apiKey: apiKey.trim(), + originalModelName: model ? model.model_name : null, }); // If it reaches here, the test succeeded, so we can save it @@ -62,12 +89,13 @@ export default function CustomModelSettings({ provider, onClose, onSaved }: Prop baseUrl: baseUrl.trim(), apiKey: apiKey.trim(), capabilities: supportsVision ? ['vision'] : [], + originalModelName: model ? model.model_name : null, }); // Show success message notifications.show({ title: t('settings.provider.saveSuccess', 'Success'), - message: 'Custom model added successfully.', + message: model ? 'Custom model updated successfully.' : 'Custom model added successfully.', color: 'teal', }); @@ -93,7 +121,7 @@ export default function CustomModelSettings({ provider, onClose, onSaved }: Prop - Add Custom Model + {model ? 'Edit Custom Model' : 'Add Custom Model'} } @@ -110,84 +138,92 @@ export default function CustomModelSettings({ provider, onClose, onSaved }: Prop }} > - setModelName(e.currentTarget.value)} - required - autoComplete="off" - data-1p-ignore="true" - data-lpignore="true" - styles={{ - input: { - background: 'var(--flock-bg-surface)', - color: 'var(--flock-text-primary)', - }, - }} - /> + {loadingDetails ? ( + + + + ) : ( + <> + setModelName(e.currentTarget.value)} + required + autoComplete="off" + data-1p-ignore="true" + data-lpignore="true" + styles={{ + input: { + background: 'var(--flock-bg-surface)', + color: 'var(--flock-text-primary)', + }, + }} + /> - setBaseUrl(e.currentTarget.value)} - required - autoComplete="off" - data-1p-ignore="true" - data-lpignore="true" - styles={{ - input: { - background: 'var(--flock-bg-surface)', - color: 'var(--flock-text-primary)', - }, - }} - /> + setBaseUrl(e.currentTarget.value)} + required + autoComplete="off" + data-1p-ignore="true" + data-lpignore="true" + styles={{ + input: { + background: 'var(--flock-bg-surface)', + color: 'var(--flock-text-primary)', + }, + }} + /> - setApiKey(e.currentTarget.value)} - required - autoComplete="new-password" - data-1p-ignore="true" - data-lpignore="true" - styles={{ - input: { - background: 'var(--flock-bg-surface)', - color: 'var(--flock-text-primary)', - }, - }} - /> + setApiKey(e.currentTarget.value)} + required + autoComplete="new-password" + data-1p-ignore="true" + data-lpignore="true" + styles={{ + input: { + background: 'var(--flock-bg-surface)', + color: 'var(--flock-text-primary)', + }, + }} + /> - setSupportsVision(e.currentTarget.checked)} - color="blue" - styles={{ - label: { - color: 'var(--flock-text-primary)', - fontWeight: 500, - }, - }} - /> + setSupportsVision(e.currentTarget.checked)} + color="blue" + styles={{ + label: { + color: 'var(--flock-text-primary)', + fontWeight: 500, + }, + }} + /> - - - - + + + + + + )} ); diff --git a/flock-ui/src/components/Settings/ModelSettings/components/ProviderCard.tsx b/flock-ui/src/components/Settings/ModelSettings/components/ProviderCard.tsx index 82519e81..7d76c5f0 100644 --- a/flock-ui/src/components/Settings/ModelSettings/components/ProviderCard.tsx +++ b/flock-ui/src/components/Settings/ModelSettings/components/ProviderCard.tsx @@ -19,6 +19,7 @@ import { IconX, IconPlug, IconTrash, + IconEdit, } from '@tabler/icons-react'; import { TFunction } from 'i18next'; import { ModelProvider, ModelItem } from '@/components/Settings/ModelSettings/types'; @@ -35,7 +36,7 @@ interface ProviderCardProps { testResult: { id: string; ok: boolean; msg: string } | null; handleTestConnection: (providerId: string) => Promise; setEditingProvider: (provider: ModelProvider | null) => void; - setAddingCustomModel: (provider: ModelProvider | null) => void; + setAddingCustomModel: (value: { provider: ModelProvider; model?: ModelItem } | null) => void; setExpanded: React.Dispatch>>; handleDeleteModel: (modelId: string, providerId: string) => Promise; handleToggleOnline: (modelId: string, currentOnline: boolean) => Promise; @@ -199,7 +200,7 @@ export function ProviderCard({ )} {(provider.id === 'openai_compatible' || provider.id === 'anthropic_compatible') && ( - )} @@ -287,16 +288,28 @@ export function ProviderCard({ > {(provider.id === 'openai_compatible' || provider.id === 'anthropic_compatible') && ( - - handleDeleteModel(model.id, provider.id)} - > - - - + <> + + setAddingCustomModel({ provider, model })} + > + + + + + handleDeleteModel(model.id, provider.id)} + > + + + + )} >({}); const [loading, setLoading] = useState(true); const [editingProvider, setEditingProvider] = useState(null); - const [addingCustomModel, setAddingCustomModel] = useState(null); + const [addingCustomModel, setAddingCustomModel] = useState<{ + provider: ModelProvider; + model?: ModelItem; + } | null>(null); const [testingProvider, setTestingProvider] = useState(null); const [testResult, setTestResult] = useState<{ id: string; ok: boolean; msg: string } | null>(null); const [connectedProviders, setConnectedProviders] = useState>(new Set()); @@ -33,8 +36,6 @@ export function useModelSettings() { invoke('get_app_config', { key: 'summary_model' }), ]); setProviders(list); - if (def) setDefaultCfg(def); - if (summary) setSummaryCfg(summary); const connected = new Set(); const models: Record = {}; @@ -48,6 +49,28 @@ export function useModelSettings() { setModelsMap(models); setConnectedProviders(connected); + // Check if default model is set. If not, auto-set to first authenticated provider's first model. + let resolvedDefault = def; + if (!def || !def.model) { + const firstAvailableProvider = list.find(p => p.is_available); + if (firstAvailableProvider) { + const providerModels = models[firstAvailableProvider.id] || []; + const firstOnlineModel = providerModels.find(m => m.is_online); + if (firstOnlineModel) { + const newDefault = { + provider: firstAvailableProvider.id, + model: firstOnlineModel.model_name + }; + await invoke('set_app_config', { key: 'default', value: newDefault }); + await invoke('set_active_model', { providerId: firstAvailableProvider.id, modelName: firstOnlineModel.model_name }); + resolvedDefault = newDefault; + } + } + } + + if (resolvedDefault) setDefaultCfg(resolvedDefault); + if (summary) setSummaryCfg(summary); + // Invalidate React Query cache when settings data refreshes queryClient.invalidateQueries({ queryKey: ['model_providers'] }); queryClient.invalidateQueries({ queryKey: ['models'] }); diff --git a/flock-ui/src/components/Settings/ModelSettings/index.tsx b/flock-ui/src/components/Settings/ModelSettings/index.tsx index 316cb5d7..8890bd1d 100644 --- a/flock-ui/src/components/Settings/ModelSettings/index.tsx +++ b/flock-ui/src/components/Settings/ModelSettings/index.tsx @@ -91,7 +91,8 @@ export default function ModelProviderPage() { {addingCustomModel && ( setAddingCustomModel(null)} onSaved={loadData} /> From dbea7c178b0113841135d640c9ebcd806ecaaa65 Mon Sep 17 00:00:00 2001 From: Onelevenvy Date: Mon, 15 Jun 2026 14:03:43 +0800 Subject: [PATCH 2/2] chore: remove verbose execution progress logs from LLM node and sandbox tools --- crates/flock-agent/src/graph/nodes/llm.rs | 13 ----------- crates/flock-tools/src/daytona/lifecycle.rs | 16 ++----------- crates/flock-tools/src/daytona/sync.rs | 8 +------ .../src/tools/sandbox/browser/interactive.rs | 10 -------- .../src/tools/sandbox/browser/mod.rs | 5 ---- .../src/tools/sandbox/code_execution.rs | 1 - .../src/tools/sandbox/computer_use.rs | 10 -------- .../flock-tools/src/tools/sandbox/fs_tools.rs | 23 ++++--------------- .../src/tools/sandbox/sandbox_exec.rs | 4 ---- 9 files changed, 7 insertions(+), 83 deletions(-) diff --git a/crates/flock-agent/src/graph/nodes/llm.rs b/crates/flock-agent/src/graph/nodes/llm.rs index bed26d3f..cf6e349d 100644 --- a/crates/flock-agent/src/graph/nodes/llm.rs +++ b/crates/flock-agent/src/graph/nodes/llm.rs @@ -141,15 +141,6 @@ pub fn make_llm_node( let local_estimate = estimate::estimate_tokens_from_messages(&msgs_for_estimate, Some(&system)); let effective_watermark = if turn_input_tokens > 0 { turn_input_tokens } else { local_estimate }; - if local_estimate > turn_input_tokens && turn_input_tokens > 0 - && local_estimate.saturating_sub(turn_input_tokens) > 10_000 - { - ctx.output.emit_info(&format!( - "Token watermark override: provider={}, local_estimate={}, using={}", - turn_input_tokens, local_estimate, effective_watermark - )); - } - let mut assistant_content: Vec = Vec::new(); if !thinking_text.is_empty() { assistant_content.push(ContentBlock::Thinking { thinking: thinking_text }); @@ -176,10 +167,6 @@ pub fn make_llm_node( state.total_output_tokens }; - ctx.output.emit_info(&format!( - "[node] <<< exiting llm (tool_calls={})", - tool_calls.len() - )); Ok(json!({ "messages": [assistant_json], diff --git a/crates/flock-tools/src/daytona/lifecycle.rs b/crates/flock-tools/src/daytona/lifecycle.rs index 94a08c19..69bb2689 100644 --- a/crates/flock-tools/src/daytona/lifecycle.rs +++ b/crates/flock-tools/src/daytona/lifecycle.rs @@ -29,7 +29,6 @@ pub async fn destroy_active_sandbox(db: &DbManager) -> anyhow::Result<()> { } } - crate::emit_info(&flock_core::tr(&format!("正在销毁 Daytona 沙盒 {}...", sandbox_id), &format!("Destroying Daytona sandbox {}...", sandbox_id))); let del_url = format!("{}/api/sandbox/{}", base, sandbox_id); match client.delete(&del_url) .header("Authorization", format!("Bearer {}", api_key)) @@ -37,9 +36,7 @@ pub async fn destroy_active_sandbox(db: &DbManager) -> anyhow::Result<()> { .await { Ok(resp) => { - if resp.status().is_success() { - crate::emit_info(&flock_core::tr(&format!("Daytona 沙盒 {} 已销毁。", sandbox_id), &format!("Daytona sandbox {} has been destroyed.", sandbox_id))); - } else { + if !resp.status().is_success() { let status = resp.status(); let body = resp.text().await.unwrap_or_default(); crate::emit_info(&flock_core::tr(&format!("销毁沙盒返回非成功状态 (HTTP {}): {}", status, body), &format!("Destroying sandbox returned non-success status (HTTP {}): {}", status, body))); @@ -108,10 +105,6 @@ pub async fn get_or_create_active_sandbox(db: &DbManager) -> anyhow::Result anyhow::Result { - if code == 0 { - crate::emit_info(&flock_core::tr( - &format!("/workspace 目录已就绪: {}", out), - &format!("/workspace directory is ready: {}", out) - )); - } else { + if code != 0 { crate::emit_info(&flock_core::tr( &format!("创建 /workspace 目录失败 (退出码 {}): {}", code, out), &format!("Failed to create /workspace directory (exit code {}): {}", code, out) diff --git a/crates/flock-tools/src/daytona/sync.rs b/crates/flock-tools/src/daytona/sync.rs index 9dd6a8da..ecd8a83a 100644 --- a/crates/flock-tools/src/daytona/sync.rs +++ b/crates/flock-tools/src/daytona/sync.rs @@ -10,8 +10,6 @@ fn should_ignore(path: &Path) -> bool { } pub async fn sync_up(db: &DbManager, sandbox_id: &str, local_workspace: &Path) -> anyhow::Result<()> { - crate::emit_info("正在将本地工作区同步到沙盒 (Sync Up)..."); - // Create a temporary tarball let tar_path = std::env::temp_dir().join(format!("flock_sync_up_{}.tar.gz", sandbox_id)); let tar_file = fs::File::create(&tar_path).context("Failed to create tar file")?; @@ -32,7 +30,7 @@ pub async fn sync_up(db: &DbManager, sandbox_id: &str, local_workspace: &Path) - if path.is_file() { if let Ok(rel_path) = path.strip_prefix(local_workspace) { if let Err(e) = tar.append_path_with_name(path, rel_path) { - crate::emit_info(&format!("Failed to tar {}: {}", rel_path.display(), e)); + log::warn!("Failed to tar {}: {}", rel_path.display(), e); } } } @@ -50,13 +48,10 @@ pub async fn sync_up(db: &DbManager, sandbox_id: &str, local_workspace: &Path) - execute_command_in_sandbox(db, sandbox_id, cmd).await?; let _ = fs::remove_file(tar_path); - crate::emit_info("同步到沙盒完成。"); Ok(()) } pub async fn sync_down(db: &DbManager, sandbox_id: &str, local_workspace: &Path) -> anyhow::Result<()> { - crate::emit_info("正在将沙盒文件同步回本地 (Sync Down)..."); - // Create a tarball in sandbox let cmd = "cd /workspace && tar -czf .flock_sync_down.tar.gz --exclude='.flock_sync_down.tar.gz' --exclude='.git' --exclude='node_modules' --exclude='target' ."; execute_command_in_sandbox(db, sandbox_id, cmd).await?; @@ -136,6 +131,5 @@ pub async fn sync_down(db: &DbManager, sandbox_id: &str, local_workspace: &Path) let cmd = "rm -f /workspace/.flock_sync_down.tar.gz"; let _ = execute_command_in_sandbox(db, sandbox_id, cmd).await; - crate::emit_info("同步回本地完成。"); Ok(()) } diff --git a/crates/flock-tools/src/tools/sandbox/browser/interactive.rs b/crates/flock-tools/src/tools/sandbox/browser/interactive.rs index a768ce23..10ba3753 100644 --- a/crates/flock-tools/src/tools/sandbox/browser/interactive.rs +++ b/crates/flock-tools/src/tools/sandbox/browser/interactive.rs @@ -70,12 +70,6 @@ pub async fn handle_interactive( ); let _ = execute_command_in_sandbox(db, sandbox_id, &check_and_start_cmd).await; - // Run security check script and auto-navigate - crate::emit_info(&flock_core::tr( - &format!("正在远程浏览器中打开网页并分析安全要素: {}...", url), - &format!("Opening web page in remote browser and analyzing security elements: {}...", url), - )); - let (need_takeover, has_password, has_captcha, screenshot_saved) = run_security_check(db, sandbox_id, session_id, name_id, url).await?; @@ -97,10 +91,6 @@ pub async fn handle_interactive( }; if !need_takeover { - crate::emit_info(&flock_core::tr( - "网页分析完毕:未检测到输入密码、验证码等敏感校验元素。自动跳过人机接管。", - "Page analysis complete: No sensitive verification elements (password inputs, captchas) detected. Skipping human takeover automatically.", - )); return Ok(format!( "人机协同远程桌面已拉起!网页分析完成:未检测到输入密码 (has_password: {})、验证码 (has_captcha: {}) 等敏感验证元素。**为了提高大模型执行效率,已自动跳过人工接管,Agent 继续流式自动运转。**{}\n\n[Remote VNC Link]({})", has_password, has_captcha, image_md, proxy_url diff --git a/crates/flock-tools/src/tools/sandbox/browser/mod.rs b/crates/flock-tools/src/tools/sandbox/browser/mod.rs index e3bb1387..7c7f0fb0 100644 --- a/crates/flock-tools/src/tools/sandbox/browser/mod.rs +++ b/crates/flock-tools/src/tools/sandbox/browser/mod.rs @@ -174,11 +174,6 @@ async fn execute_regular_action( display = DISPLAY_ID ); - let display_url = url.as_deref().unwrap_or("当前页面"); - crate::emit_info(&flock_core::tr( - &format!("正在沙盒中执行网页操作并渲染: {}...", display_url), - &format!("Executing page actions and rendering in sandbox: {}...", display_url), - )); let (stdout_stderr, exit_code) = execute_command_in_sandbox(db, sandbox_id, &run_cmd) .await .map_err(|e| format!("浏览器工具执行出错: {}", e))?; diff --git a/crates/flock-tools/src/tools/sandbox/code_execution.rs b/crates/flock-tools/src/tools/sandbox/code_execution.rs index c3bcc125..98bf9bc5 100644 --- a/crates/flock-tools/src/tools/sandbox/code_execution.rs +++ b/crates/flock-tools/src/tools/sandbox/code_execution.rs @@ -37,7 +37,6 @@ pub async fn code_execution(code: String) -> Result { b64_code ); - crate::emit_info(&flock_core::tr("正在沙盒中执行代码...", "Executing code in sandbox...")); let (stdout_stderr, exit_code) = execute_command_in_sandbox(&db, &sandbox_id, &setup_and_run_cmd).await .map_err(|e| format!("代码执行失败: {}", e))?; diff --git a/crates/flock-tools/src/tools/sandbox/computer_use.rs b/crates/flock-tools/src/tools/sandbox/computer_use.rs index d444a1ad..762a53cb 100644 --- a/crates/flock-tools/src/tools/sandbox/computer_use.rs +++ b/crates/flock-tools/src/tools/sandbox/computer_use.rs @@ -57,10 +57,6 @@ async fn capture_desktop_screenshot( ); let abs_path_str = ss_path.to_string_lossy().to_string(); - crate::emit_info(&flock_core::tr( - "远程桌面最新状态已成功截取并拉回工作区预览!", - "Latest remote desktop screenshot captured and synced to workspace!", - )); return (format!("\n\n![桌面截图](file:///{})", abs_path_str), true); } } @@ -153,10 +149,6 @@ pub async fn computer_use( // --- exec action: 直接在沙盒内执行 shell 命令,无需启动 VNC 桌面 --- if act == "exec" { let cmd = command.ok_or_else(|| "`exec` action 需要提供 `command` 参数。例如:command=\"mkdir /workspace/my_project\"".to_string())?; - crate::emit_info(&flock_core::tr( - &format!("正在沙盒中执行命令: {}...", cmd), - &format!("Executing command in sandbox: {}...", cmd) - )); let (output, exit_code) = execute_command_in_sandbox(&db, &sandbox_id, &cmd).await .map_err(|e| format!("沙盒命令执行失败: {}", e))?; @@ -307,8 +299,6 @@ pub async fn computer_use( } } - // 4. Capture desktop screenshot after action - crate::emit_info(&flock_core::tr("正在捕获当前远程桌面截图并渲染预览...", "Capturing current remote desktop screenshot for preview...")); let (image_md, _screenshot_saved) = capture_desktop_screenshot(&db, &sandbox_id, &session_id, &name_id).await; let proxy_url = match crate::daytona::get_sandbox_vnc_url(&db, &sandbox_id).await { diff --git a/crates/flock-tools/src/tools/sandbox/fs_tools.rs b/crates/flock-tools/src/tools/sandbox/fs_tools.rs index 8c59e175..41ab00a2 100644 --- a/crates/flock-tools/src/tools/sandbox/fs_tools.rs +++ b/crates/flock-tools/src/tools/sandbox/fs_tools.rs @@ -25,7 +25,6 @@ use langgraph::tool; #[tool("SandboxRead")] pub async fn sandbox_read(path: String) -> Result { let db = crate::get_db_manager().ok_or_else(|| "Database manager not initialized".to_string())?; - crate::emit_info(&flock_core::tr(&format!("正在沙盒中读取文件: {}...", path), &format!("Reading file in sandbox: {}...", path))); DaytonaFs::read_file(&db, &path).await.map_err(|e| format!("读取失败: {}", e)) } @@ -50,8 +49,6 @@ pub async fn sandbox_read(path: String) -> Result { #[tool("SandboxWrite")] pub async fn sandbox_write(path: String, content: String) -> Result { let db = crate::get_db_manager().ok_or_else(|| "Database manager not initialized".to_string())?; - crate::emit_info(&flock_core::tr(&format!("正在沙盒中写入文件: {}...", path), &format!("Writing file in sandbox: {}...", path))); - // Write to cloud sandbox DaytonaFs::write_file(&db, &path, &content).await.map_err(|e| format!("写入失败: {}", e))?; @@ -69,13 +66,8 @@ pub async fn sandbox_write(path: String, content: String) -> Result { - crate::emit_info(&flock_core::tr(&format!("文件已同步到本地: {}", local_path.display()), &format!("File synced to local: {}", local_path.display()))); - } - Err(e) => { - crate::emit_info(&flock_core::tr(&format!("同步到本地失败: {} (文件仍在云端沙盒中)", e), &format!("Sync to local failed: {} (File remains in sandbox)", e))); - } + if let Err(e) = std::fs::write(&local_path, &content) { + crate::emit_info(&flock_core::tr(&format!("同步到本地失败: {} (文件仍在云端沙盒中)", e), &format!("Sync to local failed: {} (File remains in sandbox)", e))); } } @@ -104,8 +96,6 @@ pub async fn sandbox_write(path: String, content: String) -> Result Result { let db = crate::get_db_manager().ok_or_else(|| "Database manager not initialized".to_string())?; - crate::emit_info(&flock_core::tr(&format!("正在沙盒中编辑文件: {}...", path), &format!("Editing file in sandbox: {}...", path))); - let content = DaytonaFs::read_file(&db, &path).await.map_err(|e| format!("读取失败: {}", e))?; if !content.contains(&old_text) { return Err("The old_text was not found in the file.".to_string()); @@ -128,13 +118,8 @@ pub async fn sandbox_edit(path: String, old_text: String, new_text: String) -> R } // Write file to local workspace - match std::fs::write(&local_path, &new_content) { - Ok(_) => { - crate::emit_info(&flock_core::tr(&format!("文件已同步到本地: {}", local_path.display()), &format!("File synced to local: {}", local_path.display()))); - } - Err(e) => { - crate::emit_info(&flock_core::tr(&format!("同步到本地失败: {} (文件仍在云端沙盒中)", e), &format!("Sync to local failed: {} (File remains in sandbox)", e))); - } + if let Err(e) = std::fs::write(&local_path, &new_content) { + crate::emit_info(&flock_core::tr(&format!("同步到本地失败: {} (文件仍在云端沙盒中)", e), &format!("Sync to local failed: {} (File remains in sandbox)", e))); } } diff --git a/crates/flock-tools/src/tools/sandbox/sandbox_exec.rs b/crates/flock-tools/src/tools/sandbox/sandbox_exec.rs index b00c29bb..3892b1b1 100644 --- a/crates/flock-tools/src/tools/sandbox/sandbox_exec.rs +++ b/crates/flock-tools/src/tools/sandbox/sandbox_exec.rs @@ -56,10 +56,6 @@ pub async fn sandbox_exec( final_cmd }; - crate::emit_info(&flock_core::tr( - &format!("正在沙盒中执行命令: {}...", command), - &format!("Executing command in sandbox: {}...", command) - )); let (output, exit_code) = execute_command_in_sandbox(&db, &sandbox_id, &cmd_with_timeout).await .map_err(|e| format!("沙盒命令执行失败: {}", e))?;