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:
2026-02-18 16:16:18 -07:00
parent 6d3582d5dc
commit 8b6f0bcc96
37 changed files with 1544 additions and 15 deletions

View File

@@ -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),