sync: Multi-project updates - SolverBot, GuruRMM, Dataforth
SolverBot: - Inject active project path into agent system prompts so agents know which directory to scope file operations to GuruRMM: - Bump agent version to 0.6.0 - Add serde aliases for PowerShell/ClaudeTask command types - Add typed CommandType enum on server for proper serialization - Support claude_task command type in send_command API Dataforth: - Fix SCP space-escaping in Sync-FromNAS.ps1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,16 +10,16 @@ use uuid::Uuid;
|
||||
|
||||
use crate::auth::AuthUser;
|
||||
use crate::db::{self, Command};
|
||||
use crate::ws::{CommandPayload, ServerMessage};
|
||||
use crate::ws::{CommandPayload, CommandType, ServerMessage};
|
||||
use crate::AppState;
|
||||
|
||||
/// Request to send a command to an agent
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SendCommandRequest {
|
||||
/// Command type (shell, powershell, python, script)
|
||||
/// Command type (shell, powershell, python, script, claude_task)
|
||||
pub command_type: String,
|
||||
|
||||
/// Command text to execute
|
||||
/// Command text to execute (also used as task description for claude_task)
|
||||
pub command: String,
|
||||
|
||||
/// Timeout in seconds (optional, default 300)
|
||||
@@ -27,6 +27,12 @@ pub struct SendCommandRequest {
|
||||
|
||||
/// Run as elevated/admin (optional, default false)
|
||||
pub elevated: Option<bool>,
|
||||
|
||||
/// Working directory for claude_task (optional, default C:\Shares\test)
|
||||
pub working_directory: Option<String>,
|
||||
|
||||
/// Context files for claude_task (optional)
|
||||
pub context_files: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Response after sending a command
|
||||
@@ -59,6 +65,18 @@ pub async fn send_command(
|
||||
"Command sent by user"
|
||||
);
|
||||
|
||||
// Validate and build command type
|
||||
let command_type = if CommandType::is_claude_task(&req.command_type) {
|
||||
CommandType::new_claude_task(
|
||||
req.command.clone(),
|
||||
req.working_directory.clone(),
|
||||
req.context_files.clone(),
|
||||
)
|
||||
} else {
|
||||
CommandType::from_api_string(&req.command_type)
|
||||
.map_err(|e| (StatusCode::BAD_REQUEST, e))?
|
||||
};
|
||||
|
||||
// Verify agent exists
|
||||
let _agent = db::get_agent_by_id(&state.db, agent_id)
|
||||
.await
|
||||
@@ -66,9 +84,10 @@ pub async fn send_command(
|
||||
.ok_or((StatusCode::NOT_FOUND, "Agent not found".to_string()))?;
|
||||
|
||||
// Create command record with user ID for audit trail
|
||||
// Store the canonical db string format for consistency
|
||||
let create = db::CreateCommand {
|
||||
agent_id,
|
||||
command_type: req.command_type.clone(),
|
||||
command_type: command_type.as_db_string().to_string(),
|
||||
command_text: req.command.clone(),
|
||||
created_by: Some(user.user_id),
|
||||
};
|
||||
@@ -80,10 +99,11 @@ pub async fn send_command(
|
||||
// Check if agent is connected
|
||||
let agents = state.agents.read().await;
|
||||
if agents.is_connected(&agent_id) {
|
||||
// Send command via WebSocket
|
||||
// Send command via WebSocket using the proper enum type
|
||||
// This serializes as snake_case to match the agent's expected format
|
||||
let cmd_msg = ServerMessage::Command(CommandPayload {
|
||||
id: command.id,
|
||||
command_type: req.command_type,
|
||||
command_type,
|
||||
command: req.command,
|
||||
timeout_seconds: req.timeout_seconds,
|
||||
elevated: req.elevated.unwrap_or(false),
|
||||
|
||||
@@ -193,10 +193,74 @@ pub struct NetworkStatePayload {
|
||||
pub state_hash: String,
|
||||
}
|
||||
|
||||
/// Types of commands that can be sent to agents.
|
||||
/// Must match the agent's CommandType enum serialization format.
|
||||
/// Uses snake_case to match the agent's #[serde(rename_all = "snake_case")].
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CommandType {
|
||||
/// Shell command (cmd on Windows, sh on Unix)
|
||||
Shell,
|
||||
/// PowerShell command (Windows)
|
||||
PowerShell,
|
||||
/// Python script
|
||||
Python,
|
||||
/// Raw script execution
|
||||
Script,
|
||||
/// Claude Code task execution
|
||||
ClaudeTask {
|
||||
/// Task description for Claude Code
|
||||
task: String,
|
||||
/// Optional working directory
|
||||
working_directory: Option<String>,
|
||||
/// Optional context files
|
||||
context_files: Option<Vec<String>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl CommandType {
|
||||
/// Parse a command type string from the API into the enum.
|
||||
/// Accepts both snake_case ("power_shell") and common formats ("powershell").
|
||||
/// Note: ClaudeTask requires additional fields - use `new_claude_task()` instead.
|
||||
pub fn from_api_string(s: &str) -> Result<Self, String> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"shell" => Ok(Self::Shell),
|
||||
"powershell" | "power_shell" => Ok(Self::PowerShell),
|
||||
"python" => Ok(Self::Python),
|
||||
"script" => Ok(Self::Script),
|
||||
"claude_task" | "claudetask" => Err(
|
||||
"claude_task type requires task field - use the claude_task-specific API fields".to_string()
|
||||
),
|
||||
_ => Err(format!("Unknown command type: '{}'. Valid types: shell, powershell, python, script, claude_task", s)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a command type string represents a claude_task.
|
||||
pub fn is_claude_task(s: &str) -> bool {
|
||||
matches!(s.to_lowercase().as_str(), "claude_task" | "claudetask")
|
||||
}
|
||||
|
||||
/// Create a ClaudeTask command type with the required fields.
|
||||
pub fn new_claude_task(task: String, working_directory: Option<String>, context_files: Option<Vec<String>>) -> Self {
|
||||
Self::ClaudeTask { task, working_directory, context_files }
|
||||
}
|
||||
|
||||
/// Convert back to the string format stored in the database.
|
||||
pub fn as_db_string(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Shell => "shell",
|
||||
Self::PowerShell => "powershell",
|
||||
Self::Python => "python",
|
||||
Self::Script => "script",
|
||||
Self::ClaudeTask { .. } => "claude_task",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CommandPayload {
|
||||
pub id: Uuid,
|
||||
pub command_type: String,
|
||||
pub command_type: CommandType,
|
||||
pub command: String,
|
||||
pub timeout_seconds: Option<u64>,
|
||||
pub elevated: bool,
|
||||
|
||||
Reference in New Issue
Block a user