feat: add Google OAuth, remote worker system, and file browser
- Google OAuth login with JWT session cookies, per-user project isolation - Remote worker registration via WebSocket, execute_on_worker/list_workers agent tools - File browser UI in workflow view, file upload/download API - Deploy script switched to local build, added tori.euphon.cloud ingress
This commit is contained in:
63
src/agent.rs
63
src/agent.rs
@@ -10,6 +10,7 @@ use crate::llm::{LlmClient, ChatMessage, Tool, ToolFunction};
|
||||
use crate::exec::LocalExecutor;
|
||||
use crate::template::{self, LoadedTemplate};
|
||||
use crate::tools::ExternalToolManager;
|
||||
use crate::worker::WorkerManager;
|
||||
use crate::LlmConfig;
|
||||
|
||||
use crate::state::{AgentState, AgentPhase, Artifact, Step, StepStatus, StepResult, StepResultStatus, check_scratchpad_size};
|
||||
@@ -80,6 +81,7 @@ pub struct AgentManager {
|
||||
template_repo: Option<crate::TemplateRepoConfig>,
|
||||
kb: Option<Arc<crate::kb::KbManager>>,
|
||||
jwt_private_key_path: Option<String>,
|
||||
pub worker_mgr: Arc<WorkerManager>,
|
||||
}
|
||||
|
||||
impl AgentManager {
|
||||
@@ -89,6 +91,7 @@ impl AgentManager {
|
||||
template_repo: Option<crate::TemplateRepoConfig>,
|
||||
kb: Option<Arc<crate::kb::KbManager>>,
|
||||
jwt_private_key_path: Option<String>,
|
||||
worker_mgr: Arc<WorkerManager>,
|
||||
) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
agents: RwLock::new(HashMap::new()),
|
||||
@@ -100,6 +103,7 @@ impl AgentManager {
|
||||
template_repo,
|
||||
kb,
|
||||
jwt_private_key_path,
|
||||
worker_mgr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -755,6 +759,19 @@ fn build_step_tools() -> Vec<Tool> {
|
||||
})),
|
||||
tool_kb_search(),
|
||||
tool_kb_read(),
|
||||
make_tool("list_workers", "列出所有已注册的远程 worker 节点及其硬件/软件信息(CPU、内存、GPU、OS、内核)。", serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
})),
|
||||
make_tool("execute_on_worker", "在指定的远程 worker 上执行脚本。脚本以 bash 执行。可以通过 HTTP 访问项目文件:GET/POST /api/obj/{project_id}/files/{path}", serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"worker": { "type": "string", "description": "Worker 名称(从 list_workers 获取)" },
|
||||
"script": { "type": "string", "description": "要执行的 bash 脚本内容" },
|
||||
"timeout": { "type": "integer", "description": "超时秒数(默认 300)", "default": 300 }
|
||||
},
|
||||
"required": ["worker", "script"]
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1500,6 +1517,52 @@ async fn run_step_loop(
|
||||
step_chat_history.push(ChatMessage::tool_result(&tc.id, &result));
|
||||
}
|
||||
|
||||
"list_workers" => {
|
||||
let _ = broadcast_tx.send(WsMessage::ActivityUpdate {
|
||||
workflow_id: workflow_id.to_string(),
|
||||
activity: format!("步骤 {} — 列出 Workers", step_order),
|
||||
});
|
||||
let workers = mgr.worker_mgr.list().await;
|
||||
let result = if workers.is_empty() {
|
||||
"没有已注册的 worker。".to_string()
|
||||
} else {
|
||||
let items: Vec<String> = workers.iter().map(|(name, info)| {
|
||||
format!("- {} (cpu={}, mem={}, gpu={}, os={}, kernel={})",
|
||||
name, info.cpu, info.memory, info.gpu, info.os, info.kernel)
|
||||
}).collect();
|
||||
format!("已注册的 workers:\n{}", items.join("\n"))
|
||||
};
|
||||
log_execution(pool, broadcast_tx, workflow_id, step_order, "list_workers", "", &result, "done").await;
|
||||
step_chat_history.push(ChatMessage::tool_result(&tc.id, &result));
|
||||
}
|
||||
|
||||
"execute_on_worker" => {
|
||||
let worker_name = args.get("worker").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let script = args.get("script").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let timeout = args.get("timeout").and_then(|v| v.as_u64()).unwrap_or(300);
|
||||
let _ = broadcast_tx.send(WsMessage::ActivityUpdate {
|
||||
workflow_id: workflow_id.to_string(),
|
||||
activity: format!("步骤 {} — 在 {} 上执行脚本", step_order, worker_name),
|
||||
});
|
||||
let result = match mgr.worker_mgr.execute(worker_name, script, timeout).await {
|
||||
Ok(wr) => {
|
||||
let mut out = String::new();
|
||||
out.push_str(&format!("exit_code: {}\n", wr.exit_code));
|
||||
if !wr.stdout.is_empty() {
|
||||
out.push_str(&format!("stdout:\n{}\n", truncate_str(&wr.stdout, 8192)));
|
||||
}
|
||||
if !wr.stderr.is_empty() {
|
||||
out.push_str(&format!("stderr:\n{}\n", truncate_str(&wr.stderr, 4096)));
|
||||
}
|
||||
out
|
||||
}
|
||||
Err(e) => format!("Error: {}", e),
|
||||
};
|
||||
let status = if result.starts_with("Error:") { "failed" } else { "done" };
|
||||
log_execution(pool, broadcast_tx, workflow_id, step_order, "execute_on_worker", &tc.function.arguments, &result, status).await;
|
||||
step_chat_history.push(ChatMessage::tool_result(&tc.id, &result));
|
||||
}
|
||||
|
||||
// External tools
|
||||
name if external_tools.as_ref().is_some_and(|e| e.has_tool(name)) => {
|
||||
let _ = broadcast_tx.send(WsMessage::ActivityUpdate {
|
||||
|
||||
Reference in New Issue
Block a user