From 188d74c0725ef2dec945d8ee8cf28c1e74a72529 Mon Sep 17 00:00:00 2001 From: Nathan Date: Wed, 17 Dec 2025 17:18:27 -0700 Subject: [PATCH] launch error display --- .../2025-12-17_23-08Z-build-status-query.md | 20900 +++++++++++++++- Scripts/remote_worker_controller.ps1 | 18 +- Services/SshService.cs | 22 +- Services/WorkerControllerService.cs | 352 +- Views/ErrorDialog.axaml | 37 + Views/ErrorDialog.axaml.cs | 23 + Views/MainWindow.axaml.cs | 18 +- 7 files changed, 21351 insertions(+), 19 deletions(-) create mode 100644 Views/ErrorDialog.axaml create mode 100644 Views/ErrorDialog.axaml.cs diff --git a/.specstory/history/2025-12-17_23-08Z-build-status-query.md b/.specstory/history/2025-12-17_23-08Z-build-status-query.md index 7e851a1..10ddc1d 100644 --- a/.specstory/history/2025-12-17_23-08Z-build-status-query.md +++ b/.specstory/history/2025-12-17_23-08Z-build-status-query.md @@ -39439,7 +39439,20905 @@ Verifying the save/load flow for the GPU value: 2. **Added default GPU handling** in `ViewModels/WorkerEditViewModel.cs`: - Updated `LoadWorker()` to default to "OPTIX_0" when the saved GPU value is empty or null - - Prevents the ComboBox from showin + - Prevents the ComboBox from showing no selection when loading existing workers + +3. **Verified GPU value flow**: + - `ToWorkerConfig()` saves the GPU value correctly (line 149) + - `WorkerControllerService.GenerateSheepItPayload()` uses the GPU value in the command line (line 145) + - JSON serialization is configured with `JsonPropertyName("gpu")` + - Build succeeded with no errors + +The GPU selection should now persist correctly: +- Selecting a GPU and saving the worker stores the value +- Reopening the worker settings shows the previously selected GPU +- The selected GPU is used when starting the worker + +The fix addresses the binding mismatch between the ComboBox items and the string property, ensuring proper two-way binding. + +--- + +_**User**_ + +it's still misreporting; the worker has clearly not started. + +--- + +_**Agent (model default, mode Agent)**_ + + +Investigating how the application determines worker status. Checking the worker controller and status tracking: + + +--- + + +
+Tool use: **semantic_search_full** + + +Parameters: + +```json +{ + "query": "How does the application determine if a worker is running or started successfully?", + "topK": 10, + "explanation": "" +} +``` +Result: + +```json +{ + "codeResults": [ + { + "codeBlock": { + "relativeWorkspacePath": ".\\Scripts\\remote_worker_controller.ps1", + "range": { + "startPosition": { + "line": 298 + }, + "endPosition": { + "line": 334 + } + }, + "contents": " else {\n $stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')\n $stderrHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'ERR')\n \n $outputHandler = [System.Diagnostics.DataReceivedEventHandler]$stdoutHandler.OnDataReceived\n $errorHandler = [System.Diagnostics.DataReceivedEventHandler]$stderrHandler.OnDataReceived\n\n $workerProcess.add_OutputDataReceived($outputHandler)\n $workerProcess.add_ErrorDataReceived($errorHandler)\n $workerProcess.BeginOutputReadLine()\n $workerProcess.BeginErrorReadLine()\n Write-ControllerLog \"Output handlers set up successfully\"\n\n # Give process a moment to start, then check again\n Start-Sleep -Milliseconds 200\n if ($workerProcess.HasExited) {\n $exitCode = -1\n try {\n $exitCode = $workerProcess.ExitCode\n }\n catch {\n Write-ControllerLog \"Unable to read exit code after delay: $($_.Exception.Message)\"\n }\n Write-ControllerLog \"Worker process exited after 200ms delay with code $exitCode\"\n Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode\n if ($exitCode -eq 0) { break }\n # Continue to restart logic below\n }\n else {\n Write-ControllerLog \"Worker process is running, entering monitoring loop\"\n \n while (-not $workerProcess.HasExited) {\n $commands = Get-PendingCommands\n foreach ($command in $commands) {\n $trimmed = $command.Trim()\n if (-not $trimmed) { continue }", + "signatures": {}, + "detailedLines": [ + { + "text": " else {", + "lineNumber": 299 + }, + { + "text": " $stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')", + "lineNumber": 300 + }, + { + "text": " $stderrHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'ERR')", + "lineNumber": 301 + }, + { + "text": " ", + "lineNumber": 302 + }, + { + "text": " $outputHandler = [System.Diagnostics.DataReceivedEventHandler]$stdoutHandler.OnDataReceived", + "lineNumber": 303 + }, + { + "text": " $errorHandler = [System.Diagnostics.DataReceivedEventHandler]$stderrHandler.OnDataReceived", + "lineNumber": 304 + }, + { + "lineNumber": 305 + }, + { + "text": " $workerProcess.add_OutputDataReceived($outputHandler)", + "lineNumber": 306 + }, + { + "text": " $workerProcess.add_ErrorDataReceived($errorHandler)", + "lineNumber": 307 + }, + { + "text": " $workerProcess.BeginOutputReadLine()", + "lineNumber": 308 + }, + { + "text": " $workerProcess.BeginErrorReadLine()", + "lineNumber": 309 + }, + { + "text": " Write-ControllerLog \"Output handlers set up successfully\"", + "lineNumber": 310 + }, + { + "lineNumber": 311 + }, + { + "text": " # Give process a moment to start, then check again", + "lineNumber": 312 + }, + { + "text": " Start-Sleep -Milliseconds 200", + "lineNumber": 313 + }, + { + "text": " if ($workerProcess.HasExited) {", + "lineNumber": 314 + }, + { + "text": " $exitCode = -1", + "lineNumber": 315 + }, + { + "text": " try {", + "lineNumber": 316 + }, + { + "text": " $exitCode = $workerProcess.ExitCode", + "lineNumber": 317 + }, + { + "text": " }", + "lineNumber": 318 + }, + { + "text": " catch {", + "lineNumber": 319 + }, + { + "text": " Write-ControllerLog \"Unable to read exit code after delay: $($_.Exception.Message)\"", + "lineNumber": 320 + }, + { + "text": " }", + "lineNumber": 321 + }, + { + "text": " Write-ControllerLog \"Worker process exited after 200ms delay with code $exitCode\"", + "lineNumber": 322 + }, + { + "text": " Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode", + "lineNumber": 323 + }, + { + "text": " if ($exitCode -eq 0) { break }", + "lineNumber": 324 + }, + { + "text": " # Continue to restart logic below", + "lineNumber": 325 + }, + { + "text": " }", + "lineNumber": 326 + }, + { + "text": " else {", + "lineNumber": 327 + }, + { + "text": " Write-ControllerLog \"Worker process is running, entering monitoring loop\"", + "lineNumber": 328 + }, + { + "text": " ", + "lineNumber": 329 + }, + { + "text": " while (-not $workerProcess.HasExited) {", + "lineNumber": 330 + }, + { + "text": " $commands = Get-PendingCommands", + "lineNumber": 331 + }, + { + "text": " foreach ($command in $commands) {", + "lineNumber": 332 + }, + { + "text": " $trimmed = $command.Trim()", + "lineNumber": 333 + }, + { + "text": " if (-not $trimmed) { continue }", + "lineNumber": 334 + } + ] + }, + "score": 0.4267798662185669 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Scripts\\remote_worker_controller.ps1", + "range": { + "startPosition": { + "line": 254 + }, + "endPosition": { + "line": 300 + } + }, + "contents": " while ($restartCount -le $MaxRestarts) {\n try {\n # Initialize worker process\n $psi = [System.Diagnostics.ProcessStartInfo]::new()\n $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue\n if ($pwsh) {\n $psi.FileName = $pwsh.Source\n }\n else {\n $psi.FileName = (Get-Command powershell -ErrorAction Stop).Source\n }\n\n $psi.Arguments = \"-NoLogo -NoProfile -ExecutionPolicy Bypass -File `\"$payloadPath`\"\"\n $psi.UseShellExecute = $false\n $psi.RedirectStandardInput = $true\n $psi.RedirectStandardOutput = $true\n $psi.RedirectStandardError = $true\n $psi.CreateNoWindow = $true\n\n $workerProcess = New-Object System.Diagnostics.Process\n $workerProcess.StartInfo = $psi\n\n if (-not $workerProcess.Start()) {\n throw \"Failed to start worker process.\"\n }\n\n Write-ControllerLog \"Worker process started with PID $($workerProcess.Id)\"\n Write-Metadata -Status 'running' -WorkerPid $workerProcess.Id -ControllerPid $controllerPid -Restarts $restartCount\n\n # Check if process exited immediately\n if ($workerProcess.HasExited) {\n $exitCode = -1\n try {\n $exitCode = $workerProcess.ExitCode\n }\n catch {\n Write-ControllerLog \"Unable to read immediate exit code: $($_.Exception.Message)\"\n }\n Write-ControllerLog \"Worker process exited immediately after start with code $exitCode\"\n Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode\n if ($exitCode -eq 0) { break }\n # Continue to restart logic below\n }\n else {\n $stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')", + "signatures": {}, + "detailedLines": [ + { + "text": " while ($restartCount -le $MaxRestarts) {", + "lineNumber": 256 + }, + { + "text": " try {", + "lineNumber": 257 + }, + { + "text": " # Initialize worker process", + "lineNumber": 258 + }, + { + "text": " $psi = [System.Diagnostics.ProcessStartInfo]::new()", + "lineNumber": 259 + }, + { + "text": " $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue", + "lineNumber": 260 + }, + { + "text": " if ($pwsh) {", + "lineNumber": 261 + }, + { + "text": " $psi.FileName = $pwsh.Source", + "lineNumber": 262 + }, + { + "text": " }", + "lineNumber": 263 + }, + { + "text": " else {", + "lineNumber": 264 + }, + { + "text": " $psi.FileName = (Get-Command powershell -ErrorAction Stop).Source", + "lineNumber": 265 + }, + { + "text": " }", + "lineNumber": 266 + }, + { + "lineNumber": 267 + }, + { + "text": " $psi.Arguments = \"-NoLogo -NoProfile -ExecutionPolicy Bypass -File `\"$payloadPath`\"\"", + "lineNumber": 268 + }, + { + "text": " $psi.UseShellExecute = $false", + "lineNumber": 269 + }, + { + "text": " $psi.RedirectStandardInput = $true", + "lineNumber": 270 + }, + { + "text": " $psi.RedirectStandardOutput = $true", + "lineNumber": 271 + }, + { + "text": " $psi.RedirectStandardError = $true", + "lineNumber": 272 + }, + { + "text": " $psi.CreateNoWindow = $true", + "lineNumber": 273 + }, + { + "lineNumber": 274 + }, + { + "text": " $workerProcess = New-Object System.Diagnostics.Process", + "lineNumber": 275 + }, + { + "text": " $workerProcess.StartInfo = $psi", + "lineNumber": 276 + }, + { + "lineNumber": 277 + }, + { + "text": " if (-not $workerProcess.Start()) {", + "lineNumber": 278 + }, + { + "text": " throw \"Failed to start worker process.\"", + "lineNumber": 279 + }, + { + "text": " }", + "lineNumber": 280 + }, + { + "lineNumber": 281 + }, + { + "text": " Write-ControllerLog \"Worker process started with PID $($workerProcess.Id)\"", + "lineNumber": 282 + }, + { + "text": " Write-Metadata -Status 'running' -WorkerPid $workerProcess.Id -ControllerPid $controllerPid -Restarts $restartCount", + "lineNumber": 283 + }, + { + "lineNumber": 284 + }, + { + "text": " # Check if process exited immediately", + "lineNumber": 285 + }, + { + "text": " if ($workerProcess.HasExited) {", + "lineNumber": 286 + }, + { + "text": " $exitCode = -1", + "lineNumber": 287 + }, + { + "text": " try {", + "lineNumber": 288 + }, + { + "text": " $exitCode = $workerProcess.ExitCode", + "lineNumber": 289 + }, + { + "text": " }", + "lineNumber": 290 + }, + { + "text": " catch {", + "lineNumber": 291 + }, + { + "text": " Write-ControllerLog \"Unable to read immediate exit code: $($_.Exception.Message)\"", + "lineNumber": 292 + }, + { + "text": " }", + "lineNumber": 293 + }, + { + "text": " Write-ControllerLog \"Worker process exited immediately after start with code $exitCode\"", + "lineNumber": 294 + }, + { + "text": " Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode", + "lineNumber": 295 + }, + { + "text": " if ($exitCode -eq 0) { break }", + "lineNumber": 296 + }, + { + "text": " # Continue to restart logic below", + "lineNumber": 297 + }, + { + "text": " }", + "lineNumber": 298 + }, + { + "text": " else {", + "lineNumber": 299 + }, + { + "text": " $stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')", + "lineNumber": 300 + } + ] + }, + "score": 0.4078198969364166 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 270 + }, + "endPosition": { + "line": 320, + "column": 8 + } + }, + "contents": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}\nif (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}\n[IO.File]::WriteAllText($payloadBase64Path, $payloadBase64, [System.Text.Encoding]::UTF8)\n$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'\n$controllerPath = Join-Path $dataRoot 'controller.ps1'\n\nif (-not (Test-Path $controllerPath)) {{\n throw \"\"Controller missing at $controllerPath\"\"\n}}\n\n$shouldStart = $true\nif (Test-Path $metaPath) {{\n try {{\n $meta = Get-Content $metaPath -Raw | ConvertFrom-Json\n if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{\n if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{\n Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"\n $shouldStart = $false\n }}\n }}\n }} catch {{\n Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow\n }}\n}}\n\nif ($shouldStart) {{\n $initialMeta = [pscustomobject]@{{\n WorkerName = $workerName\n WorkerType = $workerType\n Status = 'launching'\n ControllerPid = $null\n WorkerPid = $null\n Restarts = 0\n LastExitCode = $null\n LogPath = $logPath\n CommandPath = $commandPath\n PayloadPath = $payloadPath\n UpdatedAtUtc = (Get-Date).ToUniversalTime()\n }} | ConvertTo-Json -Depth 5\n $initialMeta | Set-Content -Path $metaPath -Encoding UTF8\n\n $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue\n if ($pwsh) {{\n $psExe = $pwsh.Source\n }}\n else {{\n $psExe = (Get-Command powershell -ErrorAction Stop).Source\n }}\n\n $controllerArgs = @(\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}", + "lineNumber": 271 + }, + { + "text": "if (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "lineNumber": 272 + }, + { + "text": "[IO.File]::WriteAllText($payloadBase64Path, $payloadBase64, [System.Text.Encoding]::UTF8)", + "lineNumber": 273 + }, + { + "text": "$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'", + "lineNumber": 274 + }, + { + "text": "$controllerPath = Join-Path $dataRoot 'controller.ps1'", + "lineNumber": 275 + }, + { + "lineNumber": 276 + }, + { + "text": "if (-not (Test-Path $controllerPath)) {{", + "lineNumber": 277 + }, + { + "text": " throw \"\"Controller missing at $controllerPath\"\"", + "lineNumber": 278 + }, + { + "text": "}}", + "lineNumber": 279 + }, + { + "lineNumber": 280 + }, + { + "text": "$shouldStart = $true", + "lineNumber": 281 + }, + { + "text": "if (Test-Path $metaPath) {{", + "lineNumber": 282 + }, + { + "text": " try {{", + "lineNumber": 283 + }, + { + "text": " $meta = Get-Content $metaPath -Raw | ConvertFrom-Json", + "lineNumber": 284 + }, + { + "text": " if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{", + "lineNumber": 285 + }, + { + "text": " if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{", + "lineNumber": 286 + }, + { + "text": " Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"", + "lineNumber": 287 + }, + { + "text": " $shouldStart = $false", + "lineNumber": 288 + }, + { + "text": " }}", + "lineNumber": 289 + }, + { + "text": " }}", + "lineNumber": 290 + }, + { + "text": " }} catch {{", + "lineNumber": 291 + }, + { + "text": " Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow", + "lineNumber": 292 + }, + { + "text": " }}", + "lineNumber": 293 + }, + { + "text": "}}", + "lineNumber": 294 + }, + { + "lineNumber": 295 + }, + { + "text": "if ($shouldStart) {{", + "lineNumber": 296 + }, + { + "text": " $initialMeta = [pscustomobject]@{{", + "lineNumber": 297 + }, + { + "text": " WorkerName = $workerName", + "lineNumber": 298 + }, + { + "text": " WorkerType = $workerType", + "lineNumber": 299 + }, + { + "text": " Status = 'launching'", + "lineNumber": 300 + }, + { + "text": " ControllerPid = $null", + "lineNumber": 301 + }, + { + "text": " WorkerPid = $null", + "lineNumber": 302 + }, + { + "text": " Restarts = 0", + "lineNumber": 303 + }, + { + "text": " LastExitCode = $null", + "lineNumber": 304 + }, + { + "text": " LogPath = $logPath", + "lineNumber": 305 + }, + { + "text": " CommandPath = $commandPath", + "lineNumber": 306 + }, + { + "text": " PayloadPath = $payloadPath", + "lineNumber": 307 + }, + { + "text": " UpdatedAtUtc = (Get-Date).ToUniversalTime()", + "lineNumber": 308 + }, + { + "text": " }} | ConvertTo-Json -Depth 5", + "lineNumber": 309 + }, + { + "text": " $initialMeta | Set-Content -Path $metaPath -Encoding UTF8", + "lineNumber": 310 + }, + { + "lineNumber": 311 + }, + { + "text": " $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue", + "lineNumber": 312 + }, + { + "text": " if ($pwsh) {{", + "lineNumber": 313 + }, + { + "text": " $psExe = $pwsh.Source", + "lineNumber": 314 + }, + { + "text": " }}", + "lineNumber": 315 + }, + { + "text": " else {{", + "lineNumber": 316 + }, + { + "text": " $psExe = (Get-Command powershell -ErrorAction Stop).Source", + "lineNumber": 317 + }, + { + "text": " }}", + "lineNumber": 318 + }, + { + "lineNumber": 319 + }, + { + "text": " $controllerArgs = @(", + "lineNumber": 320 + }, + { + "text": " ", + "lineNumber": 321 + } + ] + }, + "score": 0.3978863060474396 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Scripts\\remote_worker_controller.ps1", + "range": { + "startPosition": { + "line": 331 + }, + "endPosition": { + "line": 388 + } + }, + "contents": " foreach ($command in $commands) {\n $trimmed = $command.Trim()\n if (-not $trimmed) { continue }\n\n Write-ControllerLog \"Received command '$trimmed'\"\n try {\n $workerProcess.StandardInput.WriteLine($trimmed)\n $workerProcess.StandardInput.Flush()\n }\n catch {\n Write-ControllerLog \"Failed to forward command '$trimmed': $($_.Exception.Message)\"\n }\n\n if ($trimmed -ieq 'quit') {\n Write-ControllerLog \"Quit command issued. Waiting for worker to exit.\"\n }\n }\n\n Start-Sleep -Milliseconds 500\n }\n # End of monitoring loop - process has exited\n Write-ControllerLog \"Worker process exited, exiting monitoring loop\"\n }\n }\n\n # Wait for process to fully exit before reading exit code\n $workerProcess.WaitForExit(1000)\n \n $exitCode = -1\n try {\n $exitCode = $workerProcess.ExitCode\n }\n catch {\n Write-ControllerLog \"Unable to read worker exit code: $($_.Exception.Message)\"\n }\n \n Write-ControllerLog \"Worker exited with code $exitCode\"\n Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode\n\n if ($exitCode -eq 0) {\n break\n }\n }\n catch {\n Write-ControllerLog \"Controller error: $($_.Exception.Message)\"\n }\n\n $restartCount++\n if ($restartCount -gt $MaxRestarts) {\n Write-ControllerLog \"Maximum restart attempts reached. Controller stopping.\"\n break\n }\n\n Write-ControllerLog \"Restarting worker in $RestartDelaySeconds seconds (attempt $restartCount of $MaxRestarts).\"\n Start-Sleep -Seconds $RestartDelaySeconds\n }", + "signatures": {}, + "detailedLines": [ + { + "text": " foreach ($command in $commands) {", + "lineNumber": 332 + }, + { + "text": " $trimmed = $command.Trim()", + "lineNumber": 333 + }, + { + "text": " if (-not $trimmed) { continue }", + "lineNumber": 334 + }, + { + "lineNumber": 335 + }, + { + "text": " Write-ControllerLog \"Received command '$trimmed'\"", + "lineNumber": 336 + }, + { + "text": " try {", + "lineNumber": 337 + }, + { + "text": " $workerProcess.StandardInput.WriteLine($trimmed)", + "lineNumber": 338 + }, + { + "text": " $workerProcess.StandardInput.Flush()", + "lineNumber": 339 + }, + { + "text": " }", + "lineNumber": 340 + }, + { + "text": " catch {", + "lineNumber": 341 + }, + { + "text": " Write-ControllerLog \"Failed to forward command '$trimmed': $($_.Exception.Message)\"", + "lineNumber": 342 + }, + { + "text": " }", + "lineNumber": 343 + }, + { + "lineNumber": 344 + }, + { + "text": " if ($trimmed -ieq 'quit') {", + "lineNumber": 345 + }, + { + "text": " Write-ControllerLog \"Quit command issued. Waiting for worker to exit.\"", + "lineNumber": 346 + }, + { + "text": " }", + "lineNumber": 347 + }, + { + "text": " }", + "lineNumber": 348 + }, + { + "lineNumber": 349 + }, + { + "text": " Start-Sleep -Milliseconds 500", + "lineNumber": 350 + }, + { + "text": " }", + "lineNumber": 351 + }, + { + "text": " # End of monitoring loop - process has exited", + "lineNumber": 352 + }, + { + "text": " Write-ControllerLog \"Worker process exited, exiting monitoring loop\"", + "lineNumber": 353 + }, + { + "text": " }", + "lineNumber": 354 + }, + { + "text": " }", + "lineNumber": 355 + }, + { + "lineNumber": 356 + }, + { + "text": " # Wait for process to fully exit before reading exit code", + "lineNumber": 357 + }, + { + "text": " $workerProcess.WaitForExit(1000)", + "lineNumber": 358 + }, + { + "text": " ", + "lineNumber": 359 + }, + { + "text": " $exitCode = -1", + "lineNumber": 360 + }, + { + "text": " try {", + "lineNumber": 361 + }, + { + "text": " $exitCode = $workerProcess.ExitCode", + "lineNumber": 362 + }, + { + "text": " }", + "lineNumber": 363 + }, + { + "text": " catch {", + "lineNumber": 364 + }, + { + "text": " Write-ControllerLog \"Unable to read worker exit code: $($_.Exception.Message)\"", + "lineNumber": 365 + }, + { + "text": " }", + "lineNumber": 366 + }, + { + "text": " ", + "lineNumber": 367 + }, + { + "text": " Write-ControllerLog \"Worker exited with code $exitCode\"", + "lineNumber": 368 + }, + { + "text": " Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode", + "lineNumber": 369 + }, + { + "lineNumber": 370 + }, + { + "text": " if ($exitCode -eq 0) {", + "lineNumber": 371 + }, + { + "text": " break", + "lineNumber": 372 + }, + { + "text": " }", + "lineNumber": 373 + }, + { + "text": " }", + "lineNumber": 374 + }, + { + "text": " catch {", + "lineNumber": 375 + }, + { + "text": " Write-ControllerLog \"Controller error: $($_.Exception.Message)\"", + "lineNumber": 376 + }, + { + "text": " }", + "lineNumber": 377 + }, + { + "lineNumber": 378 + }, + { + "text": " $restartCount++", + "lineNumber": 379 + }, + { + "text": " if ($restartCount -gt $MaxRestarts) {", + "lineNumber": 380 + }, + { + "text": " Write-ControllerLog \"Maximum restart attempts reached. Controller stopping.\"", + "lineNumber": 381 + }, + { + "text": " break", + "lineNumber": 382 + }, + { + "text": " }", + "lineNumber": 383 + }, + { + "lineNumber": 384 + }, + { + "text": " Write-ControllerLog \"Restarting worker in $RestartDelaySeconds seconds (attempt $restartCount of $MaxRestarts).\"", + "lineNumber": 385 + }, + { + "text": " Start-Sleep -Seconds $RestartDelaySeconds", + "lineNumber": 386 + }, + { + "text": " }", + "lineNumber": 387 + } + ] + }, + "score": 0.33045876026153564 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Views\\MainWindow.axaml.cs", + "range": { + "startPosition": { + "line": 101 + }, + "endPosition": { + "line": 139 + } + }, + "contents": " string? workerType = vm.OperationMode;\n\n // Verify the worker supports the selected operation mode\n if (workerType == \"sheepit\" && worker.WorkerTypes.SheepIt == null)\n {\n var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", \n $\"Worker '{worker.Name}' does not have SheepIt configured.\", \n ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);\n await box.ShowAsync();\n return;\n }\n if (workerType == \"flamenco\" && worker.WorkerTypes.Flamenco == null)\n {\n var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", \n $\"Worker '{worker.Name}' does not have Flamenco configured.\", \n ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);\n await box.ShowAsync();\n return;\n }\n\n await _controllerService.StartWorkerAsync(worker, workerType);\n var successBox = MessageBoxManager.GetMessageBoxStandard(\"Start Worker\", \n $\"Worker '{worker.Name}' ({vm.OperationModeDisplayName}) started successfully.\", \n ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Success);\n await successBox.ShowAsync();\n vm.RefreshWorkers();\n }\n catch (System.Exception ex)\n {\n var errorBox = MessageBoxManager.GetMessageBoxStandard(\"Error\", \n $\"Failed to start worker: {ex.Message}\", \n ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);\n await errorBox.ShowAsync();\n }\n }\n }\n\n private async void StopWorkerButton_Click(object? sender, RoutedEventArgs e)", + "signatures": {}, + "detailedLines": [ + { + "text": " string? workerType = vm.OperationMode;", + "lineNumber": 102 + }, + { + "lineNumber": 103 + }, + { + "text": " // Verify the worker supports the selected operation mode", + "lineNumber": 104 + }, + { + "text": " if (workerType == \"sheepit\" && worker.WorkerTypes.SheepIt == null)", + "lineNumber": 105 + }, + { + "text": " {", + "lineNumber": 106 + }, + { + "text": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", ", + "lineNumber": 107 + }, + { + "text": " $\"Worker '{worker.Name}' does not have SheepIt configured.\", ", + "lineNumber": 108 + }, + { + "text": " ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);", + "lineNumber": 109 + }, + { + "text": " await box.ShowAsync();", + "lineNumber": 110 + }, + { + "text": " return;", + "lineNumber": 111 + }, + { + "text": " }", + "lineNumber": 112 + }, + { + "text": " if (workerType == \"flamenco\" && worker.WorkerTypes.Flamenco == null)", + "lineNumber": 113 + }, + { + "text": " {", + "lineNumber": 114 + }, + { + "text": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", ", + "lineNumber": 115 + }, + { + "text": " $\"Worker '{worker.Name}' does not have Flamenco configured.\", ", + "lineNumber": 116 + }, + { + "text": " ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);", + "lineNumber": 117 + }, + { + "text": " await box.ShowAsync();", + "lineNumber": 118 + }, + { + "text": " return;", + "lineNumber": 119 + }, + { + "text": " }", + "lineNumber": 120 + }, + { + "lineNumber": 121 + }, + { + "text": " await _controllerService.StartWorkerAsync(worker, workerType);", + "lineNumber": 122 + }, + { + "text": " var successBox = MessageBoxManager.GetMessageBoxStandard(\"Start Worker\", ", + "lineNumber": 123 + }, + { + "text": " $\"Worker '{worker.Name}' ({vm.OperationModeDisplayName}) started successfully.\", ", + "lineNumber": 124 + }, + { + "text": " ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Success);", + "lineNumber": 125 + }, + { + "text": " await successBox.ShowAsync();", + "lineNumber": 126 + }, + { + "text": " vm.RefreshWorkers();", + "lineNumber": 127 + }, + { + "text": " }", + "lineNumber": 128 + }, + { + "text": " catch (System.Exception ex)", + "lineNumber": 129 + }, + { + "text": " {", + "lineNumber": 130 + }, + { + "text": " var errorBox = MessageBoxManager.GetMessageBoxStandard(\"Error\", ", + "lineNumber": 131 + }, + { + "text": " $\"Failed to start worker: {ex.Message}\", ", + "lineNumber": 132 + }, + { + "text": " ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);", + "lineNumber": 133 + }, + { + "text": " await errorBox.ShowAsync();", + "lineNumber": 134 + }, + { + "text": " }", + "lineNumber": 135 + }, + { + "text": " }", + "lineNumber": 136 + }, + { + "text": " }", + "lineNumber": 137 + }, + { + "lineNumber": 138 + }, + { + "text": " private async void StopWorkerButton_Click(object? sender, RoutedEventArgs e)", + "lineNumber": 139 + } + ] + }, + "score": 0.3148171007633209 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Scripts\\remote_worker_controller.ps1", + "range": { + "startPosition": {}, + "endPosition": { + "line": 57 + } + }, + "contents": "param(\n [Parameter(Mandatory = $true)]\n [string]$WorkerName,\n\n [Parameter(Mandatory = $true)]\n [string]$WorkerType,\n\n [string]$PayloadBase64,\n\n [string]$PayloadBase64Path,\n\n[string]$DataRoot = (Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'),\n\n [int]$MaxRestarts = 5,\n\n [int]$RestartDelaySeconds = 10\n)\n\n$ErrorActionPreference = 'Stop'\ntry {\n if ($Host -and $Host.Runspace) {\n [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace = $Host.Runspace\n }\n}\ncatch {\n # Ignore runspace assignment errors - not critical for non-interactive execution\n}\n\n# region Path setup\n$workerRoot = Join-Path -Path $DataRoot -ChildPath $WorkerType\n$instanceRoot = Join-Path -Path $workerRoot -ChildPath $WorkerName\nNew-Item -ItemType Directory -Path $instanceRoot -Force | Out-Null\n\n$logsRoot = Join-Path -Path $instanceRoot -ChildPath 'logs'\n$stateRoot = Join-Path -Path $instanceRoot -ChildPath 'state'\nNew-Item -ItemType Directory -Path $logsRoot -Force | Out-Null\nNew-Item -ItemType Directory -Path $stateRoot -Force | Out-Null\n\n$logPath = Join-Path -Path $logsRoot -ChildPath 'worker.log'\n$metaPath = Join-Path -Path $stateRoot -ChildPath 'worker-info.json'\n$commandPath = Join-Path -Path $stateRoot -ChildPath 'commands.txt'\n$payloadPath = Join-Path -Path $stateRoot -ChildPath \"payload.ps1\"\n# endregion\n\n# region Logging\ntry {\n $logStream = [System.IO.FileStream]::new(\n $logPath,\n [System.IO.FileMode]::Append,\n [System.IO.FileAccess]::Write,\n [System.IO.FileShare]::ReadWrite\n )\n $logWriter = [System.IO.StreamWriter]::new($logStream, [System.Text.Encoding]::UTF8)\n $logWriter.AutoFlush = $true\n}\ncatch {\n # If we can't open the log file, write error to metadata and exit", + "signatures": {}, + "detailedLines": [ + { + "text": "param(", + "lineNumber": 1 + }, + { + "text": " [Parameter(Mandatory = $true)]", + "lineNumber": 2 + }, + { + "text": " [string]$WorkerName,", + "lineNumber": 3 + }, + { + "lineNumber": 4 + }, + { + "text": " [Parameter(Mandatory = $true)]", + "lineNumber": 5 + }, + { + "text": " [string]$WorkerType,", + "lineNumber": 6 + }, + { + "lineNumber": 7 + }, + { + "text": " [string]$PayloadBase64,", + "lineNumber": 8 + }, + { + "lineNumber": 9 + }, + { + "text": " [string]$PayloadBase64Path,", + "lineNumber": 10 + }, + { + "lineNumber": 11 + }, + { + "text": "[string]$DataRoot = (Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'),", + "lineNumber": 12 + }, + { + "lineNumber": 13 + }, + { + "text": " [int]$MaxRestarts = 5,", + "lineNumber": 14 + }, + { + "lineNumber": 15 + }, + { + "text": " [int]$RestartDelaySeconds = 10", + "lineNumber": 16 + }, + { + "text": ")", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 19 + }, + { + "text": "try {", + "lineNumber": 20 + }, + { + "text": " if ($Host -and $Host.Runspace) {", + "lineNumber": 21 + }, + { + "text": " [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace = $Host.Runspace", + "lineNumber": 22 + }, + { + "text": " }", + "lineNumber": 23 + }, + { + "text": "}", + "lineNumber": 24 + }, + { + "text": "catch {", + "lineNumber": 25 + }, + { + "text": " # Ignore runspace assignment errors - not critical for non-interactive execution", + "lineNumber": 26 + }, + { + "text": "}", + "lineNumber": 27 + }, + { + "lineNumber": 28 + }, + { + "text": "# region Path setup", + "lineNumber": 29 + }, + { + "text": "$workerRoot = Join-Path -Path $DataRoot -ChildPath $WorkerType", + "lineNumber": 30 + }, + { + "text": "$instanceRoot = Join-Path -Path $workerRoot -ChildPath $WorkerName", + "lineNumber": 31 + }, + { + "text": "New-Item -ItemType Directory -Path $instanceRoot -Force | Out-Null", + "lineNumber": 32 + }, + { + "lineNumber": 33 + }, + { + "text": "$logsRoot = Join-Path -Path $instanceRoot -ChildPath 'logs'", + "lineNumber": 34 + }, + { + "text": "$stateRoot = Join-Path -Path $instanceRoot -ChildPath 'state'", + "lineNumber": 35 + }, + { + "text": "New-Item -ItemType Directory -Path $logsRoot -Force | Out-Null", + "lineNumber": 36 + }, + { + "text": "New-Item -ItemType Directory -Path $stateRoot -Force | Out-Null", + "lineNumber": 37 + }, + { + "lineNumber": 38 + }, + { + "text": "$logPath = Join-Path -Path $logsRoot -ChildPath 'worker.log'", + "lineNumber": 39 + }, + { + "text": "$metaPath = Join-Path -Path $stateRoot -ChildPath 'worker-info.json'", + "lineNumber": 40 + }, + { + "text": "$commandPath = Join-Path -Path $stateRoot -ChildPath 'commands.txt'", + "lineNumber": 41 + }, + { + "text": "$payloadPath = Join-Path -Path $stateRoot -ChildPath \"payload.ps1\"", + "lineNumber": 42 + }, + { + "text": "# endregion", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": "# region Logging", + "lineNumber": 45 + }, + { + "text": "try {", + "lineNumber": 46 + }, + { + "text": " $logStream = [System.IO.FileStream]::new(", + "lineNumber": 47 + }, + { + "text": " $logPath,", + "lineNumber": 48 + }, + { + "text": " [System.IO.FileMode]::Append,", + "lineNumber": 49 + }, + { + "text": " [System.IO.FileAccess]::Write,", + "lineNumber": 50 + }, + { + "text": " [System.IO.FileShare]::ReadWrite", + "lineNumber": 51 + }, + { + "text": " )", + "lineNumber": 52 + }, + { + "text": " $logWriter = [System.IO.StreamWriter]::new($logStream, [System.Text.Encoding]::UTF8)", + "lineNumber": 53 + }, + { + "text": " $logWriter.AutoFlush = $true", + "lineNumber": 54 + }, + { + "text": "}", + "lineNumber": 55 + }, + { + "text": "catch {", + "lineNumber": 56 + }, + { + "text": " # If we can't open the log file, write error to metadata and exit", + "lineNumber": 57 + } + ] + }, + "score": 0.3097786009311676 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 317 + }, + "endPosition": { + "line": 352 + } + }, + "contents": " }}\n\n $controllerArgs = @(\n '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',\n '-File',\"\"$controllerPath\"\",\n '-WorkerName',\"\"$workerName\"\",\n '-WorkerType',\"\"$workerType\"\",\n '-PayloadBase64Path',\"\"$payloadBase64Path\"\"\n )\n\n Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden | Out-Null\n Write-Host \"\"Worker $workerName started under controller.\"\" -ForegroundColor Green\n}}\n\";\n\n // Pipe script through stdin to avoid command line length limits\n await _sshService.ExecuteRemoteScriptAsync(worker, ensureScript);\n }\n\n public async Task StopWorkerAsync(WorkerConfig worker, string workerType)\n {\n var script = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\n$instanceRoot = Join-Path (Join-Path $dataRoot '{workerType}') '{worker.Name}'\n$commandPath = Join-Path $instanceRoot 'state\\commands.txt'\n[IO.File]::WriteAllText($commandPath, 'quit', [System.Text.Encoding]::UTF8)\nWrite-Host \"\"Quit command sent to worker {worker.Name}.\"\"\n\";\n\n await _sshService.ExecuteRemoteCommandAsync(worker, $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {Convert.ToBase64String(Encoding.Unicode.GetBytes(script))}\");\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": " }}", + "lineNumber": 318 + }, + { + "lineNumber": 319 + }, + { + "text": " $controllerArgs = @(", + "lineNumber": 320 + }, + { + "text": " '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',", + "lineNumber": 321 + }, + { + "text": " '-File',\"\"$controllerPath\"\",", + "lineNumber": 322 + }, + { + "text": " '-WorkerName',\"\"$workerName\"\",", + "lineNumber": 323 + }, + { + "text": " '-WorkerType',\"\"$workerType\"\",", + "lineNumber": 324 + }, + { + "text": " '-PayloadBase64Path',\"\"$payloadBase64Path\"\"", + "lineNumber": 325 + }, + { + "text": " )", + "lineNumber": 326 + }, + { + "lineNumber": 327 + }, + { + "text": " Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden | Out-Null", + "lineNumber": 328 + }, + { + "text": " Write-Host \"\"Worker $workerName started under controller.\"\" -ForegroundColor Green", + "lineNumber": 329 + }, + { + "text": "}}", + "lineNumber": 330 + }, + { + "text": "\";", + "lineNumber": 331 + }, + { + "lineNumber": 332 + }, + { + "text": " // Pipe script through stdin to avoid command line length limits", + "lineNumber": 333 + }, + { + "text": " await _sshService.ExecuteRemoteScriptAsync(worker, ensureScript);", + "lineNumber": 334 + }, + { + "text": " }", + "lineNumber": 335 + }, + { + "lineNumber": 336 + }, + { + "text": " public async Task StopWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 337 + }, + { + "text": " {", + "lineNumber": 338 + }, + { + "text": " var script = $@\"", + "lineNumber": 339 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 340 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 341 + }, + { + "text": "$instanceRoot = Join-Path (Join-Path $dataRoot '{workerType}') '{worker.Name}'", + "lineNumber": 342 + }, + { + "text": "$commandPath = Join-Path $instanceRoot 'state\\commands.txt'", + "lineNumber": 343 + }, + { + "text": "[IO.File]::WriteAllText($commandPath, 'quit', [System.Text.Encoding]::UTF8)", + "lineNumber": 344 + }, + { + "text": "Write-Host \"\"Quit command sent to worker {worker.Name}.\"\"", + "lineNumber": 345 + }, + { + "text": "\";", + "lineNumber": 346 + }, + { + "lineNumber": 347 + }, + { + "text": " await _sshService.ExecuteRemoteCommandAsync(worker, $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {Convert.ToBase64String(Encoding.Unicode.GetBytes(script))}\");", + "lineNumber": 348 + }, + { + "text": " }", + "lineNumber": 349 + }, + { + "text": " }", + "lineNumber": 350 + }, + { + "text": "}", + "lineNumber": 351 + } + ] + }, + "score": 0.3018067479133606 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 185 + }, + "endPosition": { + "line": 231 + } + }, + "contents": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')\n\ntry {{\n Write-Host \"\"Setting up network connections...\"\" -ForegroundColor Cyan\n\n $drives = @({drives})\n $networkPaths = @({paths})\n\n # Disconnect all existing connections\n Write-Host \"\"Disconnecting existing network connections...\"\" -ForegroundColor Yellow\n foreach ($path in $networkPaths) {{ net use $path /delete /y 2>$null }}\n foreach ($drive in $drives) {{ net use $drive /delete /y 2>$null }}\n Write-Host \"\"All network connections cleared.\"\" -ForegroundColor Green\n\n # Connect to network shares (simplified - credentials should be stored securely)\n Write-Host \"\"Establishing network connections...\"\" -ForegroundColor Cyan\n # TODO: Add credential handling for network shares\n\n # Start worker\n Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan\n Set-Location '{workerPath}'\n if (Test-Path 'flamenco-worker.exe') {{\n Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green\n $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait\n $exitCode = $workerProcess.ExitCode\n Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow\n exit $exitCode\n }} else {{\n Write-Host \"\"Error: flamenco-worker.exe not found in {workerPath}\"\" -ForegroundColor Red\n [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {workerPath}\"\")\n exit 1\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "signatures": {}, + "detailedLines": [ + { + "text": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "lineNumber": 186 + }, + { + "lineNumber": 187 + }, + { + "text": "try {{", + "lineNumber": 188 + }, + { + "text": " Write-Host \"\"Setting up network connections...\"\" -ForegroundColor Cyan", + "lineNumber": 189 + }, + { + "lineNumber": 190 + }, + { + "text": " $drives = @({drives})", + "lineNumber": 191 + }, + { + "text": " $networkPaths = @({paths})", + "lineNumber": 192 + }, + { + "lineNumber": 193 + }, + { + "text": " # Disconnect all existing connections", + "lineNumber": 194 + }, + { + "text": " Write-Host \"\"Disconnecting existing network connections...\"\" -ForegroundColor Yellow", + "lineNumber": 195 + }, + { + "text": " foreach ($path in $networkPaths) {{ net use $path /delete /y 2>$null }}", + "lineNumber": 196 + }, + { + "text": " foreach ($drive in $drives) {{ net use $drive /delete /y 2>$null }}", + "lineNumber": 197 + }, + { + "text": " Write-Host \"\"All network connections cleared.\"\" -ForegroundColor Green", + "lineNumber": 198 + }, + { + "lineNumber": 199 + }, + { + "text": " # Connect to network shares (simplified - credentials should be stored securely)", + "lineNumber": 200 + }, + { + "text": " Write-Host \"\"Establishing network connections...\"\" -ForegroundColor Cyan", + "lineNumber": 201 + }, + { + "text": " # TODO: Add credential handling for network shares", + "lineNumber": 202 + }, + { + "lineNumber": 203 + }, + { + "text": " # Start worker", + "lineNumber": 204 + }, + { + "text": " Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan", + "lineNumber": 205 + }, + { + "text": " Set-Location '{workerPath}'", + "lineNumber": 206 + }, + { + "text": " if (Test-Path 'flamenco-worker.exe') {{", + "lineNumber": 207 + }, + { + "text": " Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green", + "lineNumber": 208 + }, + { + "text": " $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait", + "lineNumber": 209 + }, + { + "text": " $exitCode = $workerProcess.ExitCode", + "lineNumber": 210 + }, + { + "text": " Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow", + "lineNumber": 211 + }, + { + "text": " exit $exitCode", + "lineNumber": 212 + }, + { + "text": " }} else {{", + "lineNumber": 213 + }, + { + "text": " Write-Host \"\"Error: flamenco-worker.exe not found in {workerPath}\"\" -ForegroundColor Red", + "lineNumber": 214 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {workerPath}\"\")", + "lineNumber": 215 + }, + { + "text": " exit 1", + "lineNumber": 216 + }, + { + "text": " }}", + "lineNumber": 217 + }, + { + "text": "}}", + "lineNumber": 218 + }, + { + "text": "catch {{", + "lineNumber": 219 + }, + { + "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)", + "lineNumber": 220 + }, + { + "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)", + "lineNumber": 221 + }, + { + "text": " Write-Host $errorMsg -ForegroundColor Red", + "lineNumber": 222 + }, + { + "text": " Write-Host $stackMsg -ForegroundColor DarkRed", + "lineNumber": 223 + }, + { + "text": " [Console]::Error.WriteLine($errorMsg)", + "lineNumber": 224 + }, + { + "text": " [Console]::Error.WriteLine($stackMsg)", + "lineNumber": 225 + }, + { + "text": " exit 1", + "lineNumber": 226 + }, + { + "text": "}}", + "lineNumber": 227 + }, + { + "text": "\";", + "lineNumber": 228 + }, + { + "text": " }", + "lineNumber": 229 + }, + { + "lineNumber": 230 + }, + { + "text": " public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 231 + } + ] + }, + "score": 0.30027469992637634 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 227 + }, + "endPosition": { + "line": 272 + } + }, + "contents": "\";\n }\n\n public async Task StartWorkerAsync(WorkerConfig worker, string workerType)\n {\n await DeployControllerAsync(worker);\n\n string payloadScript;\n if (workerType == \"sheepit\")\n {\n payloadScript = GenerateSheepItPayload(worker);\n }\n else if (workerType == \"flamenco\")\n {\n payloadScript = GenerateFlamencoPayload(worker);\n }\n else\n {\n throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));\n }\n\n var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n\n var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";\n var jsonParamsBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(jsonParams));\n\n var ensureScript = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{jsonParamsBase64}')))\n$workerName = $params.WorkerName\n$workerType = $params.WorkerType\n$payloadBase64 = $params.PayloadBase64\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\n$instanceRoot = Join-Path (Join-Path $dataRoot $workerType) $workerName\n$logsRoot = Join-Path $instanceRoot 'logs'\n$stateRoot = Join-Path $instanceRoot 'state'\nNew-Item -ItemType Directory -Path $logsRoot -Force | Out-Null\nNew-Item -ItemType Directory -Path $stateRoot -Force | Out-Null\n$logPath = Join-Path $logsRoot 'worker.log'\n$commandPath = Join-Path $stateRoot 'commands.txt'\n$payloadPath = Join-Path $stateRoot 'payload.ps1'\n$payloadBase64Path = Join-Path $stateRoot 'payload.b64'\nif (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}\nif (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "signatures": {}, + "detailedLines": [ + { + "text": "\";", + "lineNumber": 228 + }, + { + "text": " }", + "lineNumber": 229 + }, + { + "lineNumber": 230 + }, + { + "text": " public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 231 + }, + { + "text": " {", + "lineNumber": 232 + }, + { + "text": " await DeployControllerAsync(worker);", + "lineNumber": 233 + }, + { + "lineNumber": 234 + }, + { + "text": " string payloadScript;", + "lineNumber": 235 + }, + { + "text": " if (workerType == \"sheepit\")", + "lineNumber": 236 + }, + { + "text": " {", + "lineNumber": 237 + }, + { + "text": " payloadScript = GenerateSheepItPayload(worker);", + "lineNumber": 238 + }, + { + "text": " }", + "lineNumber": 239 + }, + { + "text": " else if (workerType == \"flamenco\")", + "lineNumber": 240 + }, + { + "text": " {", + "lineNumber": 241 + }, + { + "text": " payloadScript = GenerateFlamencoPayload(worker);", + "lineNumber": 242 + }, + { + "text": " }", + "lineNumber": 243 + }, + { + "text": " else", + "lineNumber": 244 + }, + { + "text": " {", + "lineNumber": 245 + }, + { + "text": " throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));", + "lineNumber": 246 + }, + { + "text": " }", + "lineNumber": 247 + }, + { + "lineNumber": 248 + }, + { + "text": " var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));", + "lineNumber": 249 + }, + { + "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);", + "lineNumber": 250 + }, + { + "lineNumber": 251 + }, + { + "text": " var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";", + "lineNumber": 252 + }, + { + "text": " var jsonParamsBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(jsonParams));", + "lineNumber": 253 + }, + { + "lineNumber": 254 + }, + { + "text": " var ensureScript = $@\"", + "lineNumber": 255 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 256 + }, + { + "text": "$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{jsonParamsBase64}')))", + "lineNumber": 257 + }, + { + "text": "$workerName = $params.WorkerName", + "lineNumber": 258 + }, + { + "text": "$workerType = $params.WorkerType", + "lineNumber": 259 + }, + { + "text": "$payloadBase64 = $params.PayloadBase64", + "lineNumber": 260 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 261 + }, + { + "text": "$instanceRoot = Join-Path (Join-Path $dataRoot $workerType) $workerName", + "lineNumber": 262 + }, + { + "text": "$logsRoot = Join-Path $instanceRoot 'logs'", + "lineNumber": 263 + }, + { + "text": "$stateRoot = Join-Path $instanceRoot 'state'", + "lineNumber": 264 + }, + { + "text": "New-Item -ItemType Directory -Path $logsRoot -Force | Out-Null", + "lineNumber": 265 + }, + { + "text": "New-Item -ItemType Directory -Path $stateRoot -Force | Out-Null", + "lineNumber": 266 + }, + { + "text": "$logPath = Join-Path $logsRoot 'worker.log'", + "lineNumber": 267 + }, + { + "text": "$commandPath = Join-Path $stateRoot 'commands.txt'", + "lineNumber": 268 + }, + { + "text": "$payloadPath = Join-Path $stateRoot 'payload.ps1'", + "lineNumber": 269 + }, + { + "text": "$payloadBase64Path = Join-Path $stateRoot 'payload.b64'", + "lineNumber": 270 + }, + { + "text": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}", + "lineNumber": 271 + }, + { + "text": "if (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "lineNumber": 272 + } + ] + }, + "score": 0.2886880934238434 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\README.md", + "range": { + "startPosition": {}, + "endPosition": { + "line": 50 + } + }, + "contents": "# Unified Farm Launcher\n\nA desktop application for managing remote render farm workers across multiple systems. Supports both **SheepIt** and **Flamenco** worker types with a unified interface for starting, stopping, and monitoring workers via SSH.\n\n## Features\n\n- **Multi-Worker Support**: Manage multiple remote workers from a single interface\n- **Worker Types**: \n - **[SheepIt](https://www.sheepit-renderfarm.com/)**: Distributed rendering for Blender projects\n - **[Flamenco](https://flamenco.blender.org/)**: Blender Foundation's render farm manager\n- **Remote Management**: Control workers on remote machines via SSH\n- **Worker Lifecycle**: Start, stop, and attach to worker processes\n- **Filtering**: Filter workers by type (All, SheepIt, Flamenco)\n- **Configuration Management**: Add, edit, and delete worker configurations\n- **Real-time Logging**: Attach to worker logs for real-time monitoring\n\n## Requirements\n\n- **.NET 8.0** or later\n- **Windows** (PowerShell required on remote machines)\n- **SSH access** to remote worker machines\n- **Java** (Temurin 21 recommended) on remote machines for SheepIt workers\n- **Flamenco worker executable** on remote machines for Flamenco workers\n\n## Building\n\n```bash\ndotnet build\n```\n\n## Running\n\n```bash\ndotnet run\n```\n\nOr build and run the executable from the output directory.\n\n## Configuration\n\nConfiguration is stored in `%LocalAppData%\\UnifiedFarmLauncher\\workers.json`. The application will create this file automatically on first run.\n\n### Configuration Structure\n\n```json\n{\n \"workers\": [\n {\n \"id\": 1,\n \"name\": \"worker-name\",", + "signatures": {}, + "detailedLines": [ + { + "text": "# Unified Farm Launcher", + "lineNumber": 1 + }, + { + "lineNumber": 2 + }, + { + "text": "A desktop application for managing remote render farm workers across multiple systems. Supports both **SheepIt** and **Flamenco** worker types with a unified interface for starting, stopping, and monitoring workers via SSH.", + "lineNumber": 3 + }, + { + "lineNumber": 4 + }, + { + "text": "## Features", + "lineNumber": 5 + }, + { + "lineNumber": 6 + }, + { + "text": "- **Multi-Worker Support**: Manage multiple remote workers from a single interface", + "lineNumber": 7 + }, + { + "text": "- **Worker Types**: ", + "lineNumber": 8 + }, + { + "text": " - **[SheepIt](https://www.sheepit-renderfarm.com/)**: Distributed rendering for Blender projects", + "lineNumber": 9 + }, + { + "text": " - **[Flamenco](https://flamenco.blender.org/)**: Blender Foundation's render farm manager", + "lineNumber": 10 + }, + { + "text": "- **Remote Management**: Control workers on remote machines via SSH", + "lineNumber": 11 + }, + { + "text": "- **Worker Lifecycle**: Start, stop, and attach to worker processes", + "lineNumber": 12 + }, + { + "text": "- **Filtering**: Filter workers by type (All, SheepIt, Flamenco)", + "lineNumber": 13 + }, + { + "text": "- **Configuration Management**: Add, edit, and delete worker configurations", + "lineNumber": 14 + }, + { + "text": "- **Real-time Logging**: Attach to worker logs for real-time monitoring", + "lineNumber": 15 + }, + { + "lineNumber": 16 + }, + { + "text": "## Requirements", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": "- **.NET 8.0** or later", + "lineNumber": 19 + }, + { + "text": "- **Windows** (PowerShell required on remote machines)", + "lineNumber": 20 + }, + { + "text": "- **SSH access** to remote worker machines", + "lineNumber": 21 + }, + { + "text": "- **Java** (Temurin 21 recommended) on remote machines for SheepIt workers", + "lineNumber": 22 + }, + { + "text": "- **Flamenco worker executable** on remote machines for Flamenco workers", + "lineNumber": 23 + }, + { + "lineNumber": 24 + }, + { + "text": "## Building", + "lineNumber": 25 + }, + { + "lineNumber": 26 + }, + { + "text": "```bash", + "lineNumber": 27 + }, + { + "text": "dotnet build", + "lineNumber": 28 + }, + { + "text": "```", + "lineNumber": 29 + }, + { + "lineNumber": 30 + }, + { + "text": "## Running", + "lineNumber": 31 + }, + { + "lineNumber": 32 + }, + { + "text": "```bash", + "lineNumber": 33 + }, + { + "text": "dotnet run", + "lineNumber": 34 + }, + { + "text": "```", + "lineNumber": 35 + }, + { + "lineNumber": 36 + }, + { + "text": "Or build and run the executable from the output directory.", + "lineNumber": 37 + }, + { + "lineNumber": 38 + }, + { + "text": "## Configuration", + "lineNumber": 39 + }, + { + "lineNumber": 40 + }, + { + "text": "Configuration is stored in `%LocalAppData%\\UnifiedFarmLauncher\\workers.json`. The application will create this file automatically on first run.", + "lineNumber": 41 + }, + { + "lineNumber": 42 + }, + { + "text": "### Configuration Structure", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": "```json", + "lineNumber": 45 + }, + { + "text": "{", + "lineNumber": 46 + }, + { + "text": " \"workers\": [", + "lineNumber": 47 + }, + { + "text": " {", + "lineNumber": 48 + }, + { + "text": " \"id\": 1,", + "lineNumber": 49 + }, + { + "text": " \"name\": \"worker-name\",", + "lineNumber": 50 + } + ] + }, + "score": 0.2874065637588501 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 138 + }, + "endPosition": { + "line": 187 + } + }, + "contents": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')\n Set-Location $sheepDir\n \n $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, \n '-ui', 'text', '--log-stdout', '--verbose', \n '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')\n \n try {{\n & java @javaArgs\n }}\n catch {{\n Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red\n Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow\n [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")\n throw\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public string GenerateFlamencoPayload(WorkerConfig worker)\n {\n if (worker.WorkerTypes.Flamenco == null)\n throw new InvalidOperationException(\"Worker does not have Flamenco configuration\");\n\n var config = _configService.Load();\n var flamenco = worker.WorkerTypes.Flamenco;\n var globalSettings = config.GlobalSettings;\n var workerPath = globalSettings.FlamencoWorkerPath.Replace(\"'\", \"''\");\n var drives = string.Join(\", \", Array.ConvertAll(flamenco.NetworkDrives.ToArray(), d => $\"'{d}'\"));\n var paths = string.Join(\", \", Array.ConvertAll(flamenco.NetworkPaths.ToArray(), p => $\"'{p.Replace(\"\\\\\", \"\\\\\\\\\")}'\"));\n\n return $@\"\n$ProgressPreference = 'SilentlyContinue'\n$ErrorActionPreference = 'Stop'\n\n# Write startup message to stderr so controller can capture it\n[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "signatures": {}, + "detailedLines": [ + { + "text": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')", + "lineNumber": 140 + }, + { + "text": " Set-Location $sheepDir", + "lineNumber": 141 + }, + { + "text": " ", + "lineNumber": 142 + }, + { + "text": " $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, ", + "lineNumber": 143 + }, + { + "text": " '-ui', 'text', '--log-stdout', '--verbose', ", + "lineNumber": 144 + }, + { + "text": " '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')", + "lineNumber": 145 + }, + { + "text": " ", + "lineNumber": 146 + }, + { + "text": " try {{", + "lineNumber": 147 + }, + { + "text": " & java @javaArgs", + "lineNumber": 148 + }, + { + "text": " }}", + "lineNumber": 149 + }, + { + "text": " catch {{", + "lineNumber": 150 + }, + { + "text": " Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red", + "lineNumber": 151 + }, + { + "text": " Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow", + "lineNumber": 152 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")", + "lineNumber": 153 + }, + { + "text": " throw", + "lineNumber": 154 + }, + { + "text": " }}", + "lineNumber": 155 + }, + { + "text": "}}", + "lineNumber": 156 + }, + { + "text": "catch {{", + "lineNumber": 157 + }, + { + "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)", + "lineNumber": 158 + }, + { + "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)", + "lineNumber": 159 + }, + { + "text": " Write-Host $errorMsg -ForegroundColor Red", + "lineNumber": 160 + }, + { + "text": " Write-Host $stackMsg -ForegroundColor DarkRed", + "lineNumber": 161 + }, + { + "text": " [Console]::Error.WriteLine($errorMsg)", + "lineNumber": 162 + }, + { + "text": " [Console]::Error.WriteLine($stackMsg)", + "lineNumber": 163 + }, + { + "text": " exit 1", + "lineNumber": 164 + }, + { + "text": "}}", + "lineNumber": 165 + }, + { + "text": "\";", + "lineNumber": 166 + }, + { + "text": " }", + "lineNumber": 167 + }, + { + "lineNumber": 168 + }, + { + "text": " public string GenerateFlamencoPayload(WorkerConfig worker)", + "lineNumber": 169 + }, + { + "text": " {", + "lineNumber": 170 + }, + { + "text": " if (worker.WorkerTypes.Flamenco == null)", + "lineNumber": 171 + }, + { + "text": " throw new InvalidOperationException(\"Worker does not have Flamenco configuration\");", + "lineNumber": 172 + }, + { + "lineNumber": 173 + }, + { + "text": " var config = _configService.Load();", + "lineNumber": 174 + }, + { + "text": " var flamenco = worker.WorkerTypes.Flamenco;", + "lineNumber": 175 + }, + { + "text": " var globalSettings = config.GlobalSettings;", + "lineNumber": 176 + }, + { + "text": " var workerPath = globalSettings.FlamencoWorkerPath.Replace(\"'\", \"''\");", + "lineNumber": 177 + }, + { + "text": " var drives = string.Join(\", \", Array.ConvertAll(flamenco.NetworkDrives.ToArray(), d => $\"'{d}'\"));", + "lineNumber": 178 + }, + { + "text": " var paths = string.Join(\", \", Array.ConvertAll(flamenco.NetworkPaths.ToArray(), p => $\"'{p.Replace(\"\\\\\", \"\\\\\\\\\")}'\"));", + "lineNumber": 179 + }, + { + "lineNumber": 180 + }, + { + "text": " return $@\"", + "lineNumber": 181 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 182 + }, + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 183 + }, + { + "lineNumber": 184 + }, + { + "text": "# Write startup message to stderr so controller can capture it", + "lineNumber": 185 + }, + { + "text": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "lineNumber": 186 + } + ] + }, + "score": 0.2836436927318573 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Scripts\\remote_worker_attach.ps1", + "range": { + "startPosition": { + "line": 119 + }, + "endPosition": { + "line": 168 + } + }, + "contents": "Write-Host \"\"\nWrite-Host \"========================================\" -ForegroundColor Cyan\nWrite-Host \"Attaching to $WorkerName ($WorkerType) logs.\" -ForegroundColor Cyan\nWrite-Host \"Log file: $($paths.Log)\" -ForegroundColor Gray\nWrite-Host \"========================================\" -ForegroundColor Cyan\n[Console]::Out.Flush()\n[Console]::Error.WriteLine(\"Attach session initialized\")\n\n# Show initial log content if available\nif (Test-Path $paths.Log) {\n $initialLines = Get-Content -Path $paths.Log -Tail 50 -ErrorAction SilentlyContinue\n if ($initialLines) {\n Write-Host \"--- Recent log output ---\" -ForegroundColor DarkGray\n $initialLines | ForEach-Object { Write-Host $_ }\n Write-Host \"--- End of initial output ---\" -ForegroundColor DarkGray\n } else {\n Write-Host \"Log file exists but is empty. Waiting for worker output...\" -ForegroundColor Yellow\n }\n} else {\n Write-Host \"Log file does not exist yet. Waiting for worker to start...\" -ForegroundColor Yellow\n}\n\nWrite-Host \"\"\nWrite-Host \"Type commands and press Enter. Type 'detach' to exit session.\" -ForegroundColor Yellow\nWrite-Host \"\"\n\n# Use Register-ObjectEvent for file system watcher, or simpler: just tail in background and poll\n$logJob = Start-Job -ScriptBlock {\n param($LogPath)\n if (Test-Path $LogPath) {\n Get-Content -Path $LogPath -Tail 0 -Wait -ErrorAction SilentlyContinue\n } else {\n while (-not (Test-Path $LogPath)) {\n Start-Sleep -Milliseconds 500\n }\n Get-Content -Path $LogPath -Tail 0 -Wait -ErrorAction SilentlyContinue\n }\n} -ArgumentList $paths.Log\n\ntry {\n while ($true) {\n # Check for new log output from background job (non-blocking)\n $logOutput = Receive-Job -Job $logJob -ErrorAction SilentlyContinue\n if ($logOutput) {\n $logOutput | ForEach-Object { Write-Host $_ }\n [Console]::Out.Flush()\n }\n\n # Use a timeout on Read-Host to allow periodic log checking", + "signatures": {}, + "detailedLines": [ + { + "text": "Write-Host \"\"", + "lineNumber": 120 + }, + { + "text": "Write-Host \"========================================\" -ForegroundColor Cyan", + "lineNumber": 121 + }, + { + "text": "Write-Host \"Attaching to $WorkerName ($WorkerType) logs.\" -ForegroundColor Cyan", + "lineNumber": 122 + }, + { + "text": "Write-Host \"Log file: $($paths.Log)\" -ForegroundColor Gray", + "lineNumber": 123 + }, + { + "text": "Write-Host \"========================================\" -ForegroundColor Cyan", + "lineNumber": 124 + }, + { + "text": "[Console]::Out.Flush()", + "lineNumber": 125 + }, + { + "text": "[Console]::Error.WriteLine(\"Attach session initialized\")", + "lineNumber": 126 + }, + { + "lineNumber": 127 + }, + { + "text": "# Show initial log content if available", + "lineNumber": 128 + }, + { + "text": "if (Test-Path $paths.Log) {", + "lineNumber": 129 + }, + { + "text": " $initialLines = Get-Content -Path $paths.Log -Tail 50 -ErrorAction SilentlyContinue", + "lineNumber": 130 + }, + { + "text": " if ($initialLines) {", + "lineNumber": 131 + }, + { + "text": " Write-Host \"--- Recent log output ---\" -ForegroundColor DarkGray", + "lineNumber": 132 + }, + { + "text": " $initialLines | ForEach-Object { Write-Host $_ }", + "lineNumber": 133 + }, + { + "text": " Write-Host \"--- End of initial output ---\" -ForegroundColor DarkGray", + "lineNumber": 134 + }, + { + "text": " } else {", + "lineNumber": 135 + }, + { + "text": " Write-Host \"Log file exists but is empty. Waiting for worker output...\" -ForegroundColor Yellow", + "lineNumber": 136 + }, + { + "text": " }", + "lineNumber": 137 + }, + { + "text": "} else {", + "lineNumber": 138 + }, + { + "text": " Write-Host \"Log file does not exist yet. Waiting for worker to start...\" -ForegroundColor Yellow", + "lineNumber": 139 + }, + { + "text": "}", + "lineNumber": 140 + }, + { + "lineNumber": 141 + }, + { + "text": "Write-Host \"\"", + "lineNumber": 142 + }, + { + "text": "Write-Host \"Type commands and press Enter. Type 'detach' to exit session.\" -ForegroundColor Yellow", + "lineNumber": 143 + }, + { + "text": "Write-Host \"\"", + "lineNumber": 144 + }, + { + "lineNumber": 145 + }, + { + "text": "# Use Register-ObjectEvent for file system watcher, or simpler: just tail in background and poll", + "lineNumber": 146 + }, + { + "text": "$logJob = Start-Job -ScriptBlock {", + "lineNumber": 147 + }, + { + "text": " param($LogPath)", + "lineNumber": 148 + }, + { + "text": " if (Test-Path $LogPath) {", + "lineNumber": 149 + }, + { + "text": " Get-Content -Path $LogPath -Tail 0 -Wait -ErrorAction SilentlyContinue", + "lineNumber": 150 + }, + { + "text": " } else {", + "lineNumber": 151 + }, + { + "text": " while (-not (Test-Path $LogPath)) {", + "lineNumber": 152 + }, + { + "text": " Start-Sleep -Milliseconds 500", + "lineNumber": 153 + }, + { + "text": " }", + "lineNumber": 154 + }, + { + "text": " Get-Content -Path $LogPath -Tail 0 -Wait -ErrorAction SilentlyContinue", + "lineNumber": 155 + }, + { + "text": " }", + "lineNumber": 156 + }, + { + "text": "} -ArgumentList $paths.Log", + "lineNumber": 157 + }, + { + "lineNumber": 158 + }, + { + "text": "try {", + "lineNumber": 159 + }, + { + "text": " while ($true) {", + "lineNumber": 160 + }, + { + "text": " # Check for new log output from background job (non-blocking)", + "lineNumber": 161 + }, + { + "text": " $logOutput = Receive-Job -Job $logJob -ErrorAction SilentlyContinue", + "lineNumber": 162 + }, + { + "text": " if ($logOutput) {", + "lineNumber": 163 + }, + { + "text": " $logOutput | ForEach-Object { Write-Host $_ }", + "lineNumber": 164 + }, + { + "text": " [Console]::Out.Flush()", + "lineNumber": 165 + }, + { + "text": " }", + "lineNumber": 166 + }, + { + "lineNumber": 167 + }, + { + "text": " # Use a timeout on Read-Host to allow periodic log checking", + "lineNumber": 168 + } + ] + }, + "score": 0.28258776664733887 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\AttachService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 46 + } + }, + "contents": "using System.Diagnostics;\nusing System.IO;\nusing System.Threading.Tasks;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class AttachService\n {\n private readonly SshService _sshService;\n private readonly WorkerControllerService _controllerService;\n\n public AttachService(SshService sshService, WorkerControllerService controllerService)\n {\n _sshService = sshService;\n _controllerService = controllerService;\n }\n\n public async Task AttachToWorkerAsync(WorkerConfig worker, string workerType, bool commandOnly = false, string? command = null)\n {\n await _controllerService.DeployAttachHelperAsync(worker);\n\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n var remoteHelper = Path.Combine(remoteBasePath, \"attach-helper.ps1\");\n\n var paramsBlock = $\"-WorkerName \\\"{worker.Name}\\\" -WorkerType \\\"{workerType}\\\"\";\n if (commandOnly)\n {\n paramsBlock += \" -CommandOnly\";\n }\n if (!string.IsNullOrEmpty(command))\n {\n paramsBlock += $\" -Command \\\"{command}\\\"\";\n }\n\n // Use Windows path format (backslashes) and ensure it's properly quoted\n // Add -NoExit to keep window open and ensure output is visible\n // Use -Command with & to properly invoke the script and avoid pipeline input issues\n var escapedHelper = remoteHelper.Replace(\"'\", \"''\");\n var remoteCmd = $\"powershell.exe -NoLogo -NoProfile -NoExit -ExecutionPolicy Bypass -Command \\\"& '{escapedHelper}' {paramsBlock}\\\"\";\n\n _sshService.StartInteractiveSsh(worker, remoteCmd);\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": "using System.Diagnostics;", + "lineNumber": 1 + }, + { + "text": "using System.IO;", + "lineNumber": 2 + }, + { + "text": "using System.Threading.Tasks;", + "lineNumber": 3 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 4 + }, + { + "lineNumber": 5 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 6 + }, + { + "text": "{", + "lineNumber": 7 + }, + { + "text": " public class AttachService", + "lineNumber": 8 + }, + { + "text": " {", + "lineNumber": 9 + }, + { + "text": " private readonly SshService _sshService;", + "lineNumber": 10 + }, + { + "text": " private readonly WorkerControllerService _controllerService;", + "lineNumber": 11 + }, + { + "lineNumber": 12 + }, + { + "text": " public AttachService(SshService sshService, WorkerControllerService controllerService)", + "lineNumber": 13 + }, + { + "text": " {", + "lineNumber": 14 + }, + { + "text": " _sshService = sshService;", + "lineNumber": 15 + }, + { + "text": " _controllerService = controllerService;", + "lineNumber": 16 + }, + { + "text": " }", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": " public async Task AttachToWorkerAsync(WorkerConfig worker, string workerType, bool commandOnly = false, string? command = null)", + "lineNumber": 19 + }, + { + "text": " {", + "lineNumber": 20 + }, + { + "text": " await _controllerService.DeployAttachHelperAsync(worker);", + "lineNumber": 21 + }, + { + "lineNumber": 22 + }, + { + "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);", + "lineNumber": 23 + }, + { + "text": " var remoteHelper = Path.Combine(remoteBasePath, \"attach-helper.ps1\");", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": " var paramsBlock = $\"-WorkerName \\\"{worker.Name}\\\" -WorkerType \\\"{workerType}\\\"\";", + "lineNumber": 26 + }, + { + "text": " if (commandOnly)", + "lineNumber": 27 + }, + { + "text": " {", + "lineNumber": 28 + }, + { + "text": " paramsBlock += \" -CommandOnly\";", + "lineNumber": 29 + }, + { + "text": " }", + "lineNumber": 30 + }, + { + "text": " if (!string.IsNullOrEmpty(command))", + "lineNumber": 31 + }, + { + "text": " {", + "lineNumber": 32 + }, + { + "text": " paramsBlock += $\" -Command \\\"{command}\\\"\";", + "lineNumber": 33 + }, + { + "text": " }", + "lineNumber": 34 + }, + { + "lineNumber": 35 + }, + { + "text": " // Use Windows path format (backslashes) and ensure it's properly quoted", + "lineNumber": 36 + }, + { + "text": " // Add -NoExit to keep window open and ensure output is visible", + "lineNumber": 37 + }, + { + "text": " // Use -Command with & to properly invoke the script and avoid pipeline input issues", + "lineNumber": 38 + }, + { + "text": " var escapedHelper = remoteHelper.Replace(\"'\", \"''\");", + "lineNumber": 39 + }, + { + "text": " var remoteCmd = $\"powershell.exe -NoLogo -NoProfile -NoExit -ExecutionPolicy Bypass -Command \\\"& '{escapedHelper}' {paramsBlock}\\\"\";", + "lineNumber": 40 + }, + { + "lineNumber": 41 + }, + { + "text": " _sshService.StartInteractiveSsh(worker, remoteCmd);", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "text": " }", + "lineNumber": 44 + }, + { + "text": "}", + "lineNumber": 45 + } + ] + }, + "score": 0.28152531385421753 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 257 + }, + "endPosition": { + "line": 301 + } + }, + "contents": " };\n\n var output = new StringBuilder();\n var error = new StringBuilder();\n\n process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };\n process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };\n\n process.Start();\n process.BeginOutputReadLine();\n process.BeginErrorReadLine();\n\n // Write script to stdin\n await process.StandardInput.WriteAsync(script);\n process.StandardInput.Close();\n\n await process.WaitForExitAsync();\n\n if (process.ExitCode != 0 && !interactive)\n {\n throw new InvalidOperationException($\"SSH script execution failed with exit code {process.ExitCode}: {error}\");\n }\n\n return output.ToString();\n }\n\n public async Task<string> GetWorkerBasePathAsync(WorkerConfig worker)\n {\n if (_workerBasePathCache.TryGetValue(worker.Name, out var cached))\n return cached;\n\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var scriptBlock = \"$ProgressPreference='SilentlyContinue'; [Environment]::GetFolderPath('LocalApplicationData')\";\n var encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock));\n var remoteCmd = $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {encoded}\";\n\n var output = await ExecuteRemoteCommandAsync(worker, remoteCmd);\n var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);\n var basePath = lines.LastOrDefault()?.Trim();\n\n if (string.IsNullOrEmpty(basePath))\n throw new InvalidOperationException($\"Unable to read LocalAppData path on {worker.Name}.\");\n\n var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");", + "signatures": {}, + "detailedLines": [ + { + "text": " };", + "lineNumber": 258 + }, + { + "lineNumber": 259 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 260 + }, + { + "text": " var error = new StringBuilder();", + "lineNumber": 261 + }, + { + "lineNumber": 262 + }, + { + "text": " process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };", + "lineNumber": 263 + }, + { + "text": " process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };", + "lineNumber": 264 + }, + { + "lineNumber": 265 + }, + { + "text": " process.Start();", + "lineNumber": 266 + }, + { + "text": " process.BeginOutputReadLine();", + "lineNumber": 267 + }, + { + "text": " process.BeginErrorReadLine();", + "lineNumber": 268 + }, + { + "lineNumber": 269 + }, + { + "text": " // Write script to stdin", + "lineNumber": 270 + }, + { + "text": " await process.StandardInput.WriteAsync(script);", + "lineNumber": 271 + }, + { + "text": " process.StandardInput.Close();", + "lineNumber": 272 + }, + { + "lineNumber": 273 + }, + { + "text": " await process.WaitForExitAsync();", + "lineNumber": 274 + }, + { + "lineNumber": 275 + }, + { + "text": " if (process.ExitCode != 0 && !interactive)", + "lineNumber": 276 + }, + { + "text": " {", + "lineNumber": 277 + }, + { + "text": " throw new InvalidOperationException($\"SSH script execution failed with exit code {process.ExitCode}: {error}\");", + "lineNumber": 278 + }, + { + "text": " }", + "lineNumber": 279 + }, + { + "lineNumber": 280 + }, + { + "text": " return output.ToString();", + "lineNumber": 281 + }, + { + "text": " }", + "lineNumber": 282 + }, + { + "lineNumber": 283 + }, + { + "text": " public async Task<string> GetWorkerBasePathAsync(WorkerConfig worker)", + "lineNumber": 284 + }, + { + "text": " {", + "lineNumber": 285 + }, + { + "text": " if (_workerBasePathCache.TryGetValue(worker.Name, out var cached))", + "lineNumber": 286 + }, + { + "text": " return cached;", + "lineNumber": 287 + }, + { + "lineNumber": 288 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 289 + }, + { + "text": " var scriptBlock = \"$ProgressPreference='SilentlyContinue'; [Environment]::GetFolderPath('LocalApplicationData')\";", + "lineNumber": 290 + }, + { + "text": " var encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock));", + "lineNumber": 291 + }, + { + "text": " var remoteCmd = $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {encoded}\";", + "lineNumber": 292 + }, + { + "lineNumber": 293 + }, + { + "text": " var output = await ExecuteRemoteCommandAsync(worker, remoteCmd);", + "lineNumber": 294 + }, + { + "text": " var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);", + "lineNumber": 295 + }, + { + "text": " var basePath = lines.LastOrDefault()?.Trim();", + "lineNumber": 296 + }, + { + "lineNumber": 297 + }, + { + "text": " if (string.IsNullOrEmpty(basePath))", + "lineNumber": 298 + }, + { + "text": " throw new InvalidOperationException($\"Unable to read LocalAppData path on {worker.Name}.\");", + "lineNumber": 299 + }, + { + "lineNumber": 300 + }, + { + "text": " var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");", + "lineNumber": 301 + } + ] + }, + "score": 0.2796224355697632 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\CHANGELOG.md", + "range": { + "startPosition": {}, + "endPosition": { + "line": 35 + } + }, + "contents": "# Changelog\n\nAll notable changes to the Unified Farm Launcher project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n### Added\n- README documentation\n- CHANGELOG file\n\n## [0.0.1] - 2025-11-21\n\n### Added\n- **Avalonia Desktop Application**: Complete migration from PowerShell scripts to a cross-platform desktop application\n- **Worker Management UI**: Graphical interface for managing multiple remote workers\n- **Multi-Worker Type Support**: Unified interface supporting both SheepIt and Flamenco worker types\n- **Worker Configuration Management**: Add, edit, and delete worker configurations through the UI\n- **Worker Filtering**: Filter workers by type (All, SheepIt, Flamenco) using tab controls\n- **Remote Worker Controller**: PowerShell-based controller script deployed to remote machines for process management\n- **Worker Lifecycle Management**: Start, stop, and monitor worker processes remotely\n- **Log Attachment Service**: Real-time log viewing by attaching to remote worker processes\n- **SSH Service**: Secure remote communication with worker machines\n- **Configuration Persistence**: JSON-based configuration stored in `%LocalAppData%\\UnifiedFarmLauncher\\workers.json`\n- **Worker Status Tracking**: Metadata tracking for worker state, PIDs, restart counts, and exit codes\n- **Automatic Restart Logic**: Configurable restart attempts with delay for failed workers\n- **Command Queue System**: File-based command queue for remote worker control\n- **Embedded PowerShell Scripts**: Controller and attach helper scripts embedded as resources\n\n### Changed\n- **Architecture**: Migrated from standalone PowerShell scripts to structured C# application with MVVM pattern\n- **Payload Handling**: Switched from command-line argument passing to file-based payload storage to avoid Windows command-line length limits\n- **Worker Process Management**: Implemented controller-based architecture for better process lifecycle management", + "signatures": {}, + "detailedLines": [ + { + "text": "# Changelog", + "lineNumber": 1 + }, + { + "lineNumber": 2 + }, + { + "text": "All notable changes to the Unified Farm Launcher project will be documented in this file.", + "lineNumber": 3 + }, + { + "lineNumber": 4 + }, + { + "text": "The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),", + "lineNumber": 5 + }, + { + "text": "and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).", + "lineNumber": 6 + }, + { + "lineNumber": 7 + }, + { + "text": "## [Unreleased]", + "lineNumber": 8 + }, + { + "lineNumber": 9 + }, + { + "text": "### Added", + "lineNumber": 10 + }, + { + "text": "- README documentation", + "lineNumber": 11 + }, + { + "text": "- CHANGELOG file", + "lineNumber": 12 + }, + { + "lineNumber": 13 + }, + { + "text": "## [0.0.1] - 2025-11-21", + "lineNumber": 14 + }, + { + "lineNumber": 15 + }, + { + "text": "### Added", + "lineNumber": 16 + }, + { + "text": "- **Avalonia Desktop Application**: Complete migration from PowerShell scripts to a cross-platform desktop application", + "lineNumber": 17 + }, + { + "text": "- **Worker Management UI**: Graphical interface for managing multiple remote workers", + "lineNumber": 18 + }, + { + "text": "- **Multi-Worker Type Support**: Unified interface supporting both SheepIt and Flamenco worker types", + "lineNumber": 19 + }, + { + "text": "- **Worker Configuration Management**: Add, edit, and delete worker configurations through the UI", + "lineNumber": 20 + }, + { + "text": "- **Worker Filtering**: Filter workers by type (All, SheepIt, Flamenco) using tab controls", + "lineNumber": 21 + }, + { + "text": "- **Remote Worker Controller**: PowerShell-based controller script deployed to remote machines for process management", + "lineNumber": 22 + }, + { + "text": "- **Worker Lifecycle Management**: Start, stop, and monitor worker processes remotely", + "lineNumber": 23 + }, + { + "text": "- **Log Attachment Service**: Real-time log viewing by attaching to remote worker processes", + "lineNumber": 24 + }, + { + "text": "- **SSH Service**: Secure remote communication with worker machines", + "lineNumber": 25 + }, + { + "text": "- **Configuration Persistence**: JSON-based configuration stored in `%LocalAppData%\\UnifiedFarmLauncher\\workers.json`", + "lineNumber": 26 + }, + { + "text": "- **Worker Status Tracking**: Metadata tracking for worker state, PIDs, restart counts, and exit codes", + "lineNumber": 27 + }, + { + "text": "- **Automatic Restart Logic**: Configurable restart attempts with delay for failed workers", + "lineNumber": 28 + }, + { + "text": "- **Command Queue System**: File-based command queue for remote worker control", + "lineNumber": 29 + }, + { + "text": "- **Embedded PowerShell Scripts**: Controller and attach helper scripts embedded as resources", + "lineNumber": 30 + }, + { + "lineNumber": 31 + }, + { + "text": "### Changed", + "lineNumber": 32 + }, + { + "text": "- **Architecture**: Migrated from standalone PowerShell scripts to structured C# application with MVVM pattern", + "lineNumber": 33 + }, + { + "text": "- **Payload Handling**: Switched from command-line argument passing to file-based payload storage to avoid Windows command-line length limits", + "lineNumber": 34 + }, + { + "text": "- **Worker Process Management**: Implemented controller-based architecture for better process lifecycle management", + "lineNumber": 35 + } + ] + }, + "score": 0.27744439244270325 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\README.md", + "range": { + "startPosition": { + "line": 41 + }, + "endPosition": { + "line": 128 + } + }, + "contents": "### Configuration Structure\n\n```json\n{\n \"workers\": [\n {\n \"id\": 1,\n \"name\": \"worker-name\",\n \"enabled\": true,\n \"ssh\": {\n \"host\": \"192.168.1.100\",\n \"port\": 22,\n \"username\": \"user\",\n \"password\": \"password\"\n },\n \"workerTypes\": {\n \"sheepIt\": {\n \"username\": \"sheepit-username\",\n \"renderKey\": \"render-key\",\n \"gpu\": \"CPU\"\n },\n \"flamenco\": {\n \"workerPath\": \"C:\\\\path\\\\to\\\\flamenco-worker\",\n \"networkDrives\": [\"Z:\", \"Y:\"],\n \"networkPaths\": [\"\\\\\\\\server\\\\share\"]\n }\n }\n }\n ],\n \"globalSettings\": {\n \"sheepItJarUrls\": [\n \"https://sheepit-renderfarm.com/media/applet/client-latest.php\"\n ]\n }\n}\n```\n\n## Usage\n\n### Adding a Worker\n\n1. Click **Add Worker** button\n2. Fill in the worker details:\n - **Name**: Unique identifier for the worker\n - **SSH Configuration**: Host, port, username, and password\n - **Worker Type Configuration**: Configure either SheepIt or Flamenco (or both)\n3. Click **Save**\n\n### Starting a Worker\n\n1. Select a worker from the list\n2. Click **Start** button\n3. The application will:\n - Deploy the controller script to the remote machine\n - Start the worker process\n - Monitor the worker status\n\n### Stopping a Worker\n\n1. Select a running worker\n2. Click **Stop** button\n3. The worker process will be gracefully terminated\n\n### Attaching to Worker Logs\n\n1. Select a worker\n2. Click **Attach** button\n3. A terminal session will open showing real-time worker logs\n4. Type `detach` to exit the session\n\n### Filtering Workers\n\nUse the tabs at the top to filter workers:\n- **All Workers**: Shows all configured workers\n- **SheepIt**: Shows only SheepIt workers\n- **Flamenco**: Shows only Flamenco workers\n\n## Architecture\n\n### Project Structure\n\n```\nUFL/\n├── Models/ # Data models (ConfigRoot, WorkerConfig, etc.)\n├── Services/ # Business logic services\n│ ├── ConfigService.cs # Configuration management", + "signatures": {}, + "detailedLines": [ + { + "text": "### Configuration Structure", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": "```json", + "lineNumber": 45 + }, + { + "text": "{", + "lineNumber": 46 + }, + { + "text": " \"workers\": [", + "lineNumber": 47 + }, + { + "text": " {", + "lineNumber": 48 + }, + { + "text": " \"id\": 1,", + "lineNumber": 49 + }, + { + "text": " \"name\": \"worker-name\",", + "lineNumber": 50 + }, + { + "text": " \"enabled\": true,", + "lineNumber": 51 + }, + { + "text": " \"ssh\": {", + "lineNumber": 52 + }, + { + "text": " \"host\": \"192.168.1.100\",", + "lineNumber": 53 + }, + { + "text": " \"port\": 22,", + "lineNumber": 54 + }, + { + "text": " \"username\": \"user\",", + "lineNumber": 55 + }, + { + "text": " \"password\": \"password\"", + "lineNumber": 56 + }, + { + "text": " },", + "lineNumber": 57 + }, + { + "text": " \"workerTypes\": {", + "lineNumber": 58 + }, + { + "text": " \"sheepIt\": {", + "lineNumber": 59 + }, + { + "text": " \"username\": \"sheepit-username\",", + "lineNumber": 60 + }, + { + "text": " \"renderKey\": \"render-key\",", + "lineNumber": 61 + }, + { + "text": " \"gpu\": \"CPU\"", + "lineNumber": 62 + }, + { + "text": " },", + "lineNumber": 63 + }, + { + "text": " \"flamenco\": {", + "lineNumber": 64 + }, + { + "text": " \"workerPath\": \"C:\\\\path\\\\to\\\\flamenco-worker\",", + "lineNumber": 65 + }, + { + "text": " \"networkDrives\": [\"Z:\", \"Y:\"],", + "lineNumber": 66 + }, + { + "text": " \"networkPaths\": [\"\\\\\\\\server\\\\share\"]", + "lineNumber": 67 + }, + { + "text": " }", + "lineNumber": 68 + }, + { + "text": " }", + "lineNumber": 69 + }, + { + "text": " }", + "lineNumber": 70 + }, + { + "text": " ],", + "lineNumber": 71 + }, + { + "text": " \"globalSettings\": {", + "lineNumber": 72 + }, + { + "text": " \"sheepItJarUrls\": [", + "lineNumber": 73 + }, + { + "text": " \"https://sheepit-renderfarm.com/media/applet/client-latest.php\"", + "lineNumber": 74 + }, + { + "text": " ]", + "lineNumber": 75 + }, + { + "text": " }", + "lineNumber": 76 + }, + { + "text": "}", + "lineNumber": 77 + }, + { + "text": "```", + "lineNumber": 78 + }, + { + "lineNumber": 79 + }, + { + "text": "## Usage", + "lineNumber": 80 + }, + { + "lineNumber": 81 + }, + { + "text": "### Adding a Worker", + "lineNumber": 82 + }, + { + "lineNumber": 83 + }, + { + "text": "1. Click **Add Worker** button", + "lineNumber": 84 + }, + { + "text": "2. Fill in the worker details:", + "lineNumber": 85 + }, + { + "text": " - **Name**: Unique identifier for the worker", + "lineNumber": 86 + }, + { + "text": " - **SSH Configuration**: Host, port, username, and password", + "lineNumber": 87 + }, + { + "text": " - **Worker Type Configuration**: Configure either SheepIt or Flamenco (or both)", + "lineNumber": 88 + }, + { + "text": "3. Click **Save**", + "lineNumber": 89 + }, + { + "lineNumber": 90 + }, + { + "text": "### Starting a Worker", + "lineNumber": 91 + }, + { + "lineNumber": 92 + }, + { + "text": "1. Select a worker from the list", + "lineNumber": 93 + }, + { + "text": "2. Click **Start** button", + "lineNumber": 94 + }, + { + "text": "3. The application will:", + "lineNumber": 95 + }, + { + "text": " - Deploy the controller script to the remote machine", + "lineNumber": 96 + }, + { + "text": " - Start the worker process", + "lineNumber": 97 + }, + { + "text": " - Monitor the worker status", + "lineNumber": 98 + }, + { + "lineNumber": 99 + }, + { + "text": "### Stopping a Worker", + "lineNumber": 100 + }, + { + "lineNumber": 101 + }, + { + "text": "1. Select a running worker", + "lineNumber": 102 + }, + { + "text": "2. Click **Stop** button", + "lineNumber": 103 + }, + { + "text": "3. The worker process will be gracefully terminated", + "lineNumber": 104 + }, + { + "lineNumber": 105 + }, + { + "text": "### Attaching to Worker Logs", + "lineNumber": 106 + }, + { + "lineNumber": 107 + }, + { + "text": "1. Select a worker", + "lineNumber": 108 + }, + { + "text": "2. Click **Attach** button", + "lineNumber": 109 + }, + { + "text": "3. A terminal session will open showing real-time worker logs", + "lineNumber": 110 + }, + { + "text": "4. Type `detach` to exit the session", + "lineNumber": 111 + }, + { + "lineNumber": 112 + }, + { + "text": "### Filtering Workers", + "lineNumber": 113 + }, + { + "lineNumber": 114 + }, + { + "text": "Use the tabs at the top to filter workers:", + "lineNumber": 115 + }, + { + "text": "- **All Workers**: Shows all configured workers", + "lineNumber": 116 + }, + { + "text": "- **SheepIt**: Shows only SheepIt workers", + "lineNumber": 117 + }, + { + "text": "- **Flamenco**: Shows only Flamenco workers", + "lineNumber": 118 + }, + { + "lineNumber": 119 + }, + { + "text": "## Architecture", + "lineNumber": 120 + }, + { + "lineNumber": 121 + }, + { + "text": "### Project Structure", + "lineNumber": 122 + }, + { + "lineNumber": 123 + }, + { + "text": "```", + "lineNumber": 124 + }, + { + "text": "UFL/", + "lineNumber": 125 + }, + { + "text": "├── Models/ # Data models (ConfigRoot, WorkerConfig, etc.)", + "lineNumber": 126 + }, + { + "text": "├── Services/ # Business logic services", + "lineNumber": 127 + }, + { + "text": "│ ├── ConfigService.cs # Configuration management", + "lineNumber": 128 + } + ] + }, + "score": 0.27570560574531555 + } + ] +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Scripts/remote_worker_controller.ps1 + +
+
+ +--- + + +
+Tool use: **ripgrep_raw_search** + + +Parameters: + +```json +{ + "pattern": "started successfully|Status|running|launching", + "path": ".", + "caseInsensitive": true +} +``` +Result: + +```json +{ + "success": { + "pattern": "started successfully|Status|running|launching", + "path": ".", + "outputMode": "content", + "workspaceResults": { + "c:\\Users\\Nathan\\Desktop\\UFL": { + "content": { + "matches": [ + { + "file": "ViewModels/MainWindowViewModel.cs", + "matches": [ + { + "lineNumber": 13, + "content": " private string _statusText = \"Ready\";" + }, + { + "lineNumber": 33, + "content": " UpdateStatusText();" + }, + { + "lineNumber": 38, + "content": " public string StatusText" + }, + { + "lineNumber": 40, + "content": " get => _statusText;" + }, + { + "lineNumber": 41, + "content": " set => SetAndRaise(ref _statusText, value);" + }, + { + "lineNumber": 98, + "content": " UpdateStatusText();" + }, + { + "lineNumber": 101, + "content": " private void UpdateStatusText()" + }, + { + "lineNumber": 105, + "content": " StatusText = $\"Total workers: {Workers.Count}\";" + }, + { + "lineNumber": 109, + "content": " StatusText = $\"Selected: {SelectedWorker.Name} ({SelectedWorker.Ssh.Host}:{SelectedWorker.Ssh.Port})\";" + } + ] + }, + { + "file": "README.md", + "matches": [ + { + "lineNumber": 31, + "content": "## Running" + }, + { + "lineNumber": 98, + "content": " - Monitor the worker status" + }, + { + "lineNumber": 102, + "content": "1. Select a running worker" + }, + { + "lineNumber": 146, + "content": " - `state\\worker-info.json`: Worker metadata and status" + }, + { + "lineNumber": 150, + "content": "3. **Worker Process**: The controller manages the worker process lifecycle, handles restarts, and monitors status." + } + ] + }, + { + "file": "Views\\MainWindow.axaml", + "matches": [ + { + "lineNumber": 72, + "content": " <!-- Status Bar -->" + }, + { + "lineNumber": 74, + "content": " <TextBlock Name=\"StatusText\" Text=\"{Binding StatusText}\" Margin=\"5\" VerticalAlignment=\"Center\"/>" + } + ] + }, + { + "file": "Services\\WorkerControllerService.cs", + "matches": [ + { + "lineNumber": 208, + "content": " Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green" + }, + { + "lineNumber": 285, + "content": " if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{" + }, + { + "lineNumber": 287, + "content": " Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"" + }, + { + "lineNumber": 300, + "content": " Status = 'launching'" + } + ] + }, + { + "file": "Services/WorkerControllerService.cs", + "matches": [ + { + "lineNumber": 208, + "content": " Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green" + }, + { + "lineNumber": 285, + "content": " if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{" + }, + { + "lineNumber": 287, + "content": " Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"" + }, + { + "lineNumber": 300, + "content": " Status = 'launching'" + } + ] + }, + { + "file": ".specstory/history/2025-12-17_22-32Z-readme-for-sheepit-worker-incompatibility-fix.md", + "matches": [ + { + "lineNumber": 197, + "content": "+ ## Running" + }, + { + "lineNumber": 264, + "content": "+ - Monitor the worker status" + }, + { + "lineNumber": 268, + "content": "+ 1. Select a running worker" + }, + { + "lineNumber": 312, + "content": "+ - &#96;state\\worker-info.json&#96;: Worker metadata and status" + }, + { + "lineNumber": 316, + "content": "+ 3. **Worker Process**: The controller manages the worker process lifecycle, handles restarts, and monitors status." + }, + { + "lineNumber": 346, + "content": "- **Requirements**: Prerequisites for running the application" + }, + { + "lineNumber": 347, + "content": "- **Building & Running**: Instructions for building and running" + }, + { + "lineNumber": 407, + "content": "| `history\\2025-05-05_16-17Z-flamenco-job-status-troubleshooting.md` |" + }, + { + "lineNumber": 525, + "content": "+ - **Worker Status Tracking**: Metadata tracking for worker state, PIDs, restart counts, and exit codes" + }, + { + "lineNumber": 543, + "content": "+ - **Process Monitoring**: Improved worker process status tracking and metadata updates" + }, + { + "lineNumber": 600, + "content": "+ - Flamenco job status troubleshooting" + } + ] + }, + { + "file": "Views/MainWindow.axaml", + "matches": [ + { + "lineNumber": 72, + "content": " <!-- Status Bar -->" + }, + { + "lineNumber": 74, + "content": " <TextBlock Name=\"StatusText\" Text=\"{Binding StatusText}\" Margin=\"5\" VerticalAlignment=\"Center\"/>" + } + ] + }, + { + "file": "ViewModels\\MainWindowViewModel.cs", + "matches": [ + { + "lineNumber": 13, + "content": " private string _statusText = \"Ready\";" + }, + { + "lineNumber": 33, + "content": " UpdateStatusText();" + }, + { + "lineNumber": 38, + "content": " public string StatusText" + }, + { + "lineNumber": 40, + "content": " get => _statusText;" + }, + { + "lineNumber": 41, + "content": " set => SetAndRaise(ref _statusText, value);" + }, + { + "lineNumber": 98, + "content": " UpdateStatusText();" + }, + { + "lineNumber": 101, + "content": " private void UpdateStatusText()" + }, + { + "lineNumber": 105, + "content": " StatusText = $\"Total workers: {Workers.Count}\";" + }, + { + "lineNumber": 109, + "content": " StatusText = $\"Selected: {SelectedWorker.Name} ({SelectedWorker.Ssh.Host}:{SelectedWorker.Ssh.Port})\";" + } + ] + }, + { + "file": "CHANGELOG.md", + "matches": [ + { + "lineNumber": 27, + "content": "- **Worker Status Tracking**: Metadata tracking for worker state, PIDs, restart counts, and exit codes" + }, + { + "lineNumber": 45, + "content": "- **Process Monitoring**: Improved worker process status tracking and metadata updates" + }, + { + "lineNumber": 102, + "content": "- Flamenco job status troubleshooting" + } + ] + }, + { + "file": "Scripts/remote_worker_controller.ps1", + "matches": [ + { + "lineNumber": 61, + "content": " Status = 'error'" + }, + { + "lineNumber": 186, + "content": " [string]$Status," + }, + { + "lineNumber": 196, + "content": " Status = $Status" + }, + { + "lineNumber": 227, + "content": " # record initial state before launching worker" + }, + { + "lineNumber": 228, + "content": " Write-Metadata -Status 'initializing' -WorkerPid $null -ControllerPid $PID -Restarts 0" + }, + { + "lineNumber": 283, + "content": " Write-Metadata -Status 'running' -WorkerPid $workerProcess.Id -ControllerPid $controllerPid -Restarts $restartCount" + }, + { + "lineNumber": 295, + "content": " Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode" + }, + { + "lineNumber": 323, + "content": " Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode" + }, + { + "lineNumber": 328, + "content": " Write-ControllerLog \"Worker process is running, entering monitoring loop\"" + }, + { + "lineNumber": 369, + "content": " Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode" + }, + { + "lineNumber": 389, + "content": " Write-Metadata -Status 'inactive' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount" + } + ] + } + ], + "totalLines": 60, + "totalMatchedLines": 60 + } + } + } + } +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Views/MainWindow.axaml.cs + +
+
+ +--- + + +The success message appears immediately after the setup script completes, not after the worker actually starts. Checking `StartWorkerAsync` to add proper verification: + + +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Scripts/remote_worker_controller.ps1 + +
+
+ +--- + + +The success message appears immediately after the setup script completes, before verifying the worker started. Adding verification by checking the worker status after a short delay: + + +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +
+Tool use: **semantic_search_full** + + +Parameters: + +```json +{ + "query": "How can we check the worker status from the metadata file to verify if a worker is actually running?", + "includePattern": "Services", + "topK": 10, + "explanation": "" +} +``` +Result: + +```json +{ + "codeResults": [ + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 270 + }, + "endPosition": { + "line": 320, + "column": 8 + } + }, + "contents": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}\nif (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}\n[IO.File]::WriteAllText($payloadBase64Path, $payloadBase64, [System.Text.Encoding]::UTF8)\n$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'\n$controllerPath = Join-Path $dataRoot 'controller.ps1'\n\nif (-not (Test-Path $controllerPath)) {{\n throw \"\"Controller missing at $controllerPath\"\"\n}}\n\n$shouldStart = $true\nif (Test-Path $metaPath) {{\n try {{\n $meta = Get-Content $metaPath -Raw | ConvertFrom-Json\n if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{\n if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{\n Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"\n $shouldStart = $false\n }}\n }}\n }} catch {{\n Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow\n }}\n}}\n\nif ($shouldStart) {{\n $initialMeta = [pscustomobject]@{{\n WorkerName = $workerName\n WorkerType = $workerType\n Status = 'launching'\n ControllerPid = $null\n WorkerPid = $null\n Restarts = 0\n LastExitCode = $null\n LogPath = $logPath\n CommandPath = $commandPath\n PayloadPath = $payloadPath\n UpdatedAtUtc = (Get-Date).ToUniversalTime()\n }} | ConvertTo-Json -Depth 5\n $initialMeta | Set-Content -Path $metaPath -Encoding UTF8\n\n $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue\n if ($pwsh) {{\n $psExe = $pwsh.Source\n }}\n else {{\n $psExe = (Get-Command powershell -ErrorAction Stop).Source\n }}\n\n $controllerArgs = @(\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}", + "lineNumber": 271 + }, + { + "text": "if (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "lineNumber": 272 + }, + { + "text": "[IO.File]::WriteAllText($payloadBase64Path, $payloadBase64, [System.Text.Encoding]::UTF8)", + "lineNumber": 273 + }, + { + "text": "$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'", + "lineNumber": 274 + }, + { + "text": "$controllerPath = Join-Path $dataRoot 'controller.ps1'", + "lineNumber": 275 + }, + { + "lineNumber": 276 + }, + { + "text": "if (-not (Test-Path $controllerPath)) {{", + "lineNumber": 277 + }, + { + "text": " throw \"\"Controller missing at $controllerPath\"\"", + "lineNumber": 278 + }, + { + "text": "}}", + "lineNumber": 279 + }, + { + "lineNumber": 280 + }, + { + "text": "$shouldStart = $true", + "lineNumber": 281 + }, + { + "text": "if (Test-Path $metaPath) {{", + "lineNumber": 282 + }, + { + "text": " try {{", + "lineNumber": 283 + }, + { + "text": " $meta = Get-Content $metaPath -Raw | ConvertFrom-Json", + "lineNumber": 284 + }, + { + "text": " if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{", + "lineNumber": 285 + }, + { + "text": " if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{", + "lineNumber": 286 + }, + { + "text": " Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"", + "lineNumber": 287 + }, + { + "text": " $shouldStart = $false", + "lineNumber": 288 + }, + { + "text": " }}", + "lineNumber": 289 + }, + { + "text": " }}", + "lineNumber": 290 + }, + { + "text": " }} catch {{", + "lineNumber": 291 + }, + { + "text": " Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow", + "lineNumber": 292 + }, + { + "text": " }}", + "lineNumber": 293 + }, + { + "text": "}}", + "lineNumber": 294 + }, + { + "lineNumber": 295 + }, + { + "text": "if ($shouldStart) {{", + "lineNumber": 296 + }, + { + "text": " $initialMeta = [pscustomobject]@{{", + "lineNumber": 297 + }, + { + "text": " WorkerName = $workerName", + "lineNumber": 298 + }, + { + "text": " WorkerType = $workerType", + "lineNumber": 299 + }, + { + "text": " Status = 'launching'", + "lineNumber": 300 + }, + { + "text": " ControllerPid = $null", + "lineNumber": 301 + }, + { + "text": " WorkerPid = $null", + "lineNumber": 302 + }, + { + "text": " Restarts = 0", + "lineNumber": 303 + }, + { + "text": " LastExitCode = $null", + "lineNumber": 304 + }, + { + "text": " LogPath = $logPath", + "lineNumber": 305 + }, + { + "text": " CommandPath = $commandPath", + "lineNumber": 306 + }, + { + "text": " PayloadPath = $payloadPath", + "lineNumber": 307 + }, + { + "text": " UpdatedAtUtc = (Get-Date).ToUniversalTime()", + "lineNumber": 308 + }, + { + "text": " }} | ConvertTo-Json -Depth 5", + "lineNumber": 309 + }, + { + "text": " $initialMeta | Set-Content -Path $metaPath -Encoding UTF8", + "lineNumber": 310 + }, + { + "lineNumber": 311 + }, + { + "text": " $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue", + "lineNumber": 312 + }, + { + "text": " if ($pwsh) {{", + "lineNumber": 313 + }, + { + "text": " $psExe = $pwsh.Source", + "lineNumber": 314 + }, + { + "text": " }}", + "lineNumber": 315 + }, + { + "text": " else {{", + "lineNumber": 316 + }, + { + "text": " $psExe = (Get-Command powershell -ErrorAction Stop).Source", + "lineNumber": 317 + }, + { + "text": " }}", + "lineNumber": 318 + }, + { + "lineNumber": 319 + }, + { + "text": " $controllerArgs = @(", + "lineNumber": 320 + }, + { + "text": " ", + "lineNumber": 321 + } + ] + }, + "score": 0.43647336959838867 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 227 + }, + "endPosition": { + "line": 272 + } + }, + "contents": "\";\n }\n\n public async Task StartWorkerAsync(WorkerConfig worker, string workerType)\n {\n await DeployControllerAsync(worker);\n\n string payloadScript;\n if (workerType == \"sheepit\")\n {\n payloadScript = GenerateSheepItPayload(worker);\n }\n else if (workerType == \"flamenco\")\n {\n payloadScript = GenerateFlamencoPayload(worker);\n }\n else\n {\n throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));\n }\n\n var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n\n var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";\n var jsonParamsBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(jsonParams));\n\n var ensureScript = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{jsonParamsBase64}')))\n$workerName = $params.WorkerName\n$workerType = $params.WorkerType\n$payloadBase64 = $params.PayloadBase64\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\n$instanceRoot = Join-Path (Join-Path $dataRoot $workerType) $workerName\n$logsRoot = Join-Path $instanceRoot 'logs'\n$stateRoot = Join-Path $instanceRoot 'state'\nNew-Item -ItemType Directory -Path $logsRoot -Force | Out-Null\nNew-Item -ItemType Directory -Path $stateRoot -Force | Out-Null\n$logPath = Join-Path $logsRoot 'worker.log'\n$commandPath = Join-Path $stateRoot 'commands.txt'\n$payloadPath = Join-Path $stateRoot 'payload.ps1'\n$payloadBase64Path = Join-Path $stateRoot 'payload.b64'\nif (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}\nif (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "signatures": {}, + "detailedLines": [ + { + "text": "\";", + "lineNumber": 228 + }, + { + "text": " }", + "lineNumber": 229 + }, + { + "lineNumber": 230 + }, + { + "text": " public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 231 + }, + { + "text": " {", + "lineNumber": 232 + }, + { + "text": " await DeployControllerAsync(worker);", + "lineNumber": 233 + }, + { + "lineNumber": 234 + }, + { + "text": " string payloadScript;", + "lineNumber": 235 + }, + { + "text": " if (workerType == \"sheepit\")", + "lineNumber": 236 + }, + { + "text": " {", + "lineNumber": 237 + }, + { + "text": " payloadScript = GenerateSheepItPayload(worker);", + "lineNumber": 238 + }, + { + "text": " }", + "lineNumber": 239 + }, + { + "text": " else if (workerType == \"flamenco\")", + "lineNumber": 240 + }, + { + "text": " {", + "lineNumber": 241 + }, + { + "text": " payloadScript = GenerateFlamencoPayload(worker);", + "lineNumber": 242 + }, + { + "text": " }", + "lineNumber": 243 + }, + { + "text": " else", + "lineNumber": 244 + }, + { + "text": " {", + "lineNumber": 245 + }, + { + "text": " throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));", + "lineNumber": 246 + }, + { + "text": " }", + "lineNumber": 247 + }, + { + "lineNumber": 248 + }, + { + "text": " var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));", + "lineNumber": 249 + }, + { + "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);", + "lineNumber": 250 + }, + { + "lineNumber": 251 + }, + { + "text": " var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";", + "lineNumber": 252 + }, + { + "text": " var jsonParamsBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(jsonParams));", + "lineNumber": 253 + }, + { + "lineNumber": 254 + }, + { + "text": " var ensureScript = $@\"", + "lineNumber": 255 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 256 + }, + { + "text": "$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{jsonParamsBase64}')))", + "lineNumber": 257 + }, + { + "text": "$workerName = $params.WorkerName", + "lineNumber": 258 + }, + { + "text": "$workerType = $params.WorkerType", + "lineNumber": 259 + }, + { + "text": "$payloadBase64 = $params.PayloadBase64", + "lineNumber": 260 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 261 + }, + { + "text": "$instanceRoot = Join-Path (Join-Path $dataRoot $workerType) $workerName", + "lineNumber": 262 + }, + { + "text": "$logsRoot = Join-Path $instanceRoot 'logs'", + "lineNumber": 263 + }, + { + "text": "$stateRoot = Join-Path $instanceRoot 'state'", + "lineNumber": 264 + }, + { + "text": "New-Item -ItemType Directory -Path $logsRoot -Force | Out-Null", + "lineNumber": 265 + }, + { + "text": "New-Item -ItemType Directory -Path $stateRoot -Force | Out-Null", + "lineNumber": 266 + }, + { + "text": "$logPath = Join-Path $logsRoot 'worker.log'", + "lineNumber": 267 + }, + { + "text": "$commandPath = Join-Path $stateRoot 'commands.txt'", + "lineNumber": 268 + }, + { + "text": "$payloadPath = Join-Path $stateRoot 'payload.ps1'", + "lineNumber": 269 + }, + { + "text": "$payloadBase64Path = Join-Path $stateRoot 'payload.b64'", + "lineNumber": 270 + }, + { + "text": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}", + "lineNumber": 271 + }, + { + "text": "if (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "lineNumber": 272 + } + ] + }, + "score": 0.257863312959671 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 317 + }, + "endPosition": { + "line": 352 + } + }, + "contents": " }}\n\n $controllerArgs = @(\n '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',\n '-File',\"\"$controllerPath\"\",\n '-WorkerName',\"\"$workerName\"\",\n '-WorkerType',\"\"$workerType\"\",\n '-PayloadBase64Path',\"\"$payloadBase64Path\"\"\n )\n\n Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden | Out-Null\n Write-Host \"\"Worker $workerName started under controller.\"\" -ForegroundColor Green\n}}\n\";\n\n // Pipe script through stdin to avoid command line length limits\n await _sshService.ExecuteRemoteScriptAsync(worker, ensureScript);\n }\n\n public async Task StopWorkerAsync(WorkerConfig worker, string workerType)\n {\n var script = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\n$instanceRoot = Join-Path (Join-Path $dataRoot '{workerType}') '{worker.Name}'\n$commandPath = Join-Path $instanceRoot 'state\\commands.txt'\n[IO.File]::WriteAllText($commandPath, 'quit', [System.Text.Encoding]::UTF8)\nWrite-Host \"\"Quit command sent to worker {worker.Name}.\"\"\n\";\n\n await _sshService.ExecuteRemoteCommandAsync(worker, $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {Convert.ToBase64String(Encoding.Unicode.GetBytes(script))}\");\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": " }}", + "lineNumber": 318 + }, + { + "lineNumber": 319 + }, + { + "text": " $controllerArgs = @(", + "lineNumber": 320 + }, + { + "text": " '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',", + "lineNumber": 321 + }, + { + "text": " '-File',\"\"$controllerPath\"\",", + "lineNumber": 322 + }, + { + "text": " '-WorkerName',\"\"$workerName\"\",", + "lineNumber": 323 + }, + { + "text": " '-WorkerType',\"\"$workerType\"\",", + "lineNumber": 324 + }, + { + "text": " '-PayloadBase64Path',\"\"$payloadBase64Path\"\"", + "lineNumber": 325 + }, + { + "text": " )", + "lineNumber": 326 + }, + { + "lineNumber": 327 + }, + { + "text": " Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden | Out-Null", + "lineNumber": 328 + }, + { + "text": " Write-Host \"\"Worker $workerName started under controller.\"\" -ForegroundColor Green", + "lineNumber": 329 + }, + { + "text": "}}", + "lineNumber": 330 + }, + { + "text": "\";", + "lineNumber": 331 + }, + { + "lineNumber": 332 + }, + { + "text": " // Pipe script through stdin to avoid command line length limits", + "lineNumber": 333 + }, + { + "text": " await _sshService.ExecuteRemoteScriptAsync(worker, ensureScript);", + "lineNumber": 334 + }, + { + "text": " }", + "lineNumber": 335 + }, + { + "lineNumber": 336 + }, + { + "text": " public async Task StopWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 337 + }, + { + "text": " {", + "lineNumber": 338 + }, + { + "text": " var script = $@\"", + "lineNumber": 339 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 340 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 341 + }, + { + "text": "$instanceRoot = Join-Path (Join-Path $dataRoot '{workerType}') '{worker.Name}'", + "lineNumber": 342 + }, + { + "text": "$commandPath = Join-Path $instanceRoot 'state\\commands.txt'", + "lineNumber": 343 + }, + { + "text": "[IO.File]::WriteAllText($commandPath, 'quit', [System.Text.Encoding]::UTF8)", + "lineNumber": 344 + }, + { + "text": "Write-Host \"\"Quit command sent to worker {worker.Name}.\"\"", + "lineNumber": 345 + }, + { + "text": "\";", + "lineNumber": 346 + }, + { + "lineNumber": 347 + }, + { + "text": " await _sshService.ExecuteRemoteCommandAsync(worker, $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {Convert.ToBase64String(Encoding.Unicode.GetBytes(script))}\");", + "lineNumber": 348 + }, + { + "text": " }", + "lineNumber": 349 + }, + { + "text": " }", + "lineNumber": 350 + }, + { + "text": "}", + "lineNumber": 351 + } + ] + }, + "score": 0.25419604778289795 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 185 + }, + "endPosition": { + "line": 231 + } + }, + "contents": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')\n\ntry {{\n Write-Host \"\"Setting up network connections...\"\" -ForegroundColor Cyan\n\n $drives = @({drives})\n $networkPaths = @({paths})\n\n # Disconnect all existing connections\n Write-Host \"\"Disconnecting existing network connections...\"\" -ForegroundColor Yellow\n foreach ($path in $networkPaths) {{ net use $path /delete /y 2>$null }}\n foreach ($drive in $drives) {{ net use $drive /delete /y 2>$null }}\n Write-Host \"\"All network connections cleared.\"\" -ForegroundColor Green\n\n # Connect to network shares (simplified - credentials should be stored securely)\n Write-Host \"\"Establishing network connections...\"\" -ForegroundColor Cyan\n # TODO: Add credential handling for network shares\n\n # Start worker\n Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan\n Set-Location '{workerPath}'\n if (Test-Path 'flamenco-worker.exe') {{\n Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green\n $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait\n $exitCode = $workerProcess.ExitCode\n Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow\n exit $exitCode\n }} else {{\n Write-Host \"\"Error: flamenco-worker.exe not found in {workerPath}\"\" -ForegroundColor Red\n [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {workerPath}\"\")\n exit 1\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "signatures": {}, + "detailedLines": [ + { + "text": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "lineNumber": 186 + }, + { + "lineNumber": 187 + }, + { + "text": "try {{", + "lineNumber": 188 + }, + { + "text": " Write-Host \"\"Setting up network connections...\"\" -ForegroundColor Cyan", + "lineNumber": 189 + }, + { + "lineNumber": 190 + }, + { + "text": " $drives = @({drives})", + "lineNumber": 191 + }, + { + "text": " $networkPaths = @({paths})", + "lineNumber": 192 + }, + { + "lineNumber": 193 + }, + { + "text": " # Disconnect all existing connections", + "lineNumber": 194 + }, + { + "text": " Write-Host \"\"Disconnecting existing network connections...\"\" -ForegroundColor Yellow", + "lineNumber": 195 + }, + { + "text": " foreach ($path in $networkPaths) {{ net use $path /delete /y 2>$null }}", + "lineNumber": 196 + }, + { + "text": " foreach ($drive in $drives) {{ net use $drive /delete /y 2>$null }}", + "lineNumber": 197 + }, + { + "text": " Write-Host \"\"All network connections cleared.\"\" -ForegroundColor Green", + "lineNumber": 198 + }, + { + "lineNumber": 199 + }, + { + "text": " # Connect to network shares (simplified - credentials should be stored securely)", + "lineNumber": 200 + }, + { + "text": " Write-Host \"\"Establishing network connections...\"\" -ForegroundColor Cyan", + "lineNumber": 201 + }, + { + "text": " # TODO: Add credential handling for network shares", + "lineNumber": 202 + }, + { + "lineNumber": 203 + }, + { + "text": " # Start worker", + "lineNumber": 204 + }, + { + "text": " Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan", + "lineNumber": 205 + }, + { + "text": " Set-Location '{workerPath}'", + "lineNumber": 206 + }, + { + "text": " if (Test-Path 'flamenco-worker.exe') {{", + "lineNumber": 207 + }, + { + "text": " Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green", + "lineNumber": 208 + }, + { + "text": " $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait", + "lineNumber": 209 + }, + { + "text": " $exitCode = $workerProcess.ExitCode", + "lineNumber": 210 + }, + { + "text": " Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow", + "lineNumber": 211 + }, + { + "text": " exit $exitCode", + "lineNumber": 212 + }, + { + "text": " }} else {{", + "lineNumber": 213 + }, + { + "text": " Write-Host \"\"Error: flamenco-worker.exe not found in {workerPath}\"\" -ForegroundColor Red", + "lineNumber": 214 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {workerPath}\"\")", + "lineNumber": 215 + }, + { + "text": " exit 1", + "lineNumber": 216 + }, + { + "text": " }}", + "lineNumber": 217 + }, + { + "text": "}}", + "lineNumber": 218 + }, + { + "text": "catch {{", + "lineNumber": 219 + }, + { + "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)", + "lineNumber": 220 + }, + { + "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)", + "lineNumber": 221 + }, + { + "text": " Write-Host $errorMsg -ForegroundColor Red", + "lineNumber": 222 + }, + { + "text": " Write-Host $stackMsg -ForegroundColor DarkRed", + "lineNumber": 223 + }, + { + "text": " [Console]::Error.WriteLine($errorMsg)", + "lineNumber": 224 + }, + { + "text": " [Console]::Error.WriteLine($stackMsg)", + "lineNumber": 225 + }, + { + "text": " exit 1", + "lineNumber": 226 + }, + { + "text": "}}", + "lineNumber": 227 + }, + { + "text": "\";", + "lineNumber": 228 + }, + { + "text": " }", + "lineNumber": 229 + }, + { + "lineNumber": 230 + }, + { + "text": " public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 231 + } + ] + }, + "score": 0.25235283374786377 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\ConfigService.cs", + "range": { + "startPosition": { + "line": 59 + }, + "endPosition": { + "line": 122, + "column": 12 + } + }, + "contents": " _config = config;\n\n try\n {\n var json = JsonSerializer.Serialize(config, JsonOptions);\n File.WriteAllText(_configPath, json);\n }\n catch (Exception ex)\n {\n throw new InvalidOperationException($\"Failed to save configuration to {_configPath}: {ex.Message}\", ex);\n }\n }\n\n public void Reload()\n {\n _config = null;\n Load();\n }\n\n public WorkerConfig? GetWorker(int id)\n {\n return Load().Workers.FirstOrDefault(w => w.Id == id);\n }\n\n public WorkerConfig? GetWorkerByName(string name)\n {\n return Load().Workers.FirstOrDefault(w => w.Name == name);\n }\n\n public void AddWorker(WorkerConfig worker)\n {\n var config = Load();\n if (config.Workers.Any(w => w.Id == worker.Id || w.Name == worker.Name))\n throw new InvalidOperationException($\"Worker with ID {worker.Id} or name '{worker.Name}' already exists\");\n\n config.Workers.Add(worker);\n Save(config);\n }\n\n public void UpdateWorker(WorkerConfig worker)\n {\n var config = Load();\n var index = config.Workers.FindIndex(w => w.Id == worker.Id);\n if (index < 0)\n throw new InvalidOperationException($\"Worker with ID {worker.Id} not found\");\n\n config.Workers[index] = worker;\n Save(config);\n }\n\n public void DeleteWorker(int id)\n {\n var config = Load();\n var worker = config.Workers.FirstOrDefault(w => w.Id == id);\n if (worker == null)\n throw new InvalidOperationException($\"Worker with ID {id} not found\");\n\n config.Workers.Remove(worker);\n Save(config);\n }\n\n public int GetNextWorkerId()\n {\n ", + "signatures": {}, + "detailedLines": [ + { + "text": " _config = config;", + "lineNumber": 60 + }, + { + "lineNumber": 61 + }, + { + "text": " try", + "lineNumber": 62 + }, + { + "text": " {", + "lineNumber": 63 + }, + { + "text": " var json = JsonSerializer.Serialize(config, JsonOptions);", + "lineNumber": 64 + }, + { + "text": " File.WriteAllText(_configPath, json);", + "lineNumber": 65 + }, + { + "text": " }", + "lineNumber": 66 + }, + { + "text": " catch (Exception ex)", + "lineNumber": 67 + }, + { + "text": " {", + "lineNumber": 68 + }, + { + "text": " throw new InvalidOperationException($\"Failed to save configuration to {_configPath}: {ex.Message}\", ex);", + "lineNumber": 69 + }, + { + "text": " }", + "lineNumber": 70 + }, + { + "text": " }", + "lineNumber": 71 + }, + { + "lineNumber": 72 + }, + { + "text": " public void Reload()", + "lineNumber": 73 + }, + { + "text": " {", + "lineNumber": 74 + }, + { + "text": " _config = null;", + "lineNumber": 75 + }, + { + "text": " Load();", + "lineNumber": 76 + }, + { + "text": " }", + "lineNumber": 77 + }, + { + "lineNumber": 78 + }, + { + "text": " public WorkerConfig? GetWorker(int id)", + "lineNumber": 79 + }, + { + "text": " {", + "lineNumber": 80 + }, + { + "text": " return Load().Workers.FirstOrDefault(w => w.Id == id);", + "lineNumber": 81 + }, + { + "text": " }", + "lineNumber": 82 + }, + { + "lineNumber": 83 + }, + { + "text": " public WorkerConfig? GetWorkerByName(string name)", + "lineNumber": 84 + }, + { + "text": " {", + "lineNumber": 85 + }, + { + "text": " return Load().Workers.FirstOrDefault(w => w.Name == name);", + "lineNumber": 86 + }, + { + "text": " }", + "lineNumber": 87 + }, + { + "lineNumber": 88 + }, + { + "text": " public void AddWorker(WorkerConfig worker)", + "lineNumber": 89 + }, + { + "text": " {", + "lineNumber": 90 + }, + { + "text": " var config = Load();", + "lineNumber": 91 + }, + { + "text": " if (config.Workers.Any(w => w.Id == worker.Id || w.Name == worker.Name))", + "lineNumber": 92 + }, + { + "text": " throw new InvalidOperationException($\"Worker with ID {worker.Id} or name '{worker.Name}' already exists\");", + "lineNumber": 93 + }, + { + "lineNumber": 94 + }, + { + "text": " config.Workers.Add(worker);", + "lineNumber": 95 + }, + { + "text": " Save(config);", + "lineNumber": 96 + }, + { + "text": " }", + "lineNumber": 97 + }, + { + "lineNumber": 98 + }, + { + "text": " public void UpdateWorker(WorkerConfig worker)", + "lineNumber": 99 + }, + { + "text": " {", + "lineNumber": 100 + }, + { + "text": " var config = Load();", + "lineNumber": 101 + }, + { + "text": " var index = config.Workers.FindIndex(w => w.Id == worker.Id);", + "lineNumber": 102 + }, + { + "text": " if (index < 0)", + "lineNumber": 103 + }, + { + "text": " throw new InvalidOperationException($\"Worker with ID {worker.Id} not found\");", + "lineNumber": 104 + }, + { + "lineNumber": 105 + }, + { + "text": " config.Workers[index] = worker;", + "lineNumber": 106 + }, + { + "text": " Save(config);", + "lineNumber": 107 + }, + { + "text": " }", + "lineNumber": 108 + }, + { + "lineNumber": 109 + }, + { + "text": " public void DeleteWorker(int id)", + "lineNumber": 110 + }, + { + "text": " {", + "lineNumber": 111 + }, + { + "text": " var config = Load();", + "lineNumber": 112 + }, + { + "text": " var worker = config.Workers.FirstOrDefault(w => w.Id == id);", + "lineNumber": 113 + }, + { + "text": " if (worker == null)", + "lineNumber": 114 + }, + { + "text": " throw new InvalidOperationException($\"Worker with ID {id} not found\");", + "lineNumber": 115 + }, + { + "lineNumber": 116 + }, + { + "text": " config.Workers.Remove(worker);", + "lineNumber": 117 + }, + { + "text": " Save(config);", + "lineNumber": 118 + }, + { + "text": " }", + "lineNumber": 119 + }, + { + "lineNumber": 120 + }, + { + "text": " public int GetNextWorkerId()", + "lineNumber": 121 + }, + { + "text": " {", + "lineNumber": 122 + }, + { + "text": " ", + "lineNumber": 123 + } + ] + }, + "score": 0.23712074756622314 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 52 + } + }, + "contents": "using System;\nusing System.IO;\nusing System.Reflection;\nusing System.Text;\nusing System.Threading.Tasks;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class WorkerControllerService\n {\n private readonly SshService _sshService;\n private readonly ConfigService _configService;\n private byte[]? _controllerScriptBytes;\n private byte[]? _attachHelperScriptBytes;\n\n public WorkerControllerService(SshService sshService, ConfigService configService)\n {\n _sshService = sshService;\n _configService = configService;\n }\n\n private byte[] GetControllerScriptBytes()\n {\n if (_controllerScriptBytes != null)\n return _controllerScriptBytes;\n\n var assembly = Assembly.GetExecutingAssembly();\n var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_controller.ps1\";\n\n using var stream = assembly.GetManifestResourceStream(resourceName);\n if (stream == null)\n throw new InvalidOperationException($\"Resource {resourceName} not found\");\n\n using var reader = new BinaryReader(stream);\n _controllerScriptBytes = reader.ReadBytes((int)stream.Length);\n return _controllerScriptBytes;\n }\n\n private byte[] GetAttachHelperScriptBytes()\n {\n if (_attachHelperScriptBytes != null)\n return _attachHelperScriptBytes;\n\n var assembly = Assembly.GetExecutingAssembly();\n var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_attach.ps1\";\n\n using var stream = assembly.GetManifestResourceStream(resourceName);\n if (stream == null)\n throw new InvalidOperationException($\"Resource {resourceName} not found\");\n\n using var reader = new BinaryReader(stream);", + "signatures": {}, + "detailedLines": [ + { + "text": "using System;", + "lineNumber": 1 + }, + { + "text": "using System.IO;", + "lineNumber": 2 + }, + { + "text": "using System.Reflection;", + "lineNumber": 3 + }, + { + "text": "using System.Text;", + "lineNumber": 4 + }, + { + "text": "using System.Threading.Tasks;", + "lineNumber": 5 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 6 + }, + { + "lineNumber": 7 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 8 + }, + { + "text": "{", + "lineNumber": 9 + }, + { + "text": " public class WorkerControllerService", + "lineNumber": 10 + }, + { + "text": " {", + "lineNumber": 11 + }, + { + "text": " private readonly SshService _sshService;", + "lineNumber": 12 + }, + { + "text": " private readonly ConfigService _configService;", + "lineNumber": 13 + }, + { + "text": " private byte[]? _controllerScriptBytes;", + "lineNumber": 14 + }, + { + "text": " private byte[]? _attachHelperScriptBytes;", + "lineNumber": 15 + }, + { + "lineNumber": 16 + }, + { + "text": " public WorkerControllerService(SshService sshService, ConfigService configService)", + "lineNumber": 17 + }, + { + "text": " {", + "lineNumber": 18 + }, + { + "text": " _sshService = sshService;", + "lineNumber": 19 + }, + { + "text": " _configService = configService;", + "lineNumber": 20 + }, + { + "text": " }", + "lineNumber": 21 + }, + { + "lineNumber": 22 + }, + { + "text": " private byte[] GetControllerScriptBytes()", + "lineNumber": 23 + }, + { + "text": " {", + "lineNumber": 24 + }, + { + "text": " if (_controllerScriptBytes != null)", + "lineNumber": 25 + }, + { + "text": " return _controllerScriptBytes;", + "lineNumber": 26 + }, + { + "lineNumber": 27 + }, + { + "text": " var assembly = Assembly.GetExecutingAssembly();", + "lineNumber": 28 + }, + { + "text": " var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_controller.ps1\";", + "lineNumber": 29 + }, + { + "lineNumber": 30 + }, + { + "text": " using var stream = assembly.GetManifestResourceStream(resourceName);", + "lineNumber": 31 + }, + { + "text": " if (stream == null)", + "lineNumber": 32 + }, + { + "text": " throw new InvalidOperationException($\"Resource {resourceName} not found\");", + "lineNumber": 33 + }, + { + "lineNumber": 34 + }, + { + "text": " using var reader = new BinaryReader(stream);", + "lineNumber": 35 + }, + { + "text": " _controllerScriptBytes = reader.ReadBytes((int)stream.Length);", + "lineNumber": 36 + }, + { + "text": " return _controllerScriptBytes;", + "lineNumber": 37 + }, + { + "text": " }", + "lineNumber": 38 + }, + { + "lineNumber": 39 + }, + { + "text": " private byte[] GetAttachHelperScriptBytes()", + "lineNumber": 40 + }, + { + "text": " {", + "lineNumber": 41 + }, + { + "text": " if (_attachHelperScriptBytes != null)", + "lineNumber": 42 + }, + { + "text": " return _attachHelperScriptBytes;", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": " var assembly = Assembly.GetExecutingAssembly();", + "lineNumber": 45 + }, + { + "text": " var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_attach.ps1\";", + "lineNumber": 46 + }, + { + "lineNumber": 47 + }, + { + "text": " using var stream = assembly.GetManifestResourceStream(resourceName);", + "lineNumber": 48 + }, + { + "text": " if (stream == null)", + "lineNumber": 49 + }, + { + "text": " throw new InvalidOperationException($\"Resource {resourceName} not found\");", + "lineNumber": 50 + }, + { + "lineNumber": 51 + }, + { + "text": " using var reader = new BinaryReader(stream);", + "lineNumber": 52 + } + ] + }, + "score": 0.2276131957769394 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\AttachService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 46 + } + }, + "contents": "using System.Diagnostics;\nusing System.IO;\nusing System.Threading.Tasks;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class AttachService\n {\n private readonly SshService _sshService;\n private readonly WorkerControllerService _controllerService;\n\n public AttachService(SshService sshService, WorkerControllerService controllerService)\n {\n _sshService = sshService;\n _controllerService = controllerService;\n }\n\n public async Task AttachToWorkerAsync(WorkerConfig worker, string workerType, bool commandOnly = false, string? command = null)\n {\n await _controllerService.DeployAttachHelperAsync(worker);\n\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n var remoteHelper = Path.Combine(remoteBasePath, \"attach-helper.ps1\");\n\n var paramsBlock = $\"-WorkerName \\\"{worker.Name}\\\" -WorkerType \\\"{workerType}\\\"\";\n if (commandOnly)\n {\n paramsBlock += \" -CommandOnly\";\n }\n if (!string.IsNullOrEmpty(command))\n {\n paramsBlock += $\" -Command \\\"{command}\\\"\";\n }\n\n // Use Windows path format (backslashes) and ensure it's properly quoted\n // Add -NoExit to keep window open and ensure output is visible\n // Use -Command with & to properly invoke the script and avoid pipeline input issues\n var escapedHelper = remoteHelper.Replace(\"'\", \"''\");\n var remoteCmd = $\"powershell.exe -NoLogo -NoProfile -NoExit -ExecutionPolicy Bypass -Command \\\"& '{escapedHelper}' {paramsBlock}\\\"\";\n\n _sshService.StartInteractiveSsh(worker, remoteCmd);\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": "using System.Diagnostics;", + "lineNumber": 1 + }, + { + "text": "using System.IO;", + "lineNumber": 2 + }, + { + "text": "using System.Threading.Tasks;", + "lineNumber": 3 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 4 + }, + { + "lineNumber": 5 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 6 + }, + { + "text": "{", + "lineNumber": 7 + }, + { + "text": " public class AttachService", + "lineNumber": 8 + }, + { + "text": " {", + "lineNumber": 9 + }, + { + "text": " private readonly SshService _sshService;", + "lineNumber": 10 + }, + { + "text": " private readonly WorkerControllerService _controllerService;", + "lineNumber": 11 + }, + { + "lineNumber": 12 + }, + { + "text": " public AttachService(SshService sshService, WorkerControllerService controllerService)", + "lineNumber": 13 + }, + { + "text": " {", + "lineNumber": 14 + }, + { + "text": " _sshService = sshService;", + "lineNumber": 15 + }, + { + "text": " _controllerService = controllerService;", + "lineNumber": 16 + }, + { + "text": " }", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": " public async Task AttachToWorkerAsync(WorkerConfig worker, string workerType, bool commandOnly = false, string? command = null)", + "lineNumber": 19 + }, + { + "text": " {", + "lineNumber": 20 + }, + { + "text": " await _controllerService.DeployAttachHelperAsync(worker);", + "lineNumber": 21 + }, + { + "lineNumber": 22 + }, + { + "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);", + "lineNumber": 23 + }, + { + "text": " var remoteHelper = Path.Combine(remoteBasePath, \"attach-helper.ps1\");", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": " var paramsBlock = $\"-WorkerName \\\"{worker.Name}\\\" -WorkerType \\\"{workerType}\\\"\";", + "lineNumber": 26 + }, + { + "text": " if (commandOnly)", + "lineNumber": 27 + }, + { + "text": " {", + "lineNumber": 28 + }, + { + "text": " paramsBlock += \" -CommandOnly\";", + "lineNumber": 29 + }, + { + "text": " }", + "lineNumber": 30 + }, + { + "text": " if (!string.IsNullOrEmpty(command))", + "lineNumber": 31 + }, + { + "text": " {", + "lineNumber": 32 + }, + { + "text": " paramsBlock += $\" -Command \\\"{command}\\\"\";", + "lineNumber": 33 + }, + { + "text": " }", + "lineNumber": 34 + }, + { + "lineNumber": 35 + }, + { + "text": " // Use Windows path format (backslashes) and ensure it's properly quoted", + "lineNumber": 36 + }, + { + "text": " // Add -NoExit to keep window open and ensure output is visible", + "lineNumber": 37 + }, + { + "text": " // Use -Command with & to properly invoke the script and avoid pipeline input issues", + "lineNumber": 38 + }, + { + "text": " var escapedHelper = remoteHelper.Replace(\"'\", \"''\");", + "lineNumber": 39 + }, + { + "text": " var remoteCmd = $\"powershell.exe -NoLogo -NoProfile -NoExit -ExecutionPolicy Bypass -Command \\\"& '{escapedHelper}' {paramsBlock}\\\"\";", + "lineNumber": 40 + }, + { + "lineNumber": 41 + }, + { + "text": " _sshService.StartInteractiveSsh(worker, remoteCmd);", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "text": " }", + "lineNumber": 44 + }, + { + "text": "}", + "lineNumber": 45 + } + ] + }, + "score": 0.22077703475952148 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 257 + }, + "endPosition": { + "line": 301 + } + }, + "contents": " };\n\n var output = new StringBuilder();\n var error = new StringBuilder();\n\n process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };\n process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };\n\n process.Start();\n process.BeginOutputReadLine();\n process.BeginErrorReadLine();\n\n // Write script to stdin\n await process.StandardInput.WriteAsync(script);\n process.StandardInput.Close();\n\n await process.WaitForExitAsync();\n\n if (process.ExitCode != 0 && !interactive)\n {\n throw new InvalidOperationException($\"SSH script execution failed with exit code {process.ExitCode}: {error}\");\n }\n\n return output.ToString();\n }\n\n public async Task<string> GetWorkerBasePathAsync(WorkerConfig worker)\n {\n if (_workerBasePathCache.TryGetValue(worker.Name, out var cached))\n return cached;\n\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var scriptBlock = \"$ProgressPreference='SilentlyContinue'; [Environment]::GetFolderPath('LocalApplicationData')\";\n var encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock));\n var remoteCmd = $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {encoded}\";\n\n var output = await ExecuteRemoteCommandAsync(worker, remoteCmd);\n var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);\n var basePath = lines.LastOrDefault()?.Trim();\n\n if (string.IsNullOrEmpty(basePath))\n throw new InvalidOperationException($\"Unable to read LocalAppData path on {worker.Name}.\");\n\n var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");", + "signatures": {}, + "detailedLines": [ + { + "text": " };", + "lineNumber": 258 + }, + { + "lineNumber": 259 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 260 + }, + { + "text": " var error = new StringBuilder();", + "lineNumber": 261 + }, + { + "lineNumber": 262 + }, + { + "text": " process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };", + "lineNumber": 263 + }, + { + "text": " process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };", + "lineNumber": 264 + }, + { + "lineNumber": 265 + }, + { + "text": " process.Start();", + "lineNumber": 266 + }, + { + "text": " process.BeginOutputReadLine();", + "lineNumber": 267 + }, + { + "text": " process.BeginErrorReadLine();", + "lineNumber": 268 + }, + { + "lineNumber": 269 + }, + { + "text": " // Write script to stdin", + "lineNumber": 270 + }, + { + "text": " await process.StandardInput.WriteAsync(script);", + "lineNumber": 271 + }, + { + "text": " process.StandardInput.Close();", + "lineNumber": 272 + }, + { + "lineNumber": 273 + }, + { + "text": " await process.WaitForExitAsync();", + "lineNumber": 274 + }, + { + "lineNumber": 275 + }, + { + "text": " if (process.ExitCode != 0 && !interactive)", + "lineNumber": 276 + }, + { + "text": " {", + "lineNumber": 277 + }, + { + "text": " throw new InvalidOperationException($\"SSH script execution failed with exit code {process.ExitCode}: {error}\");", + "lineNumber": 278 + }, + { + "text": " }", + "lineNumber": 279 + }, + { + "lineNumber": 280 + }, + { + "text": " return output.ToString();", + "lineNumber": 281 + }, + { + "text": " }", + "lineNumber": 282 + }, + { + "lineNumber": 283 + }, + { + "text": " public async Task<string> GetWorkerBasePathAsync(WorkerConfig worker)", + "lineNumber": 284 + }, + { + "text": " {", + "lineNumber": 285 + }, + { + "text": " if (_workerBasePathCache.TryGetValue(worker.Name, out var cached))", + "lineNumber": 286 + }, + { + "text": " return cached;", + "lineNumber": 287 + }, + { + "lineNumber": 288 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 289 + }, + { + "text": " var scriptBlock = \"$ProgressPreference='SilentlyContinue'; [Environment]::GetFolderPath('LocalApplicationData')\";", + "lineNumber": 290 + }, + { + "text": " var encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock));", + "lineNumber": 291 + }, + { + "text": " var remoteCmd = $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {encoded}\";", + "lineNumber": 292 + }, + { + "lineNumber": 293 + }, + { + "text": " var output = await ExecuteRemoteCommandAsync(worker, remoteCmd);", + "lineNumber": 294 + }, + { + "text": " var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);", + "lineNumber": 295 + }, + { + "text": " var basePath = lines.LastOrDefault()?.Trim();", + "lineNumber": 296 + }, + { + "lineNumber": 297 + }, + { + "text": " if (string.IsNullOrEmpty(basePath))", + "lineNumber": 298 + }, + { + "text": " throw new InvalidOperationException($\"Unable to read LocalAppData path on {worker.Name}.\");", + "lineNumber": 299 + }, + { + "lineNumber": 300 + }, + { + "text": " var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");", + "lineNumber": 301 + } + ] + }, + "score": 0.21907854080200195 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 88 + }, + "endPosition": { + "line": 140 + } + }, + "contents": " var config = _configService.Load();\n var sheepIt = worker.WorkerTypes.SheepIt;\n var globalSettings = config.GlobalSettings;\n var safeKey = globalSettings.SheepItRenderKey.Replace(\"'\", \"''\");\n var safeUser = globalSettings.SheepItUsername.Replace(\"'\", \"''\");\n var urls = globalSettings.SheepItJarUrls;\n var urlLiteral = \"@(\" + string.Join(\", \", Array.ConvertAll(urls.ToArray(), url => $\"'{url}'\")) + \")\";\n\n return $@\"\n$ProgressPreference = 'SilentlyContinue'\n$ErrorActionPreference = 'Stop'\n\n# Write startup message to stderr so controller can capture it\n[Console]::Error.WriteLine('[SHEEPIT] Payload script starting...')\n\ntry {{\n $appData = [Environment]::GetFolderPath('ApplicationData')\n $sheepDir = Join-Path $appData 'sheepit'\n if (-not (Test-Path $sheepDir)) {{\n New-Item -Path $sheepDir -ItemType Directory -Force | Out-Null\n }}\n\n $jarPath = Join-Path $sheepDir 'sheepit-client.jar'\n $urls = {urlLiteral}\n $headers = @{{ 'User-Agent' = 'Mozilla/5.0' }}\n\n if (Test-Path $jarPath) {{\n Write-Host \"\"SheepIt client already present at $jarPath. Skipping download.\"\" -ForegroundColor Green\n }}\n else {{\n $downloaded = $false\n\n foreach ($url in $urls) {{\n Write-Host \"\"Downloading SheepIt client from $url...\"\" -ForegroundColor Cyan\n try {{\n Invoke-WebRequest -Uri $url -OutFile $jarPath -UseBasicParsing -Headers $headers\n $downloaded = $true\n Write-Host \"\"Download complete.\"\" -ForegroundColor Green\n break\n }}\n catch {{\n Write-Host (\"\"Download failed from {{0}}: {{1}}\"\" -f $url, $_.Exception.Message) -ForegroundColor Yellow\n }}\n }}\n\n if (-not $downloaded) {{\n throw 'Unable to download SheepIt client from any known URL.'\n }}\n }}\n\n [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')", + "signatures": {}, + "detailedLines": [ + { + "text": " var config = _configService.Load();", + "lineNumber": 90 + }, + { + "text": " var sheepIt = worker.WorkerTypes.SheepIt;", + "lineNumber": 91 + }, + { + "text": " var globalSettings = config.GlobalSettings;", + "lineNumber": 92 + }, + { + "text": " var safeKey = globalSettings.SheepItRenderKey.Replace(\"'\", \"''\");", + "lineNumber": 93 + }, + { + "text": " var safeUser = globalSettings.SheepItUsername.Replace(\"'\", \"''\");", + "lineNumber": 94 + }, + { + "text": " var urls = globalSettings.SheepItJarUrls;", + "lineNumber": 95 + }, + { + "text": " var urlLiteral = \"@(\" + string.Join(\", \", Array.ConvertAll(urls.ToArray(), url => $\"'{url}'\")) + \")\";", + "lineNumber": 96 + }, + { + "lineNumber": 97 + }, + { + "text": " return $@\"", + "lineNumber": 98 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 99 + }, + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 100 + }, + { + "lineNumber": 101 + }, + { + "text": "# Write startup message to stderr so controller can capture it", + "lineNumber": 102 + }, + { + "text": "[Console]::Error.WriteLine('[SHEEPIT] Payload script starting...')", + "lineNumber": 103 + }, + { + "lineNumber": 104 + }, + { + "text": "try {{", + "lineNumber": 105 + }, + { + "text": " $appData = [Environment]::GetFolderPath('ApplicationData')", + "lineNumber": 106 + }, + { + "text": " $sheepDir = Join-Path $appData 'sheepit'", + "lineNumber": 107 + }, + { + "text": " if (-not (Test-Path $sheepDir)) {{", + "lineNumber": 108 + }, + { + "text": " New-Item -Path $sheepDir -ItemType Directory -Force | Out-Null", + "lineNumber": 109 + }, + { + "text": " }}", + "lineNumber": 110 + }, + { + "lineNumber": 111 + }, + { + "text": " $jarPath = Join-Path $sheepDir 'sheepit-client.jar'", + "lineNumber": 112 + }, + { + "text": " $urls = {urlLiteral}", + "lineNumber": 113 + }, + { + "text": " $headers = @{{ 'User-Agent' = 'Mozilla/5.0' }}", + "lineNumber": 114 + }, + { + "lineNumber": 115 + }, + { + "text": " if (Test-Path $jarPath) {{", + "lineNumber": 116 + }, + { + "text": " Write-Host \"\"SheepIt client already present at $jarPath. Skipping download.\"\" -ForegroundColor Green", + "lineNumber": 117 + }, + { + "text": " }}", + "lineNumber": 118 + }, + { + "text": " else {{", + "lineNumber": 119 + }, + { + "text": " $downloaded = $false", + "lineNumber": 120 + }, + { + "lineNumber": 121 + }, + { + "text": " foreach ($url in $urls) {{", + "lineNumber": 122 + }, + { + "text": " Write-Host \"\"Downloading SheepIt client from $url...\"\" -ForegroundColor Cyan", + "lineNumber": 123 + }, + { + "text": " try {{", + "lineNumber": 124 + }, + { + "text": " Invoke-WebRequest -Uri $url -OutFile $jarPath -UseBasicParsing -Headers $headers", + "lineNumber": 125 + }, + { + "text": " $downloaded = $true", + "lineNumber": 126 + }, + { + "text": " Write-Host \"\"Download complete.\"\" -ForegroundColor Green", + "lineNumber": 127 + }, + { + "text": " break", + "lineNumber": 128 + }, + { + "text": " }}", + "lineNumber": 129 + }, + { + "text": " catch {{", + "lineNumber": 130 + }, + { + "text": " Write-Host (\"\"Download failed from {{0}}: {{1}}\"\" -f $url, $_.Exception.Message) -ForegroundColor Yellow", + "lineNumber": 131 + }, + { + "text": " }}", + "lineNumber": 132 + }, + { + "text": " }}", + "lineNumber": 133 + }, + { + "lineNumber": 134 + }, + { + "text": " if (-not $downloaded) {{", + "lineNumber": 135 + }, + { + "text": " throw 'Unable to download SheepIt client from any known URL.'", + "lineNumber": 136 + }, + { + "text": " }}", + "lineNumber": 137 + }, + { + "text": " }}", + "lineNumber": 138 + }, + { + "lineNumber": 139 + }, + { + "text": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')", + "lineNumber": 140 + } + ] + }, + "score": 0.21901506185531616 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 138 + }, + "endPosition": { + "line": 187 + } + }, + "contents": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')\n Set-Location $sheepDir\n \n $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, \n '-ui', 'text', '--log-stdout', '--verbose', \n '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')\n \n try {{\n & java @javaArgs\n }}\n catch {{\n Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red\n Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow\n [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")\n throw\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public string GenerateFlamencoPayload(WorkerConfig worker)\n {\n if (worker.WorkerTypes.Flamenco == null)\n throw new InvalidOperationException(\"Worker does not have Flamenco configuration\");\n\n var config = _configService.Load();\n var flamenco = worker.WorkerTypes.Flamenco;\n var globalSettings = config.GlobalSettings;\n var workerPath = globalSettings.FlamencoWorkerPath.Replace(\"'\", \"''\");\n var drives = string.Join(\", \", Array.ConvertAll(flamenco.NetworkDrives.ToArray(), d => $\"'{d}'\"));\n var paths = string.Join(\", \", Array.ConvertAll(flamenco.NetworkPaths.ToArray(), p => $\"'{p.Replace(\"\\\\\", \"\\\\\\\\\")}'\"));\n\n return $@\"\n$ProgressPreference = 'SilentlyContinue'\n$ErrorActionPreference = 'Stop'\n\n# Write startup message to stderr so controller can capture it\n[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "signatures": {}, + "detailedLines": [ + { + "text": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')", + "lineNumber": 140 + }, + { + "text": " Set-Location $sheepDir", + "lineNumber": 141 + }, + { + "text": " ", + "lineNumber": 142 + }, + { + "text": " $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, ", + "lineNumber": 143 + }, + { + "text": " '-ui', 'text', '--log-stdout', '--verbose', ", + "lineNumber": 144 + }, + { + "text": " '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')", + "lineNumber": 145 + }, + { + "text": " ", + "lineNumber": 146 + }, + { + "text": " try {{", + "lineNumber": 147 + }, + { + "text": " & java @javaArgs", + "lineNumber": 148 + }, + { + "text": " }}", + "lineNumber": 149 + }, + { + "text": " catch {{", + "lineNumber": 150 + }, + { + "text": " Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red", + "lineNumber": 151 + }, + { + "text": " Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow", + "lineNumber": 152 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")", + "lineNumber": 153 + }, + { + "text": " throw", + "lineNumber": 154 + }, + { + "text": " }}", + "lineNumber": 155 + }, + { + "text": "}}", + "lineNumber": 156 + }, + { + "text": "catch {{", + "lineNumber": 157 + }, + { + "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)", + "lineNumber": 158 + }, + { + "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)", + "lineNumber": 159 + }, + { + "text": " Write-Host $errorMsg -ForegroundColor Red", + "lineNumber": 160 + }, + { + "text": " Write-Host $stackMsg -ForegroundColor DarkRed", + "lineNumber": 161 + }, + { + "text": " [Console]::Error.WriteLine($errorMsg)", + "lineNumber": 162 + }, + { + "text": " [Console]::Error.WriteLine($stackMsg)", + "lineNumber": 163 + }, + { + "text": " exit 1", + "lineNumber": 164 + }, + { + "text": "}}", + "lineNumber": 165 + }, + { + "text": "\";", + "lineNumber": 166 + }, + { + "text": " }", + "lineNumber": 167 + }, + { + "lineNumber": 168 + }, + { + "text": " public string GenerateFlamencoPayload(WorkerConfig worker)", + "lineNumber": 169 + }, + { + "text": " {", + "lineNumber": 170 + }, + { + "text": " if (worker.WorkerTypes.Flamenco == null)", + "lineNumber": 171 + }, + { + "text": " throw new InvalidOperationException(\"Worker does not have Flamenco configuration\");", + "lineNumber": 172 + }, + { + "lineNumber": 173 + }, + { + "text": " var config = _configService.Load();", + "lineNumber": 174 + }, + { + "text": " var flamenco = worker.WorkerTypes.Flamenco;", + "lineNumber": 175 + }, + { + "text": " var globalSettings = config.GlobalSettings;", + "lineNumber": 176 + }, + { + "text": " var workerPath = globalSettings.FlamencoWorkerPath.Replace(\"'\", \"''\");", + "lineNumber": 177 + }, + { + "text": " var drives = string.Join(\", \", Array.ConvertAll(flamenco.NetworkDrives.ToArray(), d => $\"'{d}'\"));", + "lineNumber": 178 + }, + { + "text": " var paths = string.Join(\", \", Array.ConvertAll(flamenco.NetworkPaths.ToArray(), p => $\"'{p.Replace(\"\\\\\", \"\\\\\\\\\")}'\"));", + "lineNumber": 179 + }, + { + "lineNumber": 180 + }, + { + "text": " return $@\"", + "lineNumber": 181 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 182 + }, + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 183 + }, + { + "lineNumber": 184 + }, + { + "text": "# Write startup message to stderr so controller can capture it", + "lineNumber": 185 + }, + { + "text": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "lineNumber": 186 + } + ] + }, + "score": 0.207989901304245 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 299 + }, + "endPosition": { + "line": 345, + "column": 16 + } + }, + "contents": " var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");\n _workerBasePathCache[worker.Name] = finalPath;\n return finalPath;\n }\n\n public async Task CopyFileToRemoteAsync(WorkerConfig worker, string localPath, string remotePath)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var scpArgs = BuildScpArgs(parts);\n scpArgs.Add(localPath);\n scpArgs.Add($\"{parts.Host}:\\\"{remotePath.Replace(\"\\\\\", \"/\")}\\\"\");\n\n var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetScpExecutable(),\n Arguments = string.Join(\" \", scpArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\")),\n UseShellExecute = false,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = true\n }\n };\n\n process.Start();\n await process.WaitForExitAsync();\n\n if (process.ExitCode != 0)\n {\n var error = await process.StandardError.ReadToEndAsync();\n throw new InvalidOperationException($\"SCP failed with exit code {process.ExitCode}: {error}\");\n }\n }\n\n public Process StartInteractiveSsh(WorkerConfig worker, string command)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var sshArgs = BuildSshArgs(parts, true);\n sshArgs.Add(command);\n\n var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));\n\n var process = new Process\n {\n ", + "signatures": {}, + "detailedLines": [ + { + "text": " var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");", + "lineNumber": 301 + }, + { + "text": " _workerBasePathCache[worker.Name] = finalPath;", + "lineNumber": 302 + }, + { + "text": " return finalPath;", + "lineNumber": 303 + }, + { + "text": " }", + "lineNumber": 304 + }, + { + "lineNumber": 305 + }, + { + "text": " public async Task CopyFileToRemoteAsync(WorkerConfig worker, string localPath, string remotePath)", + "lineNumber": 306 + }, + { + "text": " {", + "lineNumber": 307 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 308 + }, + { + "text": " var scpArgs = BuildScpArgs(parts);", + "lineNumber": 309 + }, + { + "text": " scpArgs.Add(localPath);", + "lineNumber": 310 + }, + { + "text": " scpArgs.Add($\"{parts.Host}:\\\"{remotePath.Replace(\"\\\\\", \"/\")}\\\"\");", + "lineNumber": 311 + }, + { + "lineNumber": 312 + }, + { + "text": " var process = new Process", + "lineNumber": 313 + }, + { + "text": " {", + "lineNumber": 314 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 315 + }, + { + "text": " {", + "lineNumber": 316 + }, + { + "text": " FileName = GetScpExecutable(),", + "lineNumber": 317 + }, + { + "text": " Arguments = string.Join(\" \", scpArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\")),", + "lineNumber": 318 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 319 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 320 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 321 + }, + { + "text": " CreateNoWindow = true", + "lineNumber": 322 + }, + { + "text": " }", + "lineNumber": 323 + }, + { + "text": " };", + "lineNumber": 324 + }, + { + "lineNumber": 325 + }, + { + "text": " process.Start();", + "lineNumber": 326 + }, + { + "text": " await process.WaitForExitAsync();", + "lineNumber": 327 + }, + { + "lineNumber": 328 + }, + { + "text": " if (process.ExitCode != 0)", + "lineNumber": 329 + }, + { + "text": " {", + "lineNumber": 330 + }, + { + "text": " var error = await process.StandardError.ReadToEndAsync();", + "lineNumber": 331 + }, + { + "text": " throw new InvalidOperationException($\"SCP failed with exit code {process.ExitCode}: {error}\");", + "lineNumber": 332 + }, + { + "text": " }", + "lineNumber": 333 + }, + { + "text": " }", + "lineNumber": 334 + }, + { + "lineNumber": 335 + }, + { + "text": " public Process StartInteractiveSsh(WorkerConfig worker, string command)", + "lineNumber": 336 + }, + { + "text": " {", + "lineNumber": 337 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 338 + }, + { + "text": " var sshArgs = BuildSshArgs(parts, true);", + "lineNumber": 339 + }, + { + "text": " sshArgs.Add(command);", + "lineNumber": 340 + }, + { + "lineNumber": 341 + }, + { + "text": " var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));", + "lineNumber": 342 + }, + { + "lineNumber": 343 + }, + { + "text": " var process = new Process", + "lineNumber": 344 + }, + { + "text": " {", + "lineNumber": 345 + }, + { + "text": " ", + "lineNumber": 346 + } + ] + }, + "score": 0.18873730301856995 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 150 + }, + "endPosition": { + "line": 216 + } + }, + "contents": " \"-o\", \"ServerAliveCountMax=30\"\n };\n\n if (interactive && parts.RequestPty)\n {\n args.Add(\"-t\");\n }\n else if (!interactive)\n {\n args.Add(\"-T\");\n }\n\n args.AddRange(parts.Options);\n\n if (parts.Port.HasValue)\n {\n args.Add(\"-p\");\n args.Add(parts.Port.Value.ToString());\n }\n\n args.Add(parts.Host);\n return args;\n }\n\n public List<string> BuildScpArgs(SshConnectionParts parts)\n {\n var args = new List<string>\n {\n \"-o\", \"ServerAliveInterval=60\",\n \"-o\", \"ServerAliveCountMax=30\"\n };\n\n args.AddRange(parts.Options);\n\n if (parts.Port.HasValue)\n {\n args.Add(\"-P\");\n args.Add(parts.Port.Value.ToString());\n }\n\n return args;\n }\n\n public async Task<string> ExecuteRemoteCommandAsync(WorkerConfig worker, string command, bool interactive = false)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var sshArgs = BuildSshArgs(parts, interactive);\n sshArgs.Add(command);\n\n var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));\n \n var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetSshExecutable(),\n WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),\n Arguments = argsString,\n UseShellExecute = false,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = !interactive\n }\n };\n\n var output = new StringBuilder();", + "signatures": {}, + "detailedLines": [ + { + "text": " \"-o\", \"ServerAliveCountMax=30\"", + "lineNumber": 151 + }, + { + "text": " };", + "lineNumber": 152 + }, + { + "lineNumber": 153 + }, + { + "text": " if (interactive && parts.RequestPty)", + "lineNumber": 154 + }, + { + "text": " {", + "lineNumber": 155 + }, + { + "text": " args.Add(\"-t\");", + "lineNumber": 156 + }, + { + "text": " }", + "lineNumber": 157 + }, + { + "text": " else if (!interactive)", + "lineNumber": 158 + }, + { + "text": " {", + "lineNumber": 159 + }, + { + "text": " args.Add(\"-T\");", + "lineNumber": 160 + }, + { + "text": " }", + "lineNumber": 161 + }, + { + "lineNumber": 162 + }, + { + "text": " args.AddRange(parts.Options);", + "lineNumber": 163 + }, + { + "lineNumber": 164 + }, + { + "text": " if (parts.Port.HasValue)", + "lineNumber": 165 + }, + { + "text": " {", + "lineNumber": 166 + }, + { + "text": " args.Add(\"-p\");", + "lineNumber": 167 + }, + { + "text": " args.Add(parts.Port.Value.ToString());", + "lineNumber": 168 + }, + { + "text": " }", + "lineNumber": 169 + }, + { + "lineNumber": 170 + }, + { + "text": " args.Add(parts.Host);", + "lineNumber": 171 + }, + { + "text": " return args;", + "lineNumber": 172 + }, + { + "text": " }", + "lineNumber": 173 + }, + { + "lineNumber": 174 + }, + { + "text": " public List<string> BuildScpArgs(SshConnectionParts parts)", + "lineNumber": 175 + }, + { + "text": " {", + "lineNumber": 176 + }, + { + "text": " var args = new List<string>", + "lineNumber": 177 + }, + { + "text": " {", + "lineNumber": 178 + }, + { + "text": " \"-o\", \"ServerAliveInterval=60\",", + "lineNumber": 179 + }, + { + "text": " \"-o\", \"ServerAliveCountMax=30\"", + "lineNumber": 180 + }, + { + "text": " };", + "lineNumber": 181 + }, + { + "lineNumber": 182 + }, + { + "text": " args.AddRange(parts.Options);", + "lineNumber": 183 + }, + { + "lineNumber": 184 + }, + { + "text": " if (parts.Port.HasValue)", + "lineNumber": 185 + }, + { + "text": " {", + "lineNumber": 186 + }, + { + "text": " args.Add(\"-P\");", + "lineNumber": 187 + }, + { + "text": " args.Add(parts.Port.Value.ToString());", + "lineNumber": 188 + }, + { + "text": " }", + "lineNumber": 189 + }, + { + "lineNumber": 190 + }, + { + "text": " return args;", + "lineNumber": 191 + }, + { + "text": " }", + "lineNumber": 192 + }, + { + "lineNumber": 193 + }, + { + "text": " public async Task<string> ExecuteRemoteCommandAsync(WorkerConfig worker, string command, bool interactive = false)", + "lineNumber": 194 + }, + { + "text": " {", + "lineNumber": 195 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 196 + }, + { + "text": " var sshArgs = BuildSshArgs(parts, interactive);", + "lineNumber": 197 + }, + { + "text": " sshArgs.Add(command);", + "lineNumber": 198 + }, + { + "lineNumber": 199 + }, + { + "text": " var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));", + "lineNumber": 200 + }, + { + "text": " ", + "lineNumber": 201 + }, + { + "text": " var process = new Process", + "lineNumber": 202 + }, + { + "text": " {", + "lineNumber": 203 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 204 + }, + { + "text": " {", + "lineNumber": 205 + }, + { + "text": " FileName = GetSshExecutable(),", + "lineNumber": 206 + }, + { + "text": " WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),", + "lineNumber": 207 + }, + { + "text": " Arguments = argsString,", + "lineNumber": 208 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 209 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 210 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 211 + }, + { + "text": " CreateNoWindow = !interactive", + "lineNumber": 212 + }, + { + "text": " }", + "lineNumber": 213 + }, + { + "text": " };", + "lineNumber": 214 + }, + { + "lineNumber": 215 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 216 + } + ] + }, + "score": 0.18746733665466309 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 49, + "column": 16 + }, + "endPosition": { + "line": 90 + } + }, + "contents": "throw new InvalidOperationException($\"Resource {resourceName} not found\");\n\n using var reader = new BinaryReader(stream);\n _attachHelperScriptBytes = reader.ReadBytes((int)stream.Length);\n return _attachHelperScriptBytes;\n }\n\n public async Task DeployControllerAsync(WorkerConfig worker)\n {\n var controllerBase64 = Convert.ToBase64String(GetControllerScriptBytes());\n var script = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\nNew-Item -ItemType Directory -Path $dataRoot -Force | Out-Null\n$controllerPath = Join-Path $dataRoot 'controller.ps1'\n[IO.File]::WriteAllBytes($controllerPath, [Convert]::FromBase64String('{controllerBase64}'))\n\";\n\n await _sshService.ExecuteRemoteScriptAsync(worker, script);\n }\n\n public async Task DeployAttachHelperAsync(WorkerConfig worker)\n {\n var helperBase64 = Convert.ToBase64String(GetAttachHelperScriptBytes());\n var script = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\nNew-Item -ItemType Directory -Path $dataRoot -Force | Out-Null\n$attachPath = Join-Path $dataRoot 'attach-helper.ps1'\n[IO.File]::WriteAllBytes($attachPath, [Convert]::FromBase64String('{helperBase64}'))\n\";\n\n await _sshService.ExecuteRemoteScriptAsync(worker, script);\n }\n\n public string GenerateSheepItPayload(WorkerConfig worker)\n {\n if (worker.WorkerTypes.SheepIt == null)\n throw new InvalidOperationException(\"Worker does not have SheepIt configuration\");\n\n var config = _configService.Load();", + "signatures": {}, + "detailedLines": [ + { + "text": "throw new InvalidOperationException($\"Resource {resourceName} not found\");", + "lineNumber": 50 + }, + { + "lineNumber": 51 + }, + { + "text": " using var reader = new BinaryReader(stream);", + "lineNumber": 52 + }, + { + "text": " _attachHelperScriptBytes = reader.ReadBytes((int)stream.Length);", + "lineNumber": 53 + }, + { + "text": " return _attachHelperScriptBytes;", + "lineNumber": 54 + }, + { + "text": " }", + "lineNumber": 55 + }, + { + "lineNumber": 56 + }, + { + "text": " public async Task DeployControllerAsync(WorkerConfig worker)", + "lineNumber": 57 + }, + { + "text": " {", + "lineNumber": 58 + }, + { + "text": " var controllerBase64 = Convert.ToBase64String(GetControllerScriptBytes());", + "lineNumber": 59 + }, + { + "text": " var script = $@\"", + "lineNumber": 60 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 61 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 62 + }, + { + "text": "New-Item -ItemType Directory -Path $dataRoot -Force | Out-Null", + "lineNumber": 63 + }, + { + "text": "$controllerPath = Join-Path $dataRoot 'controller.ps1'", + "lineNumber": 64 + }, + { + "text": "[IO.File]::WriteAllBytes($controllerPath, [Convert]::FromBase64String('{controllerBase64}'))", + "lineNumber": 65 + }, + { + "text": "\";", + "lineNumber": 66 + }, + { + "lineNumber": 67 + }, + { + "text": " await _sshService.ExecuteRemoteScriptAsync(worker, script);", + "lineNumber": 68 + }, + { + "text": " }", + "lineNumber": 69 + }, + { + "lineNumber": 70 + }, + { + "text": " public async Task DeployAttachHelperAsync(WorkerConfig worker)", + "lineNumber": 71 + }, + { + "text": " {", + "lineNumber": 72 + }, + { + "text": " var helperBase64 = Convert.ToBase64String(GetAttachHelperScriptBytes());", + "lineNumber": 73 + }, + { + "text": " var script = $@\"", + "lineNumber": 74 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 75 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 76 + }, + { + "text": "New-Item -ItemType Directory -Path $dataRoot -Force | Out-Null", + "lineNumber": 77 + }, + { + "text": "$attachPath = Join-Path $dataRoot 'attach-helper.ps1'", + "lineNumber": 78 + }, + { + "text": "[IO.File]::WriteAllBytes($attachPath, [Convert]::FromBase64String('{helperBase64}'))", + "lineNumber": 79 + }, + { + "text": "\";", + "lineNumber": 80 + }, + { + "lineNumber": 81 + }, + { + "text": " await _sshService.ExecuteRemoteScriptAsync(worker, script);", + "lineNumber": 82 + }, + { + "text": " }", + "lineNumber": 83 + }, + { + "lineNumber": 84 + }, + { + "text": " public string GenerateSheepItPayload(WorkerConfig worker)", + "lineNumber": 85 + }, + { + "text": " {", + "lineNumber": 86 + }, + { + "text": " if (worker.WorkerTypes.SheepIt == null)", + "lineNumber": 87 + }, + { + "text": " throw new InvalidOperationException(\"Worker does not have SheepIt configuration\");", + "lineNumber": 88 + }, + { + "lineNumber": 89 + }, + { + "text": " var config = _configService.Load();", + "lineNumber": 90 + } + ] + }, + "score": 0.17626219987869263 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 45 + } + }, + "contents": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Text;\nusing System.Threading.Tasks;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class SshConnectionParts\n {\n public string Host { get; set; } = string.Empty;\n public List<string> Options { get; set; } = new();\n public int? Port { get; set; }\n public bool RequestPty { get; set; }\n }\n\n public class SshService\n {\n private readonly Dictionary<string, string> _workerBasePathCache = new();\n\n private static string GetSshExecutable()\n {\n if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))\n {\n // Try common Windows OpenSSH locations\n var paths = new[]\n {\n Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"ssh.exe\"),\n \"ssh.exe\" // In PATH\n };\n foreach (var path in paths)\n {\n if (File.Exists(path) || IsInPath(\"ssh.exe\"))\n return \"ssh.exe\";\n }\n return \"ssh.exe\";\n }\n return \"ssh\";\n }\n\n private static string GetScpExecutable()", + "signatures": {}, + "detailedLines": [ + { + "text": "using System;", + "lineNumber": 1 + }, + { + "text": "using System.Collections.Generic;", + "lineNumber": 2 + }, + { + "text": "using System.Diagnostics;", + "lineNumber": 3 + }, + { + "text": "using System.IO;", + "lineNumber": 4 + }, + { + "text": "using System.Linq;", + "lineNumber": 5 + }, + { + "text": "using System.Runtime.InteropServices;", + "lineNumber": 6 + }, + { + "text": "using System.Text;", + "lineNumber": 7 + }, + { + "text": "using System.Threading.Tasks;", + "lineNumber": 8 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 9 + }, + { + "lineNumber": 10 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 11 + }, + { + "text": "{", + "lineNumber": 12 + }, + { + "text": " public class SshConnectionParts", + "lineNumber": 13 + }, + { + "text": " {", + "lineNumber": 14 + }, + { + "text": " public string Host { get; set; } = string.Empty;", + "lineNumber": 15 + }, + { + "text": " public List<string> Options { get; set; } = new();", + "lineNumber": 16 + }, + { + "text": " public int? Port { get; set; }", + "lineNumber": 17 + }, + { + "text": " public bool RequestPty { get; set; }", + "lineNumber": 18 + }, + { + "text": " }", + "lineNumber": 19 + }, + { + "lineNumber": 20 + }, + { + "text": " public class SshService", + "lineNumber": 21 + }, + { + "text": " {", + "lineNumber": 22 + }, + { + "text": " private readonly Dictionary<string, string> _workerBasePathCache = new();", + "lineNumber": 23 + }, + { + "lineNumber": 24 + }, + { + "text": " private static string GetSshExecutable()", + "lineNumber": 25 + }, + { + "text": " {", + "lineNumber": 26 + }, + { + "text": " if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))", + "lineNumber": 27 + }, + { + "text": " {", + "lineNumber": 28 + }, + { + "text": " // Try common Windows OpenSSH locations", + "lineNumber": 29 + }, + { + "text": " var paths = new[]", + "lineNumber": 30 + }, + { + "text": " {", + "lineNumber": 31 + }, + { + "text": " Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"ssh.exe\"),", + "lineNumber": 32 + }, + { + "text": " \"ssh.exe\" // In PATH", + "lineNumber": 33 + }, + { + "text": " };", + "lineNumber": 34 + }, + { + "text": " foreach (var path in paths)", + "lineNumber": 35 + }, + { + "text": " {", + "lineNumber": 36 + }, + { + "text": " if (File.Exists(path) || IsInPath(\"ssh.exe\"))", + "lineNumber": 37 + }, + { + "text": " return \"ssh.exe\";", + "lineNumber": 38 + }, + { + "text": " }", + "lineNumber": 39 + }, + { + "text": " return \"ssh.exe\";", + "lineNumber": 40 + }, + { + "text": " }", + "lineNumber": 41 + }, + { + "text": " return \"ssh\";", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": " private static string GetScpExecutable()", + "lineNumber": 45 + } + ] + }, + "score": 0.17221589386463165 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 213 + }, + "endPosition": { + "line": 260 + } + }, + "contents": " };\n\n var output = new StringBuilder();\n var error = new StringBuilder();\n\n process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };\n process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };\n\n process.Start();\n process.BeginOutputReadLine();\n process.BeginErrorReadLine();\n\n await process.WaitForExitAsync();\n\n if (process.ExitCode != 0 && !interactive)\n {\n throw new InvalidOperationException($\"SSH command failed with exit code {process.ExitCode}: {error}\");\n }\n\n return output.ToString();\n }\n\n public async Task<string> ExecuteRemoteScriptAsync(WorkerConfig worker, string script, bool interactive = false)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var sshArgs = BuildSshArgs(parts, interactive);\n // Add PowerShell command as a single quoted argument for remote execution\n sshArgs.Add(\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -Command -\");\n\n var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));\n \n var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetSshExecutable(),\n WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),\n Arguments = argsString,\n UseShellExecute = false,\n RedirectStandardInput = true,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = !interactive\n }\n };\n\n var output = new StringBuilder();", + "signatures": {}, + "detailedLines": [ + { + "text": " };", + "lineNumber": 214 + }, + { + "lineNumber": 215 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 216 + }, + { + "text": " var error = new StringBuilder();", + "lineNumber": 217 + }, + { + "lineNumber": 218 + }, + { + "text": " process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };", + "lineNumber": 219 + }, + { + "text": " process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };", + "lineNumber": 220 + }, + { + "lineNumber": 221 + }, + { + "text": " process.Start();", + "lineNumber": 222 + }, + { + "text": " process.BeginOutputReadLine();", + "lineNumber": 223 + }, + { + "text": " process.BeginErrorReadLine();", + "lineNumber": 224 + }, + { + "lineNumber": 225 + }, + { + "text": " await process.WaitForExitAsync();", + "lineNumber": 226 + }, + { + "lineNumber": 227 + }, + { + "text": " if (process.ExitCode != 0 && !interactive)", + "lineNumber": 228 + }, + { + "text": " {", + "lineNumber": 229 + }, + { + "text": " throw new InvalidOperationException($\"SSH command failed with exit code {process.ExitCode}: {error}\");", + "lineNumber": 230 + }, + { + "text": " }", + "lineNumber": 231 + }, + { + "lineNumber": 232 + }, + { + "text": " return output.ToString();", + "lineNumber": 233 + }, + { + "text": " }", + "lineNumber": 234 + }, + { + "lineNumber": 235 + }, + { + "text": " public async Task<string> ExecuteRemoteScriptAsync(WorkerConfig worker, string script, bool interactive = false)", + "lineNumber": 236 + }, + { + "text": " {", + "lineNumber": 237 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 238 + }, + { + "text": " var sshArgs = BuildSshArgs(parts, interactive);", + "lineNumber": 239 + }, + { + "text": " // Add PowerShell command as a single quoted argument for remote execution", + "lineNumber": 240 + }, + { + "text": " sshArgs.Add(\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -Command -\");", + "lineNumber": 241 + }, + { + "lineNumber": 242 + }, + { + "text": " var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));", + "lineNumber": 243 + }, + { + "text": " ", + "lineNumber": 244 + }, + { + "text": " var process = new Process", + "lineNumber": 245 + }, + { + "text": " {", + "lineNumber": 246 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 247 + }, + { + "text": " {", + "lineNumber": 248 + }, + { + "text": " FileName = GetSshExecutable(),", + "lineNumber": 249 + }, + { + "text": " WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),", + "lineNumber": 250 + }, + { + "text": " Arguments = argsString,", + "lineNumber": 251 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 252 + }, + { + "text": " RedirectStandardInput = true,", + "lineNumber": 253 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 254 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 255 + }, + { + "text": " CreateNoWindow = !interactive", + "lineNumber": 256 + }, + { + "text": " }", + "lineNumber": 257 + }, + { + "text": " };", + "lineNumber": 258 + }, + { + "lineNumber": 259 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 260 + } + ] + }, + "score": 0.17039300501346588 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\ConfigService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 62 + } + }, + "contents": "using System;\nusing System.IO;\nusing System.Linq;\nusing System.Text.Json;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class ConfigService\n {\n private static readonly JsonSerializerOptions JsonOptions = new()\n {\n WriteIndented = true,\n PropertyNamingPolicy = JsonNamingPolicy.CamelCase\n };\n\n private readonly string _configPath;\n private ConfigRoot? _config;\n\n public ConfigService()\n {\n var appDataPath = GetAppDataPath();\n Directory.CreateDirectory(appDataPath);\n _configPath = Path.Combine(appDataPath, \"workers.json\");\n }\n\n private static string GetAppDataPath()\n {\n var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);\n return Path.Combine(localAppData, \"UnifiedFarmLauncher\");\n }\n\n public ConfigRoot Load()\n {\n if (_config != null)\n return _config;\n\n if (!File.Exists(_configPath))\n {\n _config = new ConfigRoot();\n Save(_config);\n return _config;\n }\n\n try\n {\n var json = File.ReadAllText(_configPath);\n _config = JsonSerializer.Deserialize<ConfigRoot>(json, JsonOptions) ?? new ConfigRoot();\n return _config;\n }\n catch (Exception ex)\n {\n throw new InvalidOperationException($\"Failed to load configuration from {_configPath}: {ex.Message}\", ex);\n }\n }\n\n public void Save(ConfigRoot? config = null)\n {\n config ??= _config ?? new ConfigRoot();\n _config = config;\n\n try", + "signatures": {}, + "detailedLines": [ + { + "text": "using System;", + "lineNumber": 1 + }, + { + "text": "using System.IO;", + "lineNumber": 2 + }, + { + "text": "using System.Linq;", + "lineNumber": 3 + }, + { + "text": "using System.Text.Json;", + "lineNumber": 4 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 5 + }, + { + "lineNumber": 6 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 7 + }, + { + "text": "{", + "lineNumber": 8 + }, + { + "text": " public class ConfigService", + "lineNumber": 9 + }, + { + "text": " {", + "lineNumber": 10 + }, + { + "text": " private static readonly JsonSerializerOptions JsonOptions = new()", + "lineNumber": 11 + }, + { + "text": " {", + "lineNumber": 12 + }, + { + "text": " WriteIndented = true,", + "lineNumber": 13 + }, + { + "text": " PropertyNamingPolicy = JsonNamingPolicy.CamelCase", + "lineNumber": 14 + }, + { + "text": " };", + "lineNumber": 15 + }, + { + "lineNumber": 16 + }, + { + "text": " private readonly string _configPath;", + "lineNumber": 17 + }, + { + "text": " private ConfigRoot? _config;", + "lineNumber": 18 + }, + { + "lineNumber": 19 + }, + { + "text": " public ConfigService()", + "lineNumber": 20 + }, + { + "text": " {", + "lineNumber": 21 + }, + { + "text": " var appDataPath = GetAppDataPath();", + "lineNumber": 22 + }, + { + "text": " Directory.CreateDirectory(appDataPath);", + "lineNumber": 23 + }, + { + "text": " _configPath = Path.Combine(appDataPath, \"workers.json\");", + "lineNumber": 24 + }, + { + "text": " }", + "lineNumber": 25 + }, + { + "lineNumber": 26 + }, + { + "text": " private static string GetAppDataPath()", + "lineNumber": 27 + }, + { + "text": " {", + "lineNumber": 28 + }, + { + "text": " var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);", + "lineNumber": 29 + }, + { + "text": " return Path.Combine(localAppData, \"UnifiedFarmLauncher\");", + "lineNumber": 30 + }, + { + "text": " }", + "lineNumber": 31 + }, + { + "lineNumber": 32 + }, + { + "text": " public ConfigRoot Load()", + "lineNumber": 33 + }, + { + "text": " {", + "lineNumber": 34 + }, + { + "text": " if (_config != null)", + "lineNumber": 35 + }, + { + "text": " return _config;", + "lineNumber": 36 + }, + { + "lineNumber": 37 + }, + { + "text": " if (!File.Exists(_configPath))", + "lineNumber": 38 + }, + { + "text": " {", + "lineNumber": 39 + }, + { + "text": " _config = new ConfigRoot();", + "lineNumber": 40 + }, + { + "text": " Save(_config);", + "lineNumber": 41 + }, + { + "text": " return _config;", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": " try", + "lineNumber": 45 + }, + { + "text": " {", + "lineNumber": 46 + }, + { + "text": " var json = File.ReadAllText(_configPath);", + "lineNumber": 47 + }, + { + "text": " _config = JsonSerializer.Deserialize<ConfigRoot>(json, JsonOptions) ?? new ConfigRoot();", + "lineNumber": 48 + }, + { + "text": " return _config;", + "lineNumber": 49 + }, + { + "text": " }", + "lineNumber": 50 + }, + { + "text": " catch (Exception ex)", + "lineNumber": 51 + }, + { + "text": " {", + "lineNumber": 52 + }, + { + "text": " throw new InvalidOperationException($\"Failed to load configuration from {_configPath}: {ex.Message}\", ex);", + "lineNumber": 53 + }, + { + "text": " }", + "lineNumber": 54 + }, + { + "text": " }", + "lineNumber": 55 + }, + { + "lineNumber": 56 + }, + { + "text": " public void Save(ConfigRoot? config = null)", + "lineNumber": 57 + }, + { + "text": " {", + "lineNumber": 58 + }, + { + "text": " config ??= _config ?? new ConfigRoot();", + "lineNumber": 59 + }, + { + "text": " _config = config;", + "lineNumber": 60 + }, + { + "lineNumber": 61 + }, + { + "text": " try", + "lineNumber": 62 + } + ] + }, + "score": 0.15873350203037262 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\ConfigService.cs", + "range": { + "startPosition": { + "line": 119 + }, + "endPosition": { + "line": 128 + } + }, + "contents": " public int GetNextWorkerId()\n {\n var config = Load();\n return config.Workers.Count > 0 ? config.Workers.Max(w => w.Id) + 1 : 1;\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": " public int GetNextWorkerId()", + "lineNumber": 121 + }, + { + "text": " {", + "lineNumber": 122 + }, + { + "text": " var config = Load();", + "lineNumber": 123 + }, + { + "text": " return config.Workers.Count > 0 ? config.Workers.Max(w => w.Id) + 1 : 1;", + "lineNumber": 124 + }, + { + "text": " }", + "lineNumber": 125 + }, + { + "text": " }", + "lineNumber": 126 + }, + { + "text": "}", + "lineNumber": 127 + } + ] + }, + "score": 0.15597516298294067 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 40 + }, + "endPosition": { + "line": 98 + } + }, + "contents": " }\n return \"ssh\";\n }\n\n private static string GetScpExecutable()\n {\n if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))\n {\n var paths = new[]\n {\n Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"scp.exe\"),\n \"scp.exe\"\n };\n foreach (var path in paths)\n {\n if (File.Exists(path) || IsInPath(\"scp.exe\"))\n return \"scp.exe\";\n }\n return \"scp.exe\";\n }\n return \"scp\";\n }\n\n private static bool IsInPath(string executable)\n {\n try\n {\n var process = Process.Start(new ProcessStartInfo\n {\n FileName = executable,\n Arguments = \"-V\",\n UseShellExecute = false,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = true\n });\n process?.WaitForExit(1000);\n return process?.ExitCode == 0 || process?.ExitCode == 1; // SSH/SCP typically exit with 1 for version info\n }\n catch\n {\n return false;\n }\n }\n\n public SshConnectionParts ParseConnectionParts(string rawArgs, string defaultHost)\n {\n var parts = new SshConnectionParts { Host = defaultHost };\n if (string.IsNullOrWhiteSpace(rawArgs))\n return parts;\n\n var tokens = rawArgs.Split(new[] { ' ', '\\t' }, StringSplitOptions.RemoveEmptyEntries);\n var options = new List<string>();\n string? targetHost = null;\n int? port = null;\n bool requestPty = false;\n\n var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };", + "signatures": {}, + "detailedLines": [ + { + "text": " }", + "lineNumber": 41 + }, + { + "text": " return \"ssh\";", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": " private static string GetScpExecutable()", + "lineNumber": 45 + }, + { + "text": " {", + "lineNumber": 46 + }, + { + "text": " if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))", + "lineNumber": 47 + }, + { + "text": " {", + "lineNumber": 48 + }, + { + "text": " var paths = new[]", + "lineNumber": 49 + }, + { + "text": " {", + "lineNumber": 50 + }, + { + "text": " Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"scp.exe\"),", + "lineNumber": 51 + }, + { + "text": " \"scp.exe\"", + "lineNumber": 52 + }, + { + "text": " };", + "lineNumber": 53 + }, + { + "text": " foreach (var path in paths)", + "lineNumber": 54 + }, + { + "text": " {", + "lineNumber": 55 + }, + { + "text": " if (File.Exists(path) || IsInPath(\"scp.exe\"))", + "lineNumber": 56 + }, + { + "text": " return \"scp.exe\";", + "lineNumber": 57 + }, + { + "text": " }", + "lineNumber": 58 + }, + { + "text": " return \"scp.exe\";", + "lineNumber": 59 + }, + { + "text": " }", + "lineNumber": 60 + }, + { + "text": " return \"scp\";", + "lineNumber": 61 + }, + { + "text": " }", + "lineNumber": 62 + }, + { + "lineNumber": 63 + }, + { + "text": " private static bool IsInPath(string executable)", + "lineNumber": 64 + }, + { + "text": " {", + "lineNumber": 65 + }, + { + "text": " try", + "lineNumber": 66 + }, + { + "text": " {", + "lineNumber": 67 + }, + { + "text": " var process = Process.Start(new ProcessStartInfo", + "lineNumber": 68 + }, + { + "text": " {", + "lineNumber": 69 + }, + { + "text": " FileName = executable,", + "lineNumber": 70 + }, + { + "text": " Arguments = \"-V\",", + "lineNumber": 71 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 72 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 73 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 74 + }, + { + "text": " CreateNoWindow = true", + "lineNumber": 75 + }, + { + "text": " });", + "lineNumber": 76 + }, + { + "text": " process?.WaitForExit(1000);", + "lineNumber": 77 + }, + { + "text": " return process?.ExitCode == 0 || process?.ExitCode == 1; // SSH/SCP typically exit with 1 for version info", + "lineNumber": 78 + }, + { + "text": " }", + "lineNumber": 79 + }, + { + "text": " catch", + "lineNumber": 80 + }, + { + "text": " {", + "lineNumber": 81 + }, + { + "text": " return false;", + "lineNumber": 82 + }, + { + "text": " }", + "lineNumber": 83 + }, + { + "text": " }", + "lineNumber": 84 + }, + { + "lineNumber": 85 + }, + { + "text": " public SshConnectionParts ParseConnectionParts(string rawArgs, string defaultHost)", + "lineNumber": 86 + }, + { + "text": " {", + "lineNumber": 87 + }, + { + "text": " var parts = new SshConnectionParts { Host = defaultHost };", + "lineNumber": 88 + }, + { + "text": " if (string.IsNullOrWhiteSpace(rawArgs))", + "lineNumber": 89 + }, + { + "text": " return parts;", + "lineNumber": 90 + }, + { + "lineNumber": 91 + }, + { + "text": " var tokens = rawArgs.Split(new[] { ' ', '\\t' }, StringSplitOptions.RemoveEmptyEntries);", + "lineNumber": 92 + }, + { + "text": " var options = new List<string>();", + "lineNumber": 93 + }, + { + "text": " string? targetHost = null;", + "lineNumber": 94 + }, + { + "text": " int? port = null;", + "lineNumber": 95 + }, + { + "text": " bool requestPty = false;", + "lineNumber": 96 + }, + { + "lineNumber": 97 + }, + { + "text": " var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };", + "lineNumber": 98 + } + ] + }, + "score": 0.10286605358123779 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 342 + }, + "endPosition": { + "line": 361 + } + }, + "contents": " var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetSshExecutable(),\n WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),\n Arguments = argsString,\n UseShellExecute = true,\n CreateNoWindow = false\n }\n };\n\n process.Start();\n return process;\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": " var process = new Process", + "lineNumber": 344 + }, + { + "text": " {", + "lineNumber": 345 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 346 + }, + { + "text": " {", + "lineNumber": 347 + }, + { + "text": " FileName = GetSshExecutable(),", + "lineNumber": 348 + }, + { + "text": " WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),", + "lineNumber": 349 + }, + { + "text": " Arguments = argsString,", + "lineNumber": 350 + }, + { + "text": " UseShellExecute = true,", + "lineNumber": 351 + }, + { + "text": " CreateNoWindow = false", + "lineNumber": 352 + }, + { + "text": " }", + "lineNumber": 353 + }, + { + "text": " };", + "lineNumber": 354 + }, + { + "lineNumber": 355 + }, + { + "text": " process.Start();", + "lineNumber": 356 + }, + { + "text": " return process;", + "lineNumber": 357 + }, + { + "text": " }", + "lineNumber": 358 + }, + { + "text": " }", + "lineNumber": 359 + }, + { + "text": "}", + "lineNumber": 360 + } + ] + }, + "score": 0.08105870336294174 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 95 + }, + "endPosition": { + "line": 154 + } + }, + "contents": " bool requestPty = false;\n\n var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };\n\n for (int i = 0; i < tokens.Length; i++)\n {\n var token = tokens[i];\n if (token == \"-t\" || token == \"-tt\")\n {\n requestPty = true;\n continue;\n }\n\n if (token == \"-p\" && i + 1 < tokens.Length)\n {\n if (int.TryParse(tokens[i + 1], out var portValue))\n {\n port = portValue;\n i++;\n }\n continue;\n }\n\n if (token.StartsWith(\"-\"))\n {\n options.Add(token);\n if (optionsWithArgs.Contains(token) && i + 1 < tokens.Length)\n {\n options.Add(tokens[i + 1]);\n i++;\n }\n continue;\n }\n\n if (targetHost == null)\n {\n targetHost = token;\n continue;\n }\n\n options.Add(token);\n }\n\n parts.Host = targetHost ?? defaultHost;\n parts.Options = options;\n parts.Port = port;\n parts.RequestPty = requestPty;\n return parts;\n }\n\n public List<string> BuildSshArgs(SshConnectionParts parts, bool interactive)\n {\n var args = new List<string>\n {\n \"-o\", \"ServerAliveInterval=60\",\n \"-o\", \"ServerAliveCountMax=30\"\n };\n\n if (interactive && parts.RequestPty)", + "signatures": {}, + "detailedLines": [ + { + "text": " bool requestPty = false;", + "lineNumber": 96 + }, + { + "lineNumber": 97 + }, + { + "text": " var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };", + "lineNumber": 98 + }, + { + "lineNumber": 99 + }, + { + "text": " for (int i = 0; i < tokens.Length; i++)", + "lineNumber": 100 + }, + { + "text": " {", + "lineNumber": 101 + }, + { + "text": " var token = tokens[i];", + "lineNumber": 102 + }, + { + "text": " if (token == \"-t\" || token == \"-tt\")", + "lineNumber": 103 + }, + { + "text": " {", + "lineNumber": 104 + }, + { + "text": " requestPty = true;", + "lineNumber": 105 + }, + { + "text": " continue;", + "lineNumber": 106 + }, + { + "text": " }", + "lineNumber": 107 + }, + { + "lineNumber": 108 + }, + { + "text": " if (token == \"-p\" && i + 1 < tokens.Length)", + "lineNumber": 109 + }, + { + "text": " {", + "lineNumber": 110 + }, + { + "text": " if (int.TryParse(tokens[i + 1], out var portValue))", + "lineNumber": 111 + }, + { + "text": " {", + "lineNumber": 112 + }, + { + "text": " port = portValue;", + "lineNumber": 113 + }, + { + "text": " i++;", + "lineNumber": 114 + }, + { + "text": " }", + "lineNumber": 115 + }, + { + "text": " continue;", + "lineNumber": 116 + }, + { + "text": " }", + "lineNumber": 117 + }, + { + "lineNumber": 118 + }, + { + "text": " if (token.StartsWith(\"-\"))", + "lineNumber": 119 + }, + { + "text": " {", + "lineNumber": 120 + }, + { + "text": " options.Add(token);", + "lineNumber": 121 + }, + { + "text": " if (optionsWithArgs.Contains(token) && i + 1 < tokens.Length)", + "lineNumber": 122 + }, + { + "text": " {", + "lineNumber": 123 + }, + { + "text": " options.Add(tokens[i + 1]);", + "lineNumber": 124 + }, + { + "text": " i++;", + "lineNumber": 125 + }, + { + "text": " }", + "lineNumber": 126 + }, + { + "text": " continue;", + "lineNumber": 127 + }, + { + "text": " }", + "lineNumber": 128 + }, + { + "lineNumber": 129 + }, + { + "text": " if (targetHost == null)", + "lineNumber": 130 + }, + { + "text": " {", + "lineNumber": 131 + }, + { + "text": " targetHost = token;", + "lineNumber": 132 + }, + { + "text": " continue;", + "lineNumber": 133 + }, + { + "text": " }", + "lineNumber": 134 + }, + { + "lineNumber": 135 + }, + { + "text": " options.Add(token);", + "lineNumber": 136 + }, + { + "text": " }", + "lineNumber": 137 + }, + { + "lineNumber": 138 + }, + { + "text": " parts.Host = targetHost ?? defaultHost;", + "lineNumber": 139 + }, + { + "text": " parts.Options = options;", + "lineNumber": 140 + }, + { + "text": " parts.Port = port;", + "lineNumber": 141 + }, + { + "text": " parts.RequestPty = requestPty;", + "lineNumber": 142 + }, + { + "text": " return parts;", + "lineNumber": 143 + }, + { + "text": " }", + "lineNumber": 144 + }, + { + "lineNumber": 145 + }, + { + "text": " public List<string> BuildSshArgs(SshConnectionParts parts, bool interactive)", + "lineNumber": 146 + }, + { + "text": " {", + "lineNumber": 147 + }, + { + "text": " var args = new List<string>", + "lineNumber": 148 + }, + { + "text": " {", + "lineNumber": 149 + }, + { + "text": " \"-o\", \"ServerAliveInterval=60\",", + "lineNumber": 150 + }, + { + "text": " \"-o\", \"ServerAliveCountMax=30\"", + "lineNumber": 151 + }, + { + "text": " };", + "lineNumber": 152 + }, + { + "lineNumber": 153 + }, + { + "text": " if (interactive && parts.RequestPty)", + "lineNumber": 154 + } + ] + }, + "score": 0.024768350645899773 + } + ] +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +Adding worker status verification. Updating `StartWorkerAsync` to wait briefly and check the metadata to confirm the worker started before showing success: + + +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "d78f72b0-45bb-4283-a7d6-2a372650d96f" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "3abe75b1-5d3f-4fb7-b8ad-2665c4e07cd6" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:00.87 + +``` +
+
+ +--- + + +Fixing path handling for the remote PowerShell script: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "ca3e8209-4a58-4da0-9fc3-d809f6560ca1" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:00.87 + +``` +
+
+ +--- + + +Refining the verification to check for "running" status with a valid PID, since "launching" only indicates the controller started: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "b8f8ff5d-b98f-4f86-ba63-251542b06dad" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:00.84 + +``` +
+
+ +--- + + +Implemented worker status verification. Changes: + +## Changes Made + +1. **Added status verification** in `StartWorkerAsync`: + - Waits 2 seconds after starting the controller + - Checks worker status up to 3 times with 1-second delays + - Only shows success if the status is "running" or "launching" + +2. **Added `GetWorkerStatusAsync` method**: + - Reads the worker metadata file remotely + - Returns the current status, worker PID, and any error messages + - Handles cases where the metadata file doesn't exist yet + +3. **Improved error reporting**: + - If the worker doesn't start, throws an exception with the actual status and error message + - The error dialog shows what went wrong instead of a generic success message + +The application now: +- Waits for the controller to initialize +- Verifies the worker actually started before showing success +- Shows an error if the worker fails to start, with details about the failure + +This should prevent false "started successfully" messages when the worker hasn't actually started. The success message only appears when the worker is confirmed to be running or launching. + +--- + +_**User**_ + +progress! + +--- + +_**Agent (model default, mode Agent)**_ + + +The status check is running, but it's returning "unknown", which suggests the metadata file isn't being read correctly. Improving the status check to handle this case: + + +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "e897a37b-8117-45bd-a166-78928731cf61" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "565a0305-d3bc-422f-86b9-3af8fac52070" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` +C:\Users\Nathan\Desktop\UFL\Services\WorkerControllerService.cs(410,33): error CS0136: A local or parameter named 'options' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter [C:\Users\Nathan\Desktop\UFL\UnifiedFarmLauncher.csproj] + +Build FAILED. + +C:\Users\Nathan\Desktop\UFL\Services\WorkerControllerService.cs(410,33): error CS0136: A local or parameter named 'options' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter [C:\Users\Nathan\Desktop\UFL\UnifiedFarmLauncher.csproj] + 0 Warning(s) + 1 Error(s) + +Time Elapsed 00:00:00.71 + +``` +
+
+ +--- + + +Fixing the variable name conflict: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "fea51629-cfea-4e8d-9f58-ef3e97d7588e" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:01.14 + +``` +
+
+ +--- + + +Build succeeded. Improved the status checking: + +1. **Better JSON parsing** — Handles JSON on single or multiple lines +2. **More robust error handling** — Better handling of edge cases +3. **Improved output parsing** — Extracts JSON even if mixed with other output + +The status check should now correctly read the worker metadata. The "Status: unknown" error likely means the metadata file wasn't found or created yet. The improved parsing should handle this better. + +If you still see "Status: unknown", it may mean: +- The controller process hasn't created the metadata file yet (timing issue) +- The metadata file is in a different location than expected +- There's an error preventing the controller from writing the metadata + +The status check now retries up to 3 times with delays, so it should catch the worker once it starts. If issues persist, we can add more detailed logging to see what's happening during the status check. + +--- + +_**User**_ + +check the logs? + +--- + +_**Agent (model default, mode Agent)**_ + + +Checking the worker logs to diagnose the startup issue. Reading the log file from the remote machine: + + +--- + + +
+Tool use: **semantic_search_full** + + +Parameters: + +```json +{ + "query": "How can we read the worker log file from a remote machine to check for errors?", + "includePattern": "Services", + "topK": 10, + "explanation": "" +} +``` +Result: + +```json +{ + "codeResults": [ + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 257 + }, + "endPosition": { + "line": 301 + } + }, + "contents": " };\n\n var output = new StringBuilder();\n var error = new StringBuilder();\n\n process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };\n process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };\n\n process.Start();\n process.BeginOutputReadLine();\n process.BeginErrorReadLine();\n\n // Write script to stdin\n await process.StandardInput.WriteAsync(script);\n process.StandardInput.Close();\n\n await process.WaitForExitAsync();\n\n if (process.ExitCode != 0 && !interactive)\n {\n throw new InvalidOperationException($\"SSH script execution failed with exit code {process.ExitCode}: {error}\");\n }\n\n return output.ToString();\n }\n\n public async Task<string> GetWorkerBasePathAsync(WorkerConfig worker)\n {\n if (_workerBasePathCache.TryGetValue(worker.Name, out var cached))\n return cached;\n\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var scriptBlock = \"$ProgressPreference='SilentlyContinue'; [Environment]::GetFolderPath('LocalApplicationData')\";\n var encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock));\n var remoteCmd = $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {encoded}\";\n\n var output = await ExecuteRemoteCommandAsync(worker, remoteCmd);\n var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);\n var basePath = lines.LastOrDefault()?.Trim();\n\n if (string.IsNullOrEmpty(basePath))\n throw new InvalidOperationException($\"Unable to read LocalAppData path on {worker.Name}.\");\n\n var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");", + "signatures": {}, + "detailedLines": [ + { + "text": " };", + "lineNumber": 258 + }, + { + "lineNumber": 259 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 260 + }, + { + "text": " var error = new StringBuilder();", + "lineNumber": 261 + }, + { + "lineNumber": 262 + }, + { + "text": " process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };", + "lineNumber": 263 + }, + { + "text": " process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };", + "lineNumber": 264 + }, + { + "lineNumber": 265 + }, + { + "text": " process.Start();", + "lineNumber": 266 + }, + { + "text": " process.BeginOutputReadLine();", + "lineNumber": 267 + }, + { + "text": " process.BeginErrorReadLine();", + "lineNumber": 268 + }, + { + "lineNumber": 269 + }, + { + "text": " // Write script to stdin", + "lineNumber": 270 + }, + { + "text": " await process.StandardInput.WriteAsync(script);", + "lineNumber": 271 + }, + { + "text": " process.StandardInput.Close();", + "lineNumber": 272 + }, + { + "lineNumber": 273 + }, + { + "text": " await process.WaitForExitAsync();", + "lineNumber": 274 + }, + { + "lineNumber": 275 + }, + { + "text": " if (process.ExitCode != 0 && !interactive)", + "lineNumber": 276 + }, + { + "text": " {", + "lineNumber": 277 + }, + { + "text": " throw new InvalidOperationException($\"SSH script execution failed with exit code {process.ExitCode}: {error}\");", + "lineNumber": 278 + }, + { + "text": " }", + "lineNumber": 279 + }, + { + "lineNumber": 280 + }, + { + "text": " return output.ToString();", + "lineNumber": 281 + }, + { + "text": " }", + "lineNumber": 282 + }, + { + "lineNumber": 283 + }, + { + "text": " public async Task<string> GetWorkerBasePathAsync(WorkerConfig worker)", + "lineNumber": 284 + }, + { + "text": " {", + "lineNumber": 285 + }, + { + "text": " if (_workerBasePathCache.TryGetValue(worker.Name, out var cached))", + "lineNumber": 286 + }, + { + "text": " return cached;", + "lineNumber": 287 + }, + { + "lineNumber": 288 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 289 + }, + { + "text": " var scriptBlock = \"$ProgressPreference='SilentlyContinue'; [Environment]::GetFolderPath('LocalApplicationData')\";", + "lineNumber": 290 + }, + { + "text": " var encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock));", + "lineNumber": 291 + }, + { + "text": " var remoteCmd = $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {encoded}\";", + "lineNumber": 292 + }, + { + "lineNumber": 293 + }, + { + "text": " var output = await ExecuteRemoteCommandAsync(worker, remoteCmd);", + "lineNumber": 294 + }, + { + "text": " var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);", + "lineNumber": 295 + }, + { + "text": " var basePath = lines.LastOrDefault()?.Trim();", + "lineNumber": 296 + }, + { + "lineNumber": 297 + }, + { + "text": " if (string.IsNullOrEmpty(basePath))", + "lineNumber": 298 + }, + { + "text": " throw new InvalidOperationException($\"Unable to read LocalAppData path on {worker.Name}.\");", + "lineNumber": 299 + }, + { + "lineNumber": 300 + }, + { + "text": " var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");", + "lineNumber": 301 + } + ] + }, + "score": 0.4063599109649658 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 299 + }, + "endPosition": { + "line": 345, + "column": 16 + } + }, + "contents": " var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");\n _workerBasePathCache[worker.Name] = finalPath;\n return finalPath;\n }\n\n public async Task CopyFileToRemoteAsync(WorkerConfig worker, string localPath, string remotePath)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var scpArgs = BuildScpArgs(parts);\n scpArgs.Add(localPath);\n scpArgs.Add($\"{parts.Host}:\\\"{remotePath.Replace(\"\\\\\", \"/\")}\\\"\");\n\n var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetScpExecutable(),\n Arguments = string.Join(\" \", scpArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\")),\n UseShellExecute = false,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = true\n }\n };\n\n process.Start();\n await process.WaitForExitAsync();\n\n if (process.ExitCode != 0)\n {\n var error = await process.StandardError.ReadToEndAsync();\n throw new InvalidOperationException($\"SCP failed with exit code {process.ExitCode}: {error}\");\n }\n }\n\n public Process StartInteractiveSsh(WorkerConfig worker, string command)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var sshArgs = BuildSshArgs(parts, true);\n sshArgs.Add(command);\n\n var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));\n\n var process = new Process\n {\n ", + "signatures": {}, + "detailedLines": [ + { + "text": " var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");", + "lineNumber": 301 + }, + { + "text": " _workerBasePathCache[worker.Name] = finalPath;", + "lineNumber": 302 + }, + { + "text": " return finalPath;", + "lineNumber": 303 + }, + { + "text": " }", + "lineNumber": 304 + }, + { + "lineNumber": 305 + }, + { + "text": " public async Task CopyFileToRemoteAsync(WorkerConfig worker, string localPath, string remotePath)", + "lineNumber": 306 + }, + { + "text": " {", + "lineNumber": 307 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 308 + }, + { + "text": " var scpArgs = BuildScpArgs(parts);", + "lineNumber": 309 + }, + { + "text": " scpArgs.Add(localPath);", + "lineNumber": 310 + }, + { + "text": " scpArgs.Add($\"{parts.Host}:\\\"{remotePath.Replace(\"\\\\\", \"/\")}\\\"\");", + "lineNumber": 311 + }, + { + "lineNumber": 312 + }, + { + "text": " var process = new Process", + "lineNumber": 313 + }, + { + "text": " {", + "lineNumber": 314 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 315 + }, + { + "text": " {", + "lineNumber": 316 + }, + { + "text": " FileName = GetScpExecutable(),", + "lineNumber": 317 + }, + { + "text": " Arguments = string.Join(\" \", scpArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\")),", + "lineNumber": 318 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 319 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 320 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 321 + }, + { + "text": " CreateNoWindow = true", + "lineNumber": 322 + }, + { + "text": " }", + "lineNumber": 323 + }, + { + "text": " };", + "lineNumber": 324 + }, + { + "lineNumber": 325 + }, + { + "text": " process.Start();", + "lineNumber": 326 + }, + { + "text": " await process.WaitForExitAsync();", + "lineNumber": 327 + }, + { + "lineNumber": 328 + }, + { + "text": " if (process.ExitCode != 0)", + "lineNumber": 329 + }, + { + "text": " {", + "lineNumber": 330 + }, + { + "text": " var error = await process.StandardError.ReadToEndAsync();", + "lineNumber": 331 + }, + { + "text": " throw new InvalidOperationException($\"SCP failed with exit code {process.ExitCode}: {error}\");", + "lineNumber": 332 + }, + { + "text": " }", + "lineNumber": 333 + }, + { + "text": " }", + "lineNumber": 334 + }, + { + "lineNumber": 335 + }, + { + "text": " public Process StartInteractiveSsh(WorkerConfig worker, string command)", + "lineNumber": 336 + }, + { + "text": " {", + "lineNumber": 337 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 338 + }, + { + "text": " var sshArgs = BuildSshArgs(parts, true);", + "lineNumber": 339 + }, + { + "text": " sshArgs.Add(command);", + "lineNumber": 340 + }, + { + "lineNumber": 341 + }, + { + "text": " var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));", + "lineNumber": 342 + }, + { + "lineNumber": 343 + }, + { + "text": " var process = new Process", + "lineNumber": 344 + }, + { + "text": " {", + "lineNumber": 345 + }, + { + "text": " ", + "lineNumber": 346 + } + ] + }, + "score": 0.37016281485557556 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 213 + }, + "endPosition": { + "line": 260 + } + }, + "contents": " };\n\n var output = new StringBuilder();\n var error = new StringBuilder();\n\n process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };\n process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };\n\n process.Start();\n process.BeginOutputReadLine();\n process.BeginErrorReadLine();\n\n await process.WaitForExitAsync();\n\n if (process.ExitCode != 0 && !interactive)\n {\n throw new InvalidOperationException($\"SSH command failed with exit code {process.ExitCode}: {error}\");\n }\n\n return output.ToString();\n }\n\n public async Task<string> ExecuteRemoteScriptAsync(WorkerConfig worker, string script, bool interactive = false)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var sshArgs = BuildSshArgs(parts, interactive);\n // Add PowerShell command as a single quoted argument for remote execution\n sshArgs.Add(\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -Command -\");\n\n var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));\n \n var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetSshExecutable(),\n WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),\n Arguments = argsString,\n UseShellExecute = false,\n RedirectStandardInput = true,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = !interactive\n }\n };\n\n var output = new StringBuilder();", + "signatures": {}, + "detailedLines": [ + { + "text": " };", + "lineNumber": 214 + }, + { + "lineNumber": 215 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 216 + }, + { + "text": " var error = new StringBuilder();", + "lineNumber": 217 + }, + { + "lineNumber": 218 + }, + { + "text": " process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };", + "lineNumber": 219 + }, + { + "text": " process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };", + "lineNumber": 220 + }, + { + "lineNumber": 221 + }, + { + "text": " process.Start();", + "lineNumber": 222 + }, + { + "text": " process.BeginOutputReadLine();", + "lineNumber": 223 + }, + { + "text": " process.BeginErrorReadLine();", + "lineNumber": 224 + }, + { + "lineNumber": 225 + }, + { + "text": " await process.WaitForExitAsync();", + "lineNumber": 226 + }, + { + "lineNumber": 227 + }, + { + "text": " if (process.ExitCode != 0 && !interactive)", + "lineNumber": 228 + }, + { + "text": " {", + "lineNumber": 229 + }, + { + "text": " throw new InvalidOperationException($\"SSH command failed with exit code {process.ExitCode}: {error}\");", + "lineNumber": 230 + }, + { + "text": " }", + "lineNumber": 231 + }, + { + "lineNumber": 232 + }, + { + "text": " return output.ToString();", + "lineNumber": 233 + }, + { + "text": " }", + "lineNumber": 234 + }, + { + "lineNumber": 235 + }, + { + "text": " public async Task<string> ExecuteRemoteScriptAsync(WorkerConfig worker, string script, bool interactive = false)", + "lineNumber": 236 + }, + { + "text": " {", + "lineNumber": 237 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 238 + }, + { + "text": " var sshArgs = BuildSshArgs(parts, interactive);", + "lineNumber": 239 + }, + { + "text": " // Add PowerShell command as a single quoted argument for remote execution", + "lineNumber": 240 + }, + { + "text": " sshArgs.Add(\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -Command -\");", + "lineNumber": 241 + }, + { + "lineNumber": 242 + }, + { + "text": " var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));", + "lineNumber": 243 + }, + { + "text": " ", + "lineNumber": 244 + }, + { + "text": " var process = new Process", + "lineNumber": 245 + }, + { + "text": " {", + "lineNumber": 246 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 247 + }, + { + "text": " {", + "lineNumber": 248 + }, + { + "text": " FileName = GetSshExecutable(),", + "lineNumber": 249 + }, + { + "text": " WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),", + "lineNumber": 250 + }, + { + "text": " Arguments = argsString,", + "lineNumber": 251 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 252 + }, + { + "text": " RedirectStandardInput = true,", + "lineNumber": 253 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 254 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 255 + }, + { + "text": " CreateNoWindow = !interactive", + "lineNumber": 256 + }, + { + "text": " }", + "lineNumber": 257 + }, + { + "text": " };", + "lineNumber": 258 + }, + { + "lineNumber": 259 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 260 + } + ] + }, + "score": 0.3670389652252197 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 185 + }, + "endPosition": { + "line": 234 + } + }, + "contents": "$ErrorActionPreference = 'Stop'\n\n# Write startup message to stderr so controller can capture it\n[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')\n\ntry {{\n Write-Host \"\"Setting up network connections...\"\" -ForegroundColor Cyan\n\n $drives = @({drives})\n $networkPaths = @({paths})\n\n # Disconnect all existing connections\n Write-Host \"\"Disconnecting existing network connections...\"\" -ForegroundColor Yellow\n foreach ($path in $networkPaths) {{ net use $path /delete /y 2>$null }}\n foreach ($drive in $drives) {{ net use $drive /delete /y 2>$null }}\n Write-Host \"\"All network connections cleared.\"\" -ForegroundColor Green\n\n # Connect to network shares (simplified - credentials should be stored securely)\n Write-Host \"\"Establishing network connections...\"\" -ForegroundColor Cyan\n # TODO: Add credential handling for network shares\n\n # Start worker\n Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan\n Set-Location '{workerPath}'\n if (Test-Path 'flamenco-worker.exe') {{\n Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green\n $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait\n $exitCode = $workerProcess.ExitCode\n Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow\n exit $exitCode\n }} else {{\n Write-Host \"\"Error: flamenco-worker.exe not found in {workerPath}\"\" -ForegroundColor Red\n [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {workerPath}\"\")\n exit 1\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "signatures": {}, + "detailedLines": [ + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 186 + }, + { + "lineNumber": 187 + }, + { + "text": "# Write startup message to stderr so controller can capture it", + "lineNumber": 188 + }, + { + "text": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "lineNumber": 189 + }, + { + "lineNumber": 190 + }, + { + "text": "try {{", + "lineNumber": 191 + }, + { + "text": " Write-Host \"\"Setting up network connections...\"\" -ForegroundColor Cyan", + "lineNumber": 192 + }, + { + "lineNumber": 193 + }, + { + "text": " $drives = @({drives})", + "lineNumber": 194 + }, + { + "text": " $networkPaths = @({paths})", + "lineNumber": 195 + }, + { + "lineNumber": 196 + }, + { + "text": " # Disconnect all existing connections", + "lineNumber": 197 + }, + { + "text": " Write-Host \"\"Disconnecting existing network connections...\"\" -ForegroundColor Yellow", + "lineNumber": 198 + }, + { + "text": " foreach ($path in $networkPaths) {{ net use $path /delete /y 2>$null }}", + "lineNumber": 199 + }, + { + "text": " foreach ($drive in $drives) {{ net use $drive /delete /y 2>$null }}", + "lineNumber": 200 + }, + { + "text": " Write-Host \"\"All network connections cleared.\"\" -ForegroundColor Green", + "lineNumber": 201 + }, + { + "lineNumber": 202 + }, + { + "text": " # Connect to network shares (simplified - credentials should be stored securely)", + "lineNumber": 203 + }, + { + "text": " Write-Host \"\"Establishing network connections...\"\" -ForegroundColor Cyan", + "lineNumber": 204 + }, + { + "text": " # TODO: Add credential handling for network shares", + "lineNumber": 205 + }, + { + "lineNumber": 206 + }, + { + "text": " # Start worker", + "lineNumber": 207 + }, + { + "text": " Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan", + "lineNumber": 208 + }, + { + "text": " Set-Location '{workerPath}'", + "lineNumber": 209 + }, + { + "text": " if (Test-Path 'flamenco-worker.exe') {{", + "lineNumber": 210 + }, + { + "text": " Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green", + "lineNumber": 211 + }, + { + "text": " $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait", + "lineNumber": 212 + }, + { + "text": " $exitCode = $workerProcess.ExitCode", + "lineNumber": 213 + }, + { + "text": " Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow", + "lineNumber": 214 + }, + { + "text": " exit $exitCode", + "lineNumber": 215 + }, + { + "text": " }} else {{", + "lineNumber": 216 + }, + { + "text": " Write-Host \"\"Error: flamenco-worker.exe not found in {workerPath}\"\" -ForegroundColor Red", + "lineNumber": 217 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {workerPath}\"\")", + "lineNumber": 218 + }, + { + "text": " exit 1", + "lineNumber": 219 + }, + { + "text": " }}", + "lineNumber": 220 + }, + { + "text": "}}", + "lineNumber": 221 + }, + { + "text": "catch {{", + "lineNumber": 222 + }, + { + "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)", + "lineNumber": 223 + }, + { + "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)", + "lineNumber": 224 + }, + { + "text": " Write-Host $errorMsg -ForegroundColor Red", + "lineNumber": 225 + }, + { + "text": " Write-Host $stackMsg -ForegroundColor DarkRed", + "lineNumber": 226 + }, + { + "text": " [Console]::Error.WriteLine($errorMsg)", + "lineNumber": 227 + }, + { + "text": " [Console]::Error.WriteLine($stackMsg)", + "lineNumber": 228 + }, + { + "text": " exit 1", + "lineNumber": 229 + }, + { + "text": "}}", + "lineNumber": 230 + }, + { + "text": "\";", + "lineNumber": 231 + }, + { + "text": " }", + "lineNumber": 232 + }, + { + "lineNumber": 233 + }, + { + "text": " public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 234 + } + ] + }, + "score": 0.35812073945999146 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 138, + "column": 12 + }, + "endPosition": { + "line": 188 + } + }, + "contents": "throw 'Unable to download SheepIt client from any known URL.'\n }}\n }}\n\n [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')\n Set-Location $sheepDir\n \n $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, \n '-ui', 'text', '--log-stdout', '--verbose', \n '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')\n \n try {{\n & java @javaArgs\n }}\n catch {{\n Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red\n Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow\n [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")\n throw\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public string GenerateFlamencoPayload(WorkerConfig worker)\n {\n if (worker.WorkerTypes.Flamenco == null)\n throw new InvalidOperationException(\"Worker does not have Flamenco configuration\");\n\n var config = _configService.Load();\n var flamenco = worker.WorkerTypes.Flamenco;\n var globalSettings = config.GlobalSettings;\n var workerPath = globalSettings.FlamencoWorkerPath.Replace(\"'\", \"''\");\n var drives = string.Join(\", \", Array.ConvertAll(flamenco.NetworkDrives.ToArray(), d => $\"'{d}'\"));\n var paths = string.Join(\", \", Array.ConvertAll(flamenco.NetworkPaths.ToArray(), p => $\"'{p.Replace(\"\\\\\", \"\\\\\\\\\")}'\"));\n\n return $@\"\n$ProgressPreference = 'SilentlyContinue'\n$ErrorActionPreference = 'Stop'\n\n# Write startup message to stderr so controller can capture it", + "signatures": {}, + "detailedLines": [ + { + "text": "throw 'Unable to download SheepIt client from any known URL.'", + "lineNumber": 139 + }, + { + "text": " }}", + "lineNumber": 140 + }, + { + "text": " }}", + "lineNumber": 141 + }, + { + "lineNumber": 142 + }, + { + "text": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')", + "lineNumber": 143 + }, + { + "text": " Set-Location $sheepDir", + "lineNumber": 144 + }, + { + "text": " ", + "lineNumber": 145 + }, + { + "text": " $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, ", + "lineNumber": 146 + }, + { + "text": " '-ui', 'text', '--log-stdout', '--verbose', ", + "lineNumber": 147 + }, + { + "text": " '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')", + "lineNumber": 148 + }, + { + "text": " ", + "lineNumber": 149 + }, + { + "text": " try {{", + "lineNumber": 150 + }, + { + "text": " & java @javaArgs", + "lineNumber": 151 + }, + { + "text": " }}", + "lineNumber": 152 + }, + { + "text": " catch {{", + "lineNumber": 153 + }, + { + "text": " Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red", + "lineNumber": 154 + }, + { + "text": " Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow", + "lineNumber": 155 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")", + "lineNumber": 156 + }, + { + "text": " throw", + "lineNumber": 157 + }, + { + "text": " }}", + "lineNumber": 158 + }, + { + "text": "}}", + "lineNumber": 159 + }, + { + "text": "catch {{", + "lineNumber": 160 + }, + { + "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)", + "lineNumber": 161 + }, + { + "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)", + "lineNumber": 162 + }, + { + "text": " Write-Host $errorMsg -ForegroundColor Red", + "lineNumber": 163 + }, + { + "text": " Write-Host $stackMsg -ForegroundColor DarkRed", + "lineNumber": 164 + }, + { + "text": " [Console]::Error.WriteLine($errorMsg)", + "lineNumber": 165 + }, + { + "text": " [Console]::Error.WriteLine($stackMsg)", + "lineNumber": 166 + }, + { + "text": " exit 1", + "lineNumber": 167 + }, + { + "text": "}}", + "lineNumber": 168 + }, + { + "text": "\";", + "lineNumber": 169 + }, + { + "text": " }", + "lineNumber": 170 + }, + { + "lineNumber": 171 + }, + { + "text": " public string GenerateFlamencoPayload(WorkerConfig worker)", + "lineNumber": 172 + }, + { + "text": " {", + "lineNumber": 173 + }, + { + "text": " if (worker.WorkerTypes.Flamenco == null)", + "lineNumber": 174 + }, + { + "text": " throw new InvalidOperationException(\"Worker does not have Flamenco configuration\");", + "lineNumber": 175 + }, + { + "lineNumber": 176 + }, + { + "text": " var config = _configService.Load();", + "lineNumber": 177 + }, + { + "text": " var flamenco = worker.WorkerTypes.Flamenco;", + "lineNumber": 178 + }, + { + "text": " var globalSettings = config.GlobalSettings;", + "lineNumber": 179 + }, + { + "text": " var workerPath = globalSettings.FlamencoWorkerPath.Replace(\"'\", \"''\");", + "lineNumber": 180 + }, + { + "text": " var drives = string.Join(\", \", Array.ConvertAll(flamenco.NetworkDrives.ToArray(), d => $\"'{d}'\"));", + "lineNumber": 181 + }, + { + "text": " var paths = string.Join(\", \", Array.ConvertAll(flamenco.NetworkPaths.ToArray(), p => $\"'{p.Replace(\"\\\\\", \"\\\\\\\\\")}'\"));", + "lineNumber": 182 + }, + { + "lineNumber": 183 + }, + { + "text": " return $@\"", + "lineNumber": 184 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 185 + }, + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 186 + }, + { + "lineNumber": 187 + }, + { + "text": "# Write startup message to stderr so controller can capture it", + "lineNumber": 188 + } + ] + }, + "score": 0.35685110092163086 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\AttachService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 46 + } + }, + "contents": "using System.Diagnostics;\nusing System.IO;\nusing System.Threading.Tasks;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class AttachService\n {\n private readonly SshService _sshService;\n private readonly WorkerControllerService _controllerService;\n\n public AttachService(SshService sshService, WorkerControllerService controllerService)\n {\n _sshService = sshService;\n _controllerService = controllerService;\n }\n\n public async Task AttachToWorkerAsync(WorkerConfig worker, string workerType, bool commandOnly = false, string? command = null)\n {\n await _controllerService.DeployAttachHelperAsync(worker);\n\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n var remoteHelper = Path.Combine(remoteBasePath, \"attach-helper.ps1\");\n\n var paramsBlock = $\"-WorkerName \\\"{worker.Name}\\\" -WorkerType \\\"{workerType}\\\"\";\n if (commandOnly)\n {\n paramsBlock += \" -CommandOnly\";\n }\n if (!string.IsNullOrEmpty(command))\n {\n paramsBlock += $\" -Command \\\"{command}\\\"\";\n }\n\n // Use Windows path format (backslashes) and ensure it's properly quoted\n // Add -NoExit to keep window open and ensure output is visible\n // Use -Command with & to properly invoke the script and avoid pipeline input issues\n var escapedHelper = remoteHelper.Replace(\"'\", \"''\");\n var remoteCmd = $\"powershell.exe -NoLogo -NoProfile -NoExit -ExecutionPolicy Bypass -Command \\\"& '{escapedHelper}' {paramsBlock}\\\"\";\n\n _sshService.StartInteractiveSsh(worker, remoteCmd);\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": "using System.Diagnostics;", + "lineNumber": 1 + }, + { + "text": "using System.IO;", + "lineNumber": 2 + }, + { + "text": "using System.Threading.Tasks;", + "lineNumber": 3 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 4 + }, + { + "lineNumber": 5 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 6 + }, + { + "text": "{", + "lineNumber": 7 + }, + { + "text": " public class AttachService", + "lineNumber": 8 + }, + { + "text": " {", + "lineNumber": 9 + }, + { + "text": " private readonly SshService _sshService;", + "lineNumber": 10 + }, + { + "text": " private readonly WorkerControllerService _controllerService;", + "lineNumber": 11 + }, + { + "lineNumber": 12 + }, + { + "text": " public AttachService(SshService sshService, WorkerControllerService controllerService)", + "lineNumber": 13 + }, + { + "text": " {", + "lineNumber": 14 + }, + { + "text": " _sshService = sshService;", + "lineNumber": 15 + }, + { + "text": " _controllerService = controllerService;", + "lineNumber": 16 + }, + { + "text": " }", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": " public async Task AttachToWorkerAsync(WorkerConfig worker, string workerType, bool commandOnly = false, string? command = null)", + "lineNumber": 19 + }, + { + "text": " {", + "lineNumber": 20 + }, + { + "text": " await _controllerService.DeployAttachHelperAsync(worker);", + "lineNumber": 21 + }, + { + "lineNumber": 22 + }, + { + "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);", + "lineNumber": 23 + }, + { + "text": " var remoteHelper = Path.Combine(remoteBasePath, \"attach-helper.ps1\");", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": " var paramsBlock = $\"-WorkerName \\\"{worker.Name}\\\" -WorkerType \\\"{workerType}\\\"\";", + "lineNumber": 26 + }, + { + "text": " if (commandOnly)", + "lineNumber": 27 + }, + { + "text": " {", + "lineNumber": 28 + }, + { + "text": " paramsBlock += \" -CommandOnly\";", + "lineNumber": 29 + }, + { + "text": " }", + "lineNumber": 30 + }, + { + "text": " if (!string.IsNullOrEmpty(command))", + "lineNumber": 31 + }, + { + "text": " {", + "lineNumber": 32 + }, + { + "text": " paramsBlock += $\" -Command \\\"{command}\\\"\";", + "lineNumber": 33 + }, + { + "text": " }", + "lineNumber": 34 + }, + { + "lineNumber": 35 + }, + { + "text": " // Use Windows path format (backslashes) and ensure it's properly quoted", + "lineNumber": 36 + }, + { + "text": " // Add -NoExit to keep window open and ensure output is visible", + "lineNumber": 37 + }, + { + "text": " // Use -Command with & to properly invoke the script and avoid pipeline input issues", + "lineNumber": 38 + }, + { + "text": " var escapedHelper = remoteHelper.Replace(\"'\", \"''\");", + "lineNumber": 39 + }, + { + "text": " var remoteCmd = $\"powershell.exe -NoLogo -NoProfile -NoExit -ExecutionPolicy Bypass -Command \\\"& '{escapedHelper}' {paramsBlock}\\\"\";", + "lineNumber": 40 + }, + { + "lineNumber": 41 + }, + { + "text": " _sshService.StartInteractiveSsh(worker, remoteCmd);", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "text": " }", + "lineNumber": 44 + }, + { + "text": "}", + "lineNumber": 45 + } + ] + }, + "score": 0.3503228425979614 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 270 + }, + "endPosition": { + "line": 320 + } + }, + "contents": "$commandPath = Join-Path $stateRoot 'commands.txt'\n$payloadPath = Join-Path $stateRoot 'payload.ps1'\n$payloadBase64Path = Join-Path $stateRoot 'payload.b64'\nif (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}\nif (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}\n[IO.File]::WriteAllText($payloadBase64Path, $payloadBase64, [System.Text.Encoding]::UTF8)\n$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'\n$controllerPath = Join-Path $dataRoot 'controller.ps1'\n\nif (-not (Test-Path $controllerPath)) {{\n throw \"\"Controller missing at $controllerPath\"\"\n}}\n\n$shouldStart = $true\nif (Test-Path $metaPath) {{\n try {{\n $meta = Get-Content $metaPath -Raw | ConvertFrom-Json\n if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{\n if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{\n Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"\n $shouldStart = $false\n }}\n }}\n }} catch {{\n Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow\n }}\n}}\n\nif ($shouldStart) {{\n $initialMeta = [pscustomobject]@{{\n WorkerName = $workerName\n WorkerType = $workerType\n Status = 'launching'\n ControllerPid = $null\n WorkerPid = $null\n Restarts = 0\n LastExitCode = $null\n LogPath = $logPath\n CommandPath = $commandPath\n PayloadPath = $payloadPath\n UpdatedAtUtc = (Get-Date).ToUniversalTime()\n }} | ConvertTo-Json -Depth 5\n $initialMeta | Set-Content -Path $metaPath -Encoding UTF8\n\n $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue\n if ($pwsh) {{\n $psExe = $pwsh.Source\n }}\n else {{\n $psExe = (Get-Command powershell -ErrorAction Stop).Source", + "signatures": {}, + "detailedLines": [ + { + "text": "$commandPath = Join-Path $stateRoot 'commands.txt'", + "lineNumber": 271 + }, + { + "text": "$payloadPath = Join-Path $stateRoot 'payload.ps1'", + "lineNumber": 272 + }, + { + "text": "$payloadBase64Path = Join-Path $stateRoot 'payload.b64'", + "lineNumber": 273 + }, + { + "text": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}", + "lineNumber": 274 + }, + { + "text": "if (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "lineNumber": 275 + }, + { + "text": "[IO.File]::WriteAllText($payloadBase64Path, $payloadBase64, [System.Text.Encoding]::UTF8)", + "lineNumber": 276 + }, + { + "text": "$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'", + "lineNumber": 277 + }, + { + "text": "$controllerPath = Join-Path $dataRoot 'controller.ps1'", + "lineNumber": 278 + }, + { + "lineNumber": 279 + }, + { + "text": "if (-not (Test-Path $controllerPath)) {{", + "lineNumber": 280 + }, + { + "text": " throw \"\"Controller missing at $controllerPath\"\"", + "lineNumber": 281 + }, + { + "text": "}}", + "lineNumber": 282 + }, + { + "lineNumber": 283 + }, + { + "text": "$shouldStart = $true", + "lineNumber": 284 + }, + { + "text": "if (Test-Path $metaPath) {{", + "lineNumber": 285 + }, + { + "text": " try {{", + "lineNumber": 286 + }, + { + "text": " $meta = Get-Content $metaPath -Raw | ConvertFrom-Json", + "lineNumber": 287 + }, + { + "text": " if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{", + "lineNumber": 288 + }, + { + "text": " if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{", + "lineNumber": 289 + }, + { + "text": " Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"", + "lineNumber": 290 + }, + { + "text": " $shouldStart = $false", + "lineNumber": 291 + }, + { + "text": " }}", + "lineNumber": 292 + }, + { + "text": " }}", + "lineNumber": 293 + }, + { + "text": " }} catch {{", + "lineNumber": 294 + }, + { + "text": " Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow", + "lineNumber": 295 + }, + { + "text": " }}", + "lineNumber": 296 + }, + { + "text": "}}", + "lineNumber": 297 + }, + { + "lineNumber": 298 + }, + { + "text": "if ($shouldStart) {{", + "lineNumber": 299 + }, + { + "text": " $initialMeta = [pscustomobject]@{{", + "lineNumber": 300 + }, + { + "text": " WorkerName = $workerName", + "lineNumber": 301 + }, + { + "text": " WorkerType = $workerType", + "lineNumber": 302 + }, + { + "text": " Status = 'launching'", + "lineNumber": 303 + }, + { + "text": " ControllerPid = $null", + "lineNumber": 304 + }, + { + "text": " WorkerPid = $null", + "lineNumber": 305 + }, + { + "text": " Restarts = 0", + "lineNumber": 306 + }, + { + "text": " LastExitCode = $null", + "lineNumber": 307 + }, + { + "text": " LogPath = $logPath", + "lineNumber": 308 + }, + { + "text": " CommandPath = $commandPath", + "lineNumber": 309 + }, + { + "text": " PayloadPath = $payloadPath", + "lineNumber": 310 + }, + { + "text": " UpdatedAtUtc = (Get-Date).ToUniversalTime()", + "lineNumber": 311 + }, + { + "text": " }} | ConvertTo-Json -Depth 5", + "lineNumber": 312 + }, + { + "text": " $initialMeta | Set-Content -Path $metaPath -Encoding UTF8", + "lineNumber": 313 + }, + { + "lineNumber": 314 + }, + { + "text": " $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue", + "lineNumber": 315 + }, + { + "text": " if ($pwsh) {{", + "lineNumber": 316 + }, + { + "text": " $psExe = $pwsh.Source", + "lineNumber": 317 + }, + { + "text": " }}", + "lineNumber": 318 + }, + { + "text": " else {{", + "lineNumber": 319 + }, + { + "text": " $psExe = (Get-Command powershell -ErrorAction Stop).Source", + "lineNumber": 320 + } + ] + }, + "score": 0.3480184078216553 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 227 + }, + "endPosition": { + "line": 272 + } + }, + "contents": " [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public async Task StartWorkerAsync(WorkerConfig worker, string workerType)\n {\n await DeployControllerAsync(worker);\n\n string payloadScript;\n if (workerType == \"sheepit\")\n {\n payloadScript = GenerateSheepItPayload(worker);\n }\n else if (workerType == \"flamenco\")\n {\n payloadScript = GenerateFlamencoPayload(worker);\n }\n else\n {\n throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));\n }\n\n var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n\n var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";\n var jsonParamsBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(jsonParams));\n\n var ensureScript = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{jsonParamsBase64}')))\n$workerName = $params.WorkerName\n$workerType = $params.WorkerType\n$payloadBase64 = $params.PayloadBase64\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\n$instanceRoot = Join-Path (Join-Path $dataRoot $workerType) $workerName\n$logsRoot = Join-Path $instanceRoot 'logs'\n$stateRoot = Join-Path $instanceRoot 'state'\nNew-Item -ItemType Directory -Path $logsRoot -Force | Out-Null\nNew-Item -ItemType Directory -Path $stateRoot -Force | Out-Null\n$logPath = Join-Path $logsRoot 'worker.log'\n$commandPath = Join-Path $stateRoot 'commands.txt'\n$payloadPath = Join-Path $stateRoot 'payload.ps1'", + "signatures": {}, + "detailedLines": [ + { + "text": " [Console]::Error.WriteLine($stackMsg)", + "lineNumber": 228 + }, + { + "text": " exit 1", + "lineNumber": 229 + }, + { + "text": "}}", + "lineNumber": 230 + }, + { + "text": "\";", + "lineNumber": 231 + }, + { + "text": " }", + "lineNumber": 232 + }, + { + "lineNumber": 233 + }, + { + "text": " public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 234 + }, + { + "text": " {", + "lineNumber": 235 + }, + { + "text": " await DeployControllerAsync(worker);", + "lineNumber": 236 + }, + { + "lineNumber": 237 + }, + { + "text": " string payloadScript;", + "lineNumber": 238 + }, + { + "text": " if (workerType == \"sheepit\")", + "lineNumber": 239 + }, + { + "text": " {", + "lineNumber": 240 + }, + { + "text": " payloadScript = GenerateSheepItPayload(worker);", + "lineNumber": 241 + }, + { + "text": " }", + "lineNumber": 242 + }, + { + "text": " else if (workerType == \"flamenco\")", + "lineNumber": 243 + }, + { + "text": " {", + "lineNumber": 244 + }, + { + "text": " payloadScript = GenerateFlamencoPayload(worker);", + "lineNumber": 245 + }, + { + "text": " }", + "lineNumber": 246 + }, + { + "text": " else", + "lineNumber": 247 + }, + { + "text": " {", + "lineNumber": 248 + }, + { + "text": " throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));", + "lineNumber": 249 + }, + { + "text": " }", + "lineNumber": 250 + }, + { + "lineNumber": 251 + }, + { + "text": " var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));", + "lineNumber": 252 + }, + { + "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);", + "lineNumber": 253 + }, + { + "lineNumber": 254 + }, + { + "text": " var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";", + "lineNumber": 255 + }, + { + "text": " var jsonParamsBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(jsonParams));", + "lineNumber": 256 + }, + { + "lineNumber": 257 + }, + { + "text": " var ensureScript = $@\"", + "lineNumber": 258 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 259 + }, + { + "text": "$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{jsonParamsBase64}')))", + "lineNumber": 260 + }, + { + "text": "$workerName = $params.WorkerName", + "lineNumber": 261 + }, + { + "text": "$workerType = $params.WorkerType", + "lineNumber": 262 + }, + { + "text": "$payloadBase64 = $params.PayloadBase64", + "lineNumber": 263 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 264 + }, + { + "text": "$instanceRoot = Join-Path (Join-Path $dataRoot $workerType) $workerName", + "lineNumber": 265 + }, + { + "text": "$logsRoot = Join-Path $instanceRoot 'logs'", + "lineNumber": 266 + }, + { + "text": "$stateRoot = Join-Path $instanceRoot 'state'", + "lineNumber": 267 + }, + { + "text": "New-Item -ItemType Directory -Path $logsRoot -Force | Out-Null", + "lineNumber": 268 + }, + { + "text": "New-Item -ItemType Directory -Path $stateRoot -Force | Out-Null", + "lineNumber": 269 + }, + { + "text": "$logPath = Join-Path $logsRoot 'worker.log'", + "lineNumber": 270 + }, + { + "text": "$commandPath = Join-Path $stateRoot 'commands.txt'", + "lineNumber": 271 + }, + { + "text": "$payloadPath = Join-Path $stateRoot 'payload.ps1'", + "lineNumber": 272 + } + ] + }, + "score": 0.34487879276275635 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 52, + "column": 16 + } + }, + "contents": "using System;\nusing System.IO;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text;\nusing System.Text.Json;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class WorkerControllerService\n {\n private readonly SshService _sshService;\n private readonly ConfigService _configService;\n private byte[]? _controllerScriptBytes;\n private byte[]? _attachHelperScriptBytes;\n\n public WorkerControllerService(SshService sshService, ConfigService configService)\n {\n _sshService = sshService;\n _configService = configService;\n }\n\n private byte[] GetControllerScriptBytes()\n {\n if (_controllerScriptBytes != null)\n return _controllerScriptBytes;\n\n var assembly = Assembly.GetExecutingAssembly();\n var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_controller.ps1\";\n\n using var stream = assembly.GetManifestResourceStream(resourceName);\n if (stream == null)\n throw new InvalidOperationException($\"Resource {resourceName} not found\");\n\n using var reader = new BinaryReader(stream);\n _controllerScriptBytes = reader.ReadBytes((int)stream.Length);\n return _controllerScriptBytes;\n }\n\n private byte[] GetAttachHelperScriptBytes()\n {\n if (_attachHelperScriptBytes != null)\n return _attachHelperScriptBytes;\n\n var assembly = Assembly.GetExecutingAssembly();\n var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_attach.ps1\";\n\n using var stream = assembly.GetManifestResourceStream(resourceName);\n if (stream == null)\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "using System;", + "lineNumber": 1 + }, + { + "text": "using System.IO;", + "lineNumber": 2 + }, + { + "text": "using System.Linq;", + "lineNumber": 3 + }, + { + "text": "using System.Reflection;", + "lineNumber": 4 + }, + { + "text": "using System.Text;", + "lineNumber": 5 + }, + { + "text": "using System.Text.Json;", + "lineNumber": 6 + }, + { + "text": "using System.Threading;", + "lineNumber": 7 + }, + { + "text": "using System.Threading.Tasks;", + "lineNumber": 8 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 9 + }, + { + "lineNumber": 10 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 11 + }, + { + "text": "{", + "lineNumber": 12 + }, + { + "text": " public class WorkerControllerService", + "lineNumber": 13 + }, + { + "text": " {", + "lineNumber": 14 + }, + { + "text": " private readonly SshService _sshService;", + "lineNumber": 15 + }, + { + "text": " private readonly ConfigService _configService;", + "lineNumber": 16 + }, + { + "text": " private byte[]? _controllerScriptBytes;", + "lineNumber": 17 + }, + { + "text": " private byte[]? _attachHelperScriptBytes;", + "lineNumber": 18 + }, + { + "lineNumber": 19 + }, + { + "text": " public WorkerControllerService(SshService sshService, ConfigService configService)", + "lineNumber": 20 + }, + { + "text": " {", + "lineNumber": 21 + }, + { + "text": " _sshService = sshService;", + "lineNumber": 22 + }, + { + "text": " _configService = configService;", + "lineNumber": 23 + }, + { + "text": " }", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": " private byte[] GetControllerScriptBytes()", + "lineNumber": 26 + }, + { + "text": " {", + "lineNumber": 27 + }, + { + "text": " if (_controllerScriptBytes != null)", + "lineNumber": 28 + }, + { + "text": " return _controllerScriptBytes;", + "lineNumber": 29 + }, + { + "lineNumber": 30 + }, + { + "text": " var assembly = Assembly.GetExecutingAssembly();", + "lineNumber": 31 + }, + { + "text": " var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_controller.ps1\";", + "lineNumber": 32 + }, + { + "lineNumber": 33 + }, + { + "text": " using var stream = assembly.GetManifestResourceStream(resourceName);", + "lineNumber": 34 + }, + { + "text": " if (stream == null)", + "lineNumber": 35 + }, + { + "text": " throw new InvalidOperationException($\"Resource {resourceName} not found\");", + "lineNumber": 36 + }, + { + "lineNumber": 37 + }, + { + "text": " using var reader = new BinaryReader(stream);", + "lineNumber": 38 + }, + { + "text": " _controllerScriptBytes = reader.ReadBytes((int)stream.Length);", + "lineNumber": 39 + }, + { + "text": " return _controllerScriptBytes;", + "lineNumber": 40 + }, + { + "text": " }", + "lineNumber": 41 + }, + { + "lineNumber": 42 + }, + { + "text": " private byte[] GetAttachHelperScriptBytes()", + "lineNumber": 43 + }, + { + "text": " {", + "lineNumber": 44 + }, + { + "text": " if (_attachHelperScriptBytes != null)", + "lineNumber": 45 + }, + { + "text": " return _attachHelperScriptBytes;", + "lineNumber": 46 + }, + { + "lineNumber": 47 + }, + { + "text": " var assembly = Assembly.GetExecutingAssembly();", + "lineNumber": 48 + }, + { + "text": " var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_attach.ps1\";", + "lineNumber": 49 + }, + { + "lineNumber": 50 + }, + { + "text": " using var stream = assembly.GetManifestResourceStream(resourceName);", + "lineNumber": 51 + }, + { + "text": " if (stream == null)", + "lineNumber": 52 + }, + { + "text": " ", + "lineNumber": 53 + } + ] + }, + "score": 0.32897675037384033 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 150 + }, + "endPosition": { + "line": 216 + } + }, + "contents": " \"-o\", \"ServerAliveCountMax=30\"\n };\n\n if (interactive && parts.RequestPty)\n {\n args.Add(\"-t\");\n }\n else if (!interactive)\n {\n args.Add(\"-T\");\n }\n\n args.AddRange(parts.Options);\n\n if (parts.Port.HasValue)\n {\n args.Add(\"-p\");\n args.Add(parts.Port.Value.ToString());\n }\n\n args.Add(parts.Host);\n return args;\n }\n\n public List<string> BuildScpArgs(SshConnectionParts parts)\n {\n var args = new List<string>\n {\n \"-o\", \"ServerAliveInterval=60\",\n \"-o\", \"ServerAliveCountMax=30\"\n };\n\n args.AddRange(parts.Options);\n\n if (parts.Port.HasValue)\n {\n args.Add(\"-P\");\n args.Add(parts.Port.Value.ToString());\n }\n\n return args;\n }\n\n public async Task<string> ExecuteRemoteCommandAsync(WorkerConfig worker, string command, bool interactive = false)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var sshArgs = BuildSshArgs(parts, interactive);\n sshArgs.Add(command);\n\n var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));\n \n var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetSshExecutable(),\n WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),\n Arguments = argsString,\n UseShellExecute = false,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = !interactive\n }\n };\n\n var output = new StringBuilder();", + "signatures": {}, + "detailedLines": [ + { + "text": " \"-o\", \"ServerAliveCountMax=30\"", + "lineNumber": 151 + }, + { + "text": " };", + "lineNumber": 152 + }, + { + "lineNumber": 153 + }, + { + "text": " if (interactive && parts.RequestPty)", + "lineNumber": 154 + }, + { + "text": " {", + "lineNumber": 155 + }, + { + "text": " args.Add(\"-t\");", + "lineNumber": 156 + }, + { + "text": " }", + "lineNumber": 157 + }, + { + "text": " else if (!interactive)", + "lineNumber": 158 + }, + { + "text": " {", + "lineNumber": 159 + }, + { + "text": " args.Add(\"-T\");", + "lineNumber": 160 + }, + { + "text": " }", + "lineNumber": 161 + }, + { + "lineNumber": 162 + }, + { + "text": " args.AddRange(parts.Options);", + "lineNumber": 163 + }, + { + "lineNumber": 164 + }, + { + "text": " if (parts.Port.HasValue)", + "lineNumber": 165 + }, + { + "text": " {", + "lineNumber": 166 + }, + { + "text": " args.Add(\"-p\");", + "lineNumber": 167 + }, + { + "text": " args.Add(parts.Port.Value.ToString());", + "lineNumber": 168 + }, + { + "text": " }", + "lineNumber": 169 + }, + { + "lineNumber": 170 + }, + { + "text": " args.Add(parts.Host);", + "lineNumber": 171 + }, + { + "text": " return args;", + "lineNumber": 172 + }, + { + "text": " }", + "lineNumber": 173 + }, + { + "lineNumber": 174 + }, + { + "text": " public List<string> BuildScpArgs(SshConnectionParts parts)", + "lineNumber": 175 + }, + { + "text": " {", + "lineNumber": 176 + }, + { + "text": " var args = new List<string>", + "lineNumber": 177 + }, + { + "text": " {", + "lineNumber": 178 + }, + { + "text": " \"-o\", \"ServerAliveInterval=60\",", + "lineNumber": 179 + }, + { + "text": " \"-o\", \"ServerAliveCountMax=30\"", + "lineNumber": 180 + }, + { + "text": " };", + "lineNumber": 181 + }, + { + "lineNumber": 182 + }, + { + "text": " args.AddRange(parts.Options);", + "lineNumber": 183 + }, + { + "lineNumber": 184 + }, + { + "text": " if (parts.Port.HasValue)", + "lineNumber": 185 + }, + { + "text": " {", + "lineNumber": 186 + }, + { + "text": " args.Add(\"-P\");", + "lineNumber": 187 + }, + { + "text": " args.Add(parts.Port.Value.ToString());", + "lineNumber": 188 + }, + { + "text": " }", + "lineNumber": 189 + }, + { + "lineNumber": 190 + }, + { + "text": " return args;", + "lineNumber": 191 + }, + { + "text": " }", + "lineNumber": 192 + }, + { + "lineNumber": 193 + }, + { + "text": " public async Task<string> ExecuteRemoteCommandAsync(WorkerConfig worker, string command, bool interactive = false)", + "lineNumber": 194 + }, + { + "text": " {", + "lineNumber": 195 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 196 + }, + { + "text": " var sshArgs = BuildSshArgs(parts, interactive);", + "lineNumber": 197 + }, + { + "text": " sshArgs.Add(command);", + "lineNumber": 198 + }, + { + "lineNumber": 199 + }, + { + "text": " var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));", + "lineNumber": 200 + }, + { + "text": " ", + "lineNumber": 201 + }, + { + "text": " var process = new Process", + "lineNumber": 202 + }, + { + "text": " {", + "lineNumber": 203 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 204 + }, + { + "text": " {", + "lineNumber": 205 + }, + { + "text": " FileName = GetSshExecutable(),", + "lineNumber": 206 + }, + { + "text": " WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),", + "lineNumber": 207 + }, + { + "text": " Arguments = argsString,", + "lineNumber": 208 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 209 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 210 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 211 + }, + { + "text": " CreateNoWindow = !interactive", + "lineNumber": 212 + }, + { + "text": " }", + "lineNumber": 213 + }, + { + "text": " };", + "lineNumber": 214 + }, + { + "lineNumber": 215 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 216 + } + ] + }, + "score": 0.3250424861907959 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 88 + }, + "endPosition": { + "line": 141 + } + }, + "contents": " {\n if (worker.WorkerTypes.SheepIt == null)\n throw new InvalidOperationException(\"Worker does not have SheepIt configuration\");\n\n var config = _configService.Load();\n var sheepIt = worker.WorkerTypes.SheepIt;\n var globalSettings = config.GlobalSettings;\n var safeKey = globalSettings.SheepItRenderKey.Replace(\"'\", \"''\");\n var safeUser = globalSettings.SheepItUsername.Replace(\"'\", \"''\");\n var urls = globalSettings.SheepItJarUrls;\n var urlLiteral = \"@(\" + string.Join(\", \", Array.ConvertAll(urls.ToArray(), url => $\"'{url}'\")) + \")\";\n\n return $@\"\n$ProgressPreference = 'SilentlyContinue'\n$ErrorActionPreference = 'Stop'\n\n# Write startup message to stderr so controller can capture it\n[Console]::Error.WriteLine('[SHEEPIT] Payload script starting...')\n\ntry {{\n $appData = [Environment]::GetFolderPath('ApplicationData')\n $sheepDir = Join-Path $appData 'sheepit'\n if (-not (Test-Path $sheepDir)) {{\n New-Item -Path $sheepDir -ItemType Directory -Force | Out-Null\n }}\n\n $jarPath = Join-Path $sheepDir 'sheepit-client.jar'\n $urls = {urlLiteral}\n $headers = @{{ 'User-Agent' = 'Mozilla/5.0' }}\n\n if (Test-Path $jarPath) {{\n Write-Host \"\"SheepIt client already present at $jarPath. Skipping download.\"\" -ForegroundColor Green\n }}\n else {{\n $downloaded = $false\n\n foreach ($url in $urls) {{\n Write-Host \"\"Downloading SheepIt client from $url...\"\" -ForegroundColor Cyan\n try {{\n Invoke-WebRequest -Uri $url -OutFile $jarPath -UseBasicParsing -Headers $headers\n $downloaded = $true\n Write-Host \"\"Download complete.\"\" -ForegroundColor Green\n break\n }}\n catch {{\n Write-Host (\"\"Download failed from {{0}}: {{1}}\"\" -f $url, $_.Exception.Message) -ForegroundColor Yellow\n }}\n }}\n\n if (-not $downloaded) {{\n throw 'Unable to download SheepIt client from any known URL.'\n }}\n }}", + "signatures": {}, + "detailedLines": [ + { + "text": " {", + "lineNumber": 89 + }, + { + "text": " if (worker.WorkerTypes.SheepIt == null)", + "lineNumber": 90 + }, + { + "text": " throw new InvalidOperationException(\"Worker does not have SheepIt configuration\");", + "lineNumber": 91 + }, + { + "lineNumber": 92 + }, + { + "text": " var config = _configService.Load();", + "lineNumber": 93 + }, + { + "text": " var sheepIt = worker.WorkerTypes.SheepIt;", + "lineNumber": 94 + }, + { + "text": " var globalSettings = config.GlobalSettings;", + "lineNumber": 95 + }, + { + "text": " var safeKey = globalSettings.SheepItRenderKey.Replace(\"'\", \"''\");", + "lineNumber": 96 + }, + { + "text": " var safeUser = globalSettings.SheepItUsername.Replace(\"'\", \"''\");", + "lineNumber": 97 + }, + { + "text": " var urls = globalSettings.SheepItJarUrls;", + "lineNumber": 98 + }, + { + "text": " var urlLiteral = \"@(\" + string.Join(\", \", Array.ConvertAll(urls.ToArray(), url => $\"'{url}'\")) + \")\";", + "lineNumber": 99 + }, + { + "lineNumber": 100 + }, + { + "text": " return $@\"", + "lineNumber": 101 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 102 + }, + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 103 + }, + { + "lineNumber": 104 + }, + { + "text": "# Write startup message to stderr so controller can capture it", + "lineNumber": 105 + }, + { + "text": "[Console]::Error.WriteLine('[SHEEPIT] Payload script starting...')", + "lineNumber": 106 + }, + { + "lineNumber": 107 + }, + { + "text": "try {{", + "lineNumber": 108 + }, + { + "text": " $appData = [Environment]::GetFolderPath('ApplicationData')", + "lineNumber": 109 + }, + { + "text": " $sheepDir = Join-Path $appData 'sheepit'", + "lineNumber": 110 + }, + { + "text": " if (-not (Test-Path $sheepDir)) {{", + "lineNumber": 111 + }, + { + "text": " New-Item -Path $sheepDir -ItemType Directory -Force | Out-Null", + "lineNumber": 112 + }, + { + "text": " }}", + "lineNumber": 113 + }, + { + "lineNumber": 114 + }, + { + "text": " $jarPath = Join-Path $sheepDir 'sheepit-client.jar'", + "lineNumber": 115 + }, + { + "text": " $urls = {urlLiteral}", + "lineNumber": 116 + }, + { + "text": " $headers = @{{ 'User-Agent' = 'Mozilla/5.0' }}", + "lineNumber": 117 + }, + { + "lineNumber": 118 + }, + { + "text": " if (Test-Path $jarPath) {{", + "lineNumber": 119 + }, + { + "text": " Write-Host \"\"SheepIt client already present at $jarPath. Skipping download.\"\" -ForegroundColor Green", + "lineNumber": 120 + }, + { + "text": " }}", + "lineNumber": 121 + }, + { + "text": " else {{", + "lineNumber": 122 + }, + { + "text": " $downloaded = $false", + "lineNumber": 123 + }, + { + "lineNumber": 124 + }, + { + "text": " foreach ($url in $urls) {{", + "lineNumber": 125 + }, + { + "text": " Write-Host \"\"Downloading SheepIt client from $url...\"\" -ForegroundColor Cyan", + "lineNumber": 126 + }, + { + "text": " try {{", + "lineNumber": 127 + }, + { + "text": " Invoke-WebRequest -Uri $url -OutFile $jarPath -UseBasicParsing -Headers $headers", + "lineNumber": 128 + }, + { + "text": " $downloaded = $true", + "lineNumber": 129 + }, + { + "text": " Write-Host \"\"Download complete.\"\" -ForegroundColor Green", + "lineNumber": 130 + }, + { + "text": " break", + "lineNumber": 131 + }, + { + "text": " }}", + "lineNumber": 132 + }, + { + "text": " catch {{", + "lineNumber": 133 + }, + { + "text": " Write-Host (\"\"Download failed from {{0}}: {{1}}\"\" -f $url, $_.Exception.Message) -ForegroundColor Yellow", + "lineNumber": 134 + }, + { + "text": " }}", + "lineNumber": 135 + }, + { + "text": " }}", + "lineNumber": 136 + }, + { + "lineNumber": 137 + }, + { + "text": " if (-not $downloaded) {{", + "lineNumber": 138 + }, + { + "text": " throw 'Unable to download SheepIt client from any known URL.'", + "lineNumber": 139 + }, + { + "text": " }}", + "lineNumber": 140 + }, + { + "text": " }}", + "lineNumber": 141 + } + ] + }, + "score": 0.3183058500289917 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 318 + }, + "endPosition": { + "line": 354, + "column": 16 + } + }, + "contents": " else {{\n $psExe = (Get-Command powershell -ErrorAction Stop).Source\n }}\n\n $controllerArgs = @(\n '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',\n '-File',\"\"$controllerPath\"\",\n '-WorkerName',\"\"$workerName\"\",\n '-WorkerType',\"\"$workerType\"\",\n '-PayloadBase64Path',\"\"$payloadBase64Path\"\"\n )\n\n Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden | Out-Null\n Write-Host \"\"Worker $workerName started under controller.\"\" -ForegroundColor Green\n}}\n\";\n\n // Pipe script through stdin to avoid command line length limits\n await _sshService.ExecuteRemoteScriptAsync(worker, ensureScript);\n\n // Wait a moment for the controller to start and update metadata\n await Task.Delay(2000);\n\n // Verify the worker actually started by checking metadata\n // Try up to 3 times with delays to account for slower startup\n WorkerStatus? status = null;\n for (int i = 0; i < 3; i++)\n {\n status = await GetWorkerStatusAsync(worker, workerType);\n if (status != null && (status.Status == \"running\" || status.Status == \"launching\"))\n {\n break;\n }\n if (i < 2) // Don't delay on last attempt\n {\n await Task.Delay(1000);\n ", + "signatures": {}, + "detailedLines": [ + { + "text": " else {{", + "lineNumber": 319 + }, + { + "text": " $psExe = (Get-Command powershell -ErrorAction Stop).Source", + "lineNumber": 320 + }, + { + "text": " }}", + "lineNumber": 321 + }, + { + "lineNumber": 322 + }, + { + "text": " $controllerArgs = @(", + "lineNumber": 323 + }, + { + "text": " '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',", + "lineNumber": 324 + }, + { + "text": " '-File',\"\"$controllerPath\"\",", + "lineNumber": 325 + }, + { + "text": " '-WorkerName',\"\"$workerName\"\",", + "lineNumber": 326 + }, + { + "text": " '-WorkerType',\"\"$workerType\"\",", + "lineNumber": 327 + }, + { + "text": " '-PayloadBase64Path',\"\"$payloadBase64Path\"\"", + "lineNumber": 328 + }, + { + "text": " )", + "lineNumber": 329 + }, + { + "lineNumber": 330 + }, + { + "text": " Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden | Out-Null", + "lineNumber": 331 + }, + { + "text": " Write-Host \"\"Worker $workerName started under controller.\"\" -ForegroundColor Green", + "lineNumber": 332 + }, + { + "text": "}}", + "lineNumber": 333 + }, + { + "text": "\";", + "lineNumber": 334 + }, + { + "lineNumber": 335 + }, + { + "text": " // Pipe script through stdin to avoid command line length limits", + "lineNumber": 336 + }, + { + "text": " await _sshService.ExecuteRemoteScriptAsync(worker, ensureScript);", + "lineNumber": 337 + }, + { + "lineNumber": 338 + }, + { + "text": " // Wait a moment for the controller to start and update metadata", + "lineNumber": 339 + }, + { + "text": " await Task.Delay(2000);", + "lineNumber": 340 + }, + { + "lineNumber": 341 + }, + { + "text": " // Verify the worker actually started by checking metadata", + "lineNumber": 342 + }, + { + "text": " // Try up to 3 times with delays to account for slower startup", + "lineNumber": 343 + }, + { + "text": " WorkerStatus? status = null;", + "lineNumber": 344 + }, + { + "text": " for (int i = 0; i < 3; i++)", + "lineNumber": 345 + }, + { + "text": " {", + "lineNumber": 346 + }, + { + "text": " status = await GetWorkerStatusAsync(worker, workerType);", + "lineNumber": 347 + }, + { + "text": " if (status != null && (status.Status == \"running\" || status.Status == \"launching\"))", + "lineNumber": 348 + }, + { + "text": " {", + "lineNumber": 349 + }, + { + "text": " break;", + "lineNumber": 350 + }, + { + "text": " }", + "lineNumber": 351 + }, + { + "text": " if (i < 2) // Don't delay on last attempt", + "lineNumber": 352 + }, + { + "text": " {", + "lineNumber": 353 + }, + { + "text": " await Task.Delay(1000);", + "lineNumber": 354 + }, + { + "text": " ", + "lineNumber": 355 + } + ] + }, + "score": 0.29555240273475647 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 49 + }, + "endPosition": { + "line": 90, + "column": 16 + } + }, + "contents": " using var stream = assembly.GetManifestResourceStream(resourceName);\n if (stream == null)\n throw new InvalidOperationException($\"Resource {resourceName} not found\");\n\n using var reader = new BinaryReader(stream);\n _attachHelperScriptBytes = reader.ReadBytes((int)stream.Length);\n return _attachHelperScriptBytes;\n }\n\n public async Task DeployControllerAsync(WorkerConfig worker)\n {\n var controllerBase64 = Convert.ToBase64String(GetControllerScriptBytes());\n var script = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\nNew-Item -ItemType Directory -Path $dataRoot -Force | Out-Null\n$controllerPath = Join-Path $dataRoot 'controller.ps1'\n[IO.File]::WriteAllBytes($controllerPath, [Convert]::FromBase64String('{controllerBase64}'))\n\";\n\n await _sshService.ExecuteRemoteScriptAsync(worker, script);\n }\n\n public async Task DeployAttachHelperAsync(WorkerConfig worker)\n {\n var helperBase64 = Convert.ToBase64String(GetAttachHelperScriptBytes());\n var script = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\nNew-Item -ItemType Directory -Path $dataRoot -Force | Out-Null\n$attachPath = Join-Path $dataRoot 'attach-helper.ps1'\n[IO.File]::WriteAllBytes($attachPath, [Convert]::FromBase64String('{helperBase64}'))\n\";\n\n await _sshService.ExecuteRemoteScriptAsync(worker, script);\n }\n\n public string GenerateSheepItPayload(WorkerConfig worker)\n {\n if (worker.WorkerTypes.SheepIt == null)\n ", + "signatures": {}, + "detailedLines": [ + { + "text": " using var stream = assembly.GetManifestResourceStream(resourceName);", + "lineNumber": 51 + }, + { + "text": " if (stream == null)", + "lineNumber": 52 + }, + { + "text": " throw new InvalidOperationException($\"Resource {resourceName} not found\");", + "lineNumber": 53 + }, + { + "lineNumber": 54 + }, + { + "text": " using var reader = new BinaryReader(stream);", + "lineNumber": 55 + }, + { + "text": " _attachHelperScriptBytes = reader.ReadBytes((int)stream.Length);", + "lineNumber": 56 + }, + { + "text": " return _attachHelperScriptBytes;", + "lineNumber": 57 + }, + { + "text": " }", + "lineNumber": 58 + }, + { + "lineNumber": 59 + }, + { + "text": " public async Task DeployControllerAsync(WorkerConfig worker)", + "lineNumber": 60 + }, + { + "text": " {", + "lineNumber": 61 + }, + { + "text": " var controllerBase64 = Convert.ToBase64String(GetControllerScriptBytes());", + "lineNumber": 62 + }, + { + "text": " var script = $@\"", + "lineNumber": 63 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 64 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 65 + }, + { + "text": "New-Item -ItemType Directory -Path $dataRoot -Force | Out-Null", + "lineNumber": 66 + }, + { + "text": "$controllerPath = Join-Path $dataRoot 'controller.ps1'", + "lineNumber": 67 + }, + { + "text": "[IO.File]::WriteAllBytes($controllerPath, [Convert]::FromBase64String('{controllerBase64}'))", + "lineNumber": 68 + }, + { + "text": "\";", + "lineNumber": 69 + }, + { + "lineNumber": 70 + }, + { + "text": " await _sshService.ExecuteRemoteScriptAsync(worker, script);", + "lineNumber": 71 + }, + { + "text": " }", + "lineNumber": 72 + }, + { + "lineNumber": 73 + }, + { + "text": " public async Task DeployAttachHelperAsync(WorkerConfig worker)", + "lineNumber": 74 + }, + { + "text": " {", + "lineNumber": 75 + }, + { + "text": " var helperBase64 = Convert.ToBase64String(GetAttachHelperScriptBytes());", + "lineNumber": 76 + }, + { + "text": " var script = $@\"", + "lineNumber": 77 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 78 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 79 + }, + { + "text": "New-Item -ItemType Directory -Path $dataRoot -Force | Out-Null", + "lineNumber": 80 + }, + { + "text": "$attachPath = Join-Path $dataRoot 'attach-helper.ps1'", + "lineNumber": 81 + }, + { + "text": "[IO.File]::WriteAllBytes($attachPath, [Convert]::FromBase64String('{helperBase64}'))", + "lineNumber": 82 + }, + { + "text": "\";", + "lineNumber": 83 + }, + { + "lineNumber": 84 + }, + { + "text": " await _sshService.ExecuteRemoteScriptAsync(worker, script);", + "lineNumber": 85 + }, + { + "text": " }", + "lineNumber": 86 + }, + { + "lineNumber": 87 + }, + { + "text": " public string GenerateSheepItPayload(WorkerConfig worker)", + "lineNumber": 88 + }, + { + "text": " {", + "lineNumber": 89 + }, + { + "text": " if (worker.WorkerTypes.SheepIt == null)", + "lineNumber": 90 + }, + { + "text": " ", + "lineNumber": 91 + } + ] + }, + "score": 0.27579283714294434 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 45 + } + }, + "contents": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Text;\nusing System.Threading.Tasks;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class SshConnectionParts\n {\n public string Host { get; set; } = string.Empty;\n public List<string> Options { get; set; } = new();\n public int? Port { get; set; }\n public bool RequestPty { get; set; }\n }\n\n public class SshService\n {\n private readonly Dictionary<string, string> _workerBasePathCache = new();\n\n private static string GetSshExecutable()\n {\n if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))\n {\n // Try common Windows OpenSSH locations\n var paths = new[]\n {\n Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"ssh.exe\"),\n \"ssh.exe\" // In PATH\n };\n foreach (var path in paths)\n {\n if (File.Exists(path) || IsInPath(\"ssh.exe\"))\n return \"ssh.exe\";\n }\n return \"ssh.exe\";\n }\n return \"ssh\";\n }\n\n private static string GetScpExecutable()", + "signatures": {}, + "detailedLines": [ + { + "text": "using System;", + "lineNumber": 1 + }, + { + "text": "using System.Collections.Generic;", + "lineNumber": 2 + }, + { + "text": "using System.Diagnostics;", + "lineNumber": 3 + }, + { + "text": "using System.IO;", + "lineNumber": 4 + }, + { + "text": "using System.Linq;", + "lineNumber": 5 + }, + { + "text": "using System.Runtime.InteropServices;", + "lineNumber": 6 + }, + { + "text": "using System.Text;", + "lineNumber": 7 + }, + { + "text": "using System.Threading.Tasks;", + "lineNumber": 8 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 9 + }, + { + "lineNumber": 10 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 11 + }, + { + "text": "{", + "lineNumber": 12 + }, + { + "text": " public class SshConnectionParts", + "lineNumber": 13 + }, + { + "text": " {", + "lineNumber": 14 + }, + { + "text": " public string Host { get; set; } = string.Empty;", + "lineNumber": 15 + }, + { + "text": " public List<string> Options { get; set; } = new();", + "lineNumber": 16 + }, + { + "text": " public int? Port { get; set; }", + "lineNumber": 17 + }, + { + "text": " public bool RequestPty { get; set; }", + "lineNumber": 18 + }, + { + "text": " }", + "lineNumber": 19 + }, + { + "lineNumber": 20 + }, + { + "text": " public class SshService", + "lineNumber": 21 + }, + { + "text": " {", + "lineNumber": 22 + }, + { + "text": " private readonly Dictionary<string, string> _workerBasePathCache = new();", + "lineNumber": 23 + }, + { + "lineNumber": 24 + }, + { + "text": " private static string GetSshExecutable()", + "lineNumber": 25 + }, + { + "text": " {", + "lineNumber": 26 + }, + { + "text": " if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))", + "lineNumber": 27 + }, + { + "text": " {", + "lineNumber": 28 + }, + { + "text": " // Try common Windows OpenSSH locations", + "lineNumber": 29 + }, + { + "text": " var paths = new[]", + "lineNumber": 30 + }, + { + "text": " {", + "lineNumber": 31 + }, + { + "text": " Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"ssh.exe\"),", + "lineNumber": 32 + }, + { + "text": " \"ssh.exe\" // In PATH", + "lineNumber": 33 + }, + { + "text": " };", + "lineNumber": 34 + }, + { + "text": " foreach (var path in paths)", + "lineNumber": 35 + }, + { + "text": " {", + "lineNumber": 36 + }, + { + "text": " if (File.Exists(path) || IsInPath(\"ssh.exe\"))", + "lineNumber": 37 + }, + { + "text": " return \"ssh.exe\";", + "lineNumber": 38 + }, + { + "text": " }", + "lineNumber": 39 + }, + { + "text": " return \"ssh.exe\";", + "lineNumber": 40 + }, + { + "text": " }", + "lineNumber": 41 + }, + { + "text": " return \"ssh\";", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": " private static string GetScpExecutable()", + "lineNumber": 45 + } + ] + }, + "score": 0.252701997756958 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\ConfigService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 62 + } + }, + "contents": "using System;\nusing System.IO;\nusing System.Linq;\nusing System.Text.Json;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class ConfigService\n {\n private static readonly JsonSerializerOptions JsonOptions = new()\n {\n WriteIndented = true,\n PropertyNamingPolicy = JsonNamingPolicy.CamelCase\n };\n\n private readonly string _configPath;\n private ConfigRoot? _config;\n\n public ConfigService()\n {\n var appDataPath = GetAppDataPath();\n Directory.CreateDirectory(appDataPath);\n _configPath = Path.Combine(appDataPath, \"workers.json\");\n }\n\n private static string GetAppDataPath()\n {\n var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);\n return Path.Combine(localAppData, \"UnifiedFarmLauncher\");\n }\n\n public ConfigRoot Load()\n {\n if (_config != null)\n return _config;\n\n if (!File.Exists(_configPath))\n {\n _config = new ConfigRoot();\n Save(_config);\n return _config;\n }\n\n try\n {\n var json = File.ReadAllText(_configPath);\n _config = JsonSerializer.Deserialize<ConfigRoot>(json, JsonOptions) ?? new ConfigRoot();\n return _config;\n }\n catch (Exception ex)\n {\n throw new InvalidOperationException($\"Failed to load configuration from {_configPath}: {ex.Message}\", ex);\n }\n }\n\n public void Save(ConfigRoot? config = null)\n {\n config ??= _config ?? new ConfigRoot();\n _config = config;\n\n try", + "signatures": {}, + "detailedLines": [ + { + "text": "using System;", + "lineNumber": 1 + }, + { + "text": "using System.IO;", + "lineNumber": 2 + }, + { + "text": "using System.Linq;", + "lineNumber": 3 + }, + { + "text": "using System.Text.Json;", + "lineNumber": 4 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 5 + }, + { + "lineNumber": 6 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 7 + }, + { + "text": "{", + "lineNumber": 8 + }, + { + "text": " public class ConfigService", + "lineNumber": 9 + }, + { + "text": " {", + "lineNumber": 10 + }, + { + "text": " private static readonly JsonSerializerOptions JsonOptions = new()", + "lineNumber": 11 + }, + { + "text": " {", + "lineNumber": 12 + }, + { + "text": " WriteIndented = true,", + "lineNumber": 13 + }, + { + "text": " PropertyNamingPolicy = JsonNamingPolicy.CamelCase", + "lineNumber": 14 + }, + { + "text": " };", + "lineNumber": 15 + }, + { + "lineNumber": 16 + }, + { + "text": " private readonly string _configPath;", + "lineNumber": 17 + }, + { + "text": " private ConfigRoot? _config;", + "lineNumber": 18 + }, + { + "lineNumber": 19 + }, + { + "text": " public ConfigService()", + "lineNumber": 20 + }, + { + "text": " {", + "lineNumber": 21 + }, + { + "text": " var appDataPath = GetAppDataPath();", + "lineNumber": 22 + }, + { + "text": " Directory.CreateDirectory(appDataPath);", + "lineNumber": 23 + }, + { + "text": " _configPath = Path.Combine(appDataPath, \"workers.json\");", + "lineNumber": 24 + }, + { + "text": " }", + "lineNumber": 25 + }, + { + "lineNumber": 26 + }, + { + "text": " private static string GetAppDataPath()", + "lineNumber": 27 + }, + { + "text": " {", + "lineNumber": 28 + }, + { + "text": " var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);", + "lineNumber": 29 + }, + { + "text": " return Path.Combine(localAppData, \"UnifiedFarmLauncher\");", + "lineNumber": 30 + }, + { + "text": " }", + "lineNumber": 31 + }, + { + "lineNumber": 32 + }, + { + "text": " public ConfigRoot Load()", + "lineNumber": 33 + }, + { + "text": " {", + "lineNumber": 34 + }, + { + "text": " if (_config != null)", + "lineNumber": 35 + }, + { + "text": " return _config;", + "lineNumber": 36 + }, + { + "lineNumber": 37 + }, + { + "text": " if (!File.Exists(_configPath))", + "lineNumber": 38 + }, + { + "text": " {", + "lineNumber": 39 + }, + { + "text": " _config = new ConfigRoot();", + "lineNumber": 40 + }, + { + "text": " Save(_config);", + "lineNumber": 41 + }, + { + "text": " return _config;", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": " try", + "lineNumber": 45 + }, + { + "text": " {", + "lineNumber": 46 + }, + { + "text": " var json = File.ReadAllText(_configPath);", + "lineNumber": 47 + }, + { + "text": " _config = JsonSerializer.Deserialize<ConfigRoot>(json, JsonOptions) ?? new ConfigRoot();", + "lineNumber": 48 + }, + { + "text": " return _config;", + "lineNumber": 49 + }, + { + "text": " }", + "lineNumber": 50 + }, + { + "text": " catch (Exception ex)", + "lineNumber": 51 + }, + { + "text": " {", + "lineNumber": 52 + }, + { + "text": " throw new InvalidOperationException($\"Failed to load configuration from {_configPath}: {ex.Message}\", ex);", + "lineNumber": 53 + }, + { + "text": " }", + "lineNumber": 54 + }, + { + "text": " }", + "lineNumber": 55 + }, + { + "lineNumber": 56 + }, + { + "text": " public void Save(ConfigRoot? config = null)", + "lineNumber": 57 + }, + { + "text": " {", + "lineNumber": 58 + }, + { + "text": " config ??= _config ?? new ConfigRoot();", + "lineNumber": 59 + }, + { + "text": " _config = config;", + "lineNumber": 60 + }, + { + "lineNumber": 61 + }, + { + "text": " try", + "lineNumber": 62 + } + ] + }, + "score": 0.2162950038909912 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\ConfigService.cs", + "range": { + "startPosition": { + "line": 59 + }, + "endPosition": { + "line": 122, + "column": 12 + } + }, + "contents": " _config = config;\n\n try\n {\n var json = JsonSerializer.Serialize(config, JsonOptions);\n File.WriteAllText(_configPath, json);\n }\n catch (Exception ex)\n {\n throw new InvalidOperationException($\"Failed to save configuration to {_configPath}: {ex.Message}\", ex);\n }\n }\n\n public void Reload()\n {\n _config = null;\n Load();\n }\n\n public WorkerConfig? GetWorker(int id)\n {\n return Load().Workers.FirstOrDefault(w => w.Id == id);\n }\n\n public WorkerConfig? GetWorkerByName(string name)\n {\n return Load().Workers.FirstOrDefault(w => w.Name == name);\n }\n\n public void AddWorker(WorkerConfig worker)\n {\n var config = Load();\n if (config.Workers.Any(w => w.Id == worker.Id || w.Name == worker.Name))\n throw new InvalidOperationException($\"Worker with ID {worker.Id} or name '{worker.Name}' already exists\");\n\n config.Workers.Add(worker);\n Save(config);\n }\n\n public void UpdateWorker(WorkerConfig worker)\n {\n var config = Load();\n var index = config.Workers.FindIndex(w => w.Id == worker.Id);\n if (index < 0)\n throw new InvalidOperationException($\"Worker with ID {worker.Id} not found\");\n\n config.Workers[index] = worker;\n Save(config);\n }\n\n public void DeleteWorker(int id)\n {\n var config = Load();\n var worker = config.Workers.FirstOrDefault(w => w.Id == id);\n if (worker == null)\n throw new InvalidOperationException($\"Worker with ID {id} not found\");\n\n config.Workers.Remove(worker);\n Save(config);\n }\n\n public int GetNextWorkerId()\n {\n ", + "signatures": {}, + "detailedLines": [ + { + "text": " _config = config;", + "lineNumber": 60 + }, + { + "lineNumber": 61 + }, + { + "text": " try", + "lineNumber": 62 + }, + { + "text": " {", + "lineNumber": 63 + }, + { + "text": " var json = JsonSerializer.Serialize(config, JsonOptions);", + "lineNumber": 64 + }, + { + "text": " File.WriteAllText(_configPath, json);", + "lineNumber": 65 + }, + { + "text": " }", + "lineNumber": 66 + }, + { + "text": " catch (Exception ex)", + "lineNumber": 67 + }, + { + "text": " {", + "lineNumber": 68 + }, + { + "text": " throw new InvalidOperationException($\"Failed to save configuration to {_configPath}: {ex.Message}\", ex);", + "lineNumber": 69 + }, + { + "text": " }", + "lineNumber": 70 + }, + { + "text": " }", + "lineNumber": 71 + }, + { + "lineNumber": 72 + }, + { + "text": " public void Reload()", + "lineNumber": 73 + }, + { + "text": " {", + "lineNumber": 74 + }, + { + "text": " _config = null;", + "lineNumber": 75 + }, + { + "text": " Load();", + "lineNumber": 76 + }, + { + "text": " }", + "lineNumber": 77 + }, + { + "lineNumber": 78 + }, + { + "text": " public WorkerConfig? GetWorker(int id)", + "lineNumber": 79 + }, + { + "text": " {", + "lineNumber": 80 + }, + { + "text": " return Load().Workers.FirstOrDefault(w => w.Id == id);", + "lineNumber": 81 + }, + { + "text": " }", + "lineNumber": 82 + }, + { + "lineNumber": 83 + }, + { + "text": " public WorkerConfig? GetWorkerByName(string name)", + "lineNumber": 84 + }, + { + "text": " {", + "lineNumber": 85 + }, + { + "text": " return Load().Workers.FirstOrDefault(w => w.Name == name);", + "lineNumber": 86 + }, + { + "text": " }", + "lineNumber": 87 + }, + { + "lineNumber": 88 + }, + { + "text": " public void AddWorker(WorkerConfig worker)", + "lineNumber": 89 + }, + { + "text": " {", + "lineNumber": 90 + }, + { + "text": " var config = Load();", + "lineNumber": 91 + }, + { + "text": " if (config.Workers.Any(w => w.Id == worker.Id || w.Name == worker.Name))", + "lineNumber": 92 + }, + { + "text": " throw new InvalidOperationException($\"Worker with ID {worker.Id} or name '{worker.Name}' already exists\");", + "lineNumber": 93 + }, + { + "lineNumber": 94 + }, + { + "text": " config.Workers.Add(worker);", + "lineNumber": 95 + }, + { + "text": " Save(config);", + "lineNumber": 96 + }, + { + "text": " }", + "lineNumber": 97 + }, + { + "lineNumber": 98 + }, + { + "text": " public void UpdateWorker(WorkerConfig worker)", + "lineNumber": 99 + }, + { + "text": " {", + "lineNumber": 100 + }, + { + "text": " var config = Load();", + "lineNumber": 101 + }, + { + "text": " var index = config.Workers.FindIndex(w => w.Id == worker.Id);", + "lineNumber": 102 + }, + { + "text": " if (index < 0)", + "lineNumber": 103 + }, + { + "text": " throw new InvalidOperationException($\"Worker with ID {worker.Id} not found\");", + "lineNumber": 104 + }, + { + "lineNumber": 105 + }, + { + "text": " config.Workers[index] = worker;", + "lineNumber": 106 + }, + { + "text": " Save(config);", + "lineNumber": 107 + }, + { + "text": " }", + "lineNumber": 108 + }, + { + "lineNumber": 109 + }, + { + "text": " public void DeleteWorker(int id)", + "lineNumber": 110 + }, + { + "text": " {", + "lineNumber": 111 + }, + { + "text": " var config = Load();", + "lineNumber": 112 + }, + { + "text": " var worker = config.Workers.FirstOrDefault(w => w.Id == id);", + "lineNumber": 113 + }, + { + "text": " if (worker == null)", + "lineNumber": 114 + }, + { + "text": " throw new InvalidOperationException($\"Worker with ID {id} not found\");", + "lineNumber": 115 + }, + { + "lineNumber": 116 + }, + { + "text": " config.Workers.Remove(worker);", + "lineNumber": 117 + }, + { + "text": " Save(config);", + "lineNumber": 118 + }, + { + "text": " }", + "lineNumber": 119 + }, + { + "lineNumber": 120 + }, + { + "text": " public int GetNextWorkerId()", + "lineNumber": 121 + }, + { + "text": " {", + "lineNumber": 122 + }, + { + "text": " ", + "lineNumber": 123 + } + ] + }, + "score": 0.20892786979675293 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 40 + }, + "endPosition": { + "line": 98 + } + }, + "contents": " }\n return \"ssh\";\n }\n\n private static string GetScpExecutable()\n {\n if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))\n {\n var paths = new[]\n {\n Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"scp.exe\"),\n \"scp.exe\"\n };\n foreach (var path in paths)\n {\n if (File.Exists(path) || IsInPath(\"scp.exe\"))\n return \"scp.exe\";\n }\n return \"scp.exe\";\n }\n return \"scp\";\n }\n\n private static bool IsInPath(string executable)\n {\n try\n {\n var process = Process.Start(new ProcessStartInfo\n {\n FileName = executable,\n Arguments = \"-V\",\n UseShellExecute = false,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = true\n });\n process?.WaitForExit(1000);\n return process?.ExitCode == 0 || process?.ExitCode == 1; // SSH/SCP typically exit with 1 for version info\n }\n catch\n {\n return false;\n }\n }\n\n public SshConnectionParts ParseConnectionParts(string rawArgs, string defaultHost)\n {\n var parts = new SshConnectionParts { Host = defaultHost };\n if (string.IsNullOrWhiteSpace(rawArgs))\n return parts;\n\n var tokens = rawArgs.Split(new[] { ' ', '\\t' }, StringSplitOptions.RemoveEmptyEntries);\n var options = new List<string>();\n string? targetHost = null;\n int? port = null;\n bool requestPty = false;\n\n var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };", + "signatures": {}, + "detailedLines": [ + { + "text": " }", + "lineNumber": 41 + }, + { + "text": " return \"ssh\";", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": " private static string GetScpExecutable()", + "lineNumber": 45 + }, + { + "text": " {", + "lineNumber": 46 + }, + { + "text": " if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))", + "lineNumber": 47 + }, + { + "text": " {", + "lineNumber": 48 + }, + { + "text": " var paths = new[]", + "lineNumber": 49 + }, + { + "text": " {", + "lineNumber": 50 + }, + { + "text": " Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"scp.exe\"),", + "lineNumber": 51 + }, + { + "text": " \"scp.exe\"", + "lineNumber": 52 + }, + { + "text": " };", + "lineNumber": 53 + }, + { + "text": " foreach (var path in paths)", + "lineNumber": 54 + }, + { + "text": " {", + "lineNumber": 55 + }, + { + "text": " if (File.Exists(path) || IsInPath(\"scp.exe\"))", + "lineNumber": 56 + }, + { + "text": " return \"scp.exe\";", + "lineNumber": 57 + }, + { + "text": " }", + "lineNumber": 58 + }, + { + "text": " return \"scp.exe\";", + "lineNumber": 59 + }, + { + "text": " }", + "lineNumber": 60 + }, + { + "text": " return \"scp\";", + "lineNumber": 61 + }, + { + "text": " }", + "lineNumber": 62 + }, + { + "lineNumber": 63 + }, + { + "text": " private static bool IsInPath(string executable)", + "lineNumber": 64 + }, + { + "text": " {", + "lineNumber": 65 + }, + { + "text": " try", + "lineNumber": 66 + }, + { + "text": " {", + "lineNumber": 67 + }, + { + "text": " var process = Process.Start(new ProcessStartInfo", + "lineNumber": 68 + }, + { + "text": " {", + "lineNumber": 69 + }, + { + "text": " FileName = executable,", + "lineNumber": 70 + }, + { + "text": " Arguments = \"-V\",", + "lineNumber": 71 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 72 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 73 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 74 + }, + { + "text": " CreateNoWindow = true", + "lineNumber": 75 + }, + { + "text": " });", + "lineNumber": 76 + }, + { + "text": " process?.WaitForExit(1000);", + "lineNumber": 77 + }, + { + "text": " return process?.ExitCode == 0 || process?.ExitCode == 1; // SSH/SCP typically exit with 1 for version info", + "lineNumber": 78 + }, + { + "text": " }", + "lineNumber": 79 + }, + { + "text": " catch", + "lineNumber": 80 + }, + { + "text": " {", + "lineNumber": 81 + }, + { + "text": " return false;", + "lineNumber": 82 + }, + { + "text": " }", + "lineNumber": 83 + }, + { + "text": " }", + "lineNumber": 84 + }, + { + "lineNumber": 85 + }, + { + "text": " public SshConnectionParts ParseConnectionParts(string rawArgs, string defaultHost)", + "lineNumber": 86 + }, + { + "text": " {", + "lineNumber": 87 + }, + { + "text": " var parts = new SshConnectionParts { Host = defaultHost };", + "lineNumber": 88 + }, + { + "text": " if (string.IsNullOrWhiteSpace(rawArgs))", + "lineNumber": 89 + }, + { + "text": " return parts;", + "lineNumber": 90 + }, + { + "lineNumber": 91 + }, + { + "text": " var tokens = rawArgs.Split(new[] { ' ', '\\t' }, StringSplitOptions.RemoveEmptyEntries);", + "lineNumber": 92 + }, + { + "text": " var options = new List<string>();", + "lineNumber": 93 + }, + { + "text": " string? targetHost = null;", + "lineNumber": 94 + }, + { + "text": " int? port = null;", + "lineNumber": 95 + }, + { + "text": " bool requestPty = false;", + "lineNumber": 96 + }, + { + "lineNumber": 97 + }, + { + "text": " var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };", + "lineNumber": 98 + } + ] + }, + "score": 0.19716566801071167 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 342 + }, + "endPosition": { + "line": 361 + } + }, + "contents": " var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetSshExecutable(),\n WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),\n Arguments = argsString,\n UseShellExecute = true,\n CreateNoWindow = false\n }\n };\n\n process.Start();\n return process;\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": " var process = new Process", + "lineNumber": 344 + }, + { + "text": " {", + "lineNumber": 345 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 346 + }, + { + "text": " {", + "lineNumber": 347 + }, + { + "text": " FileName = GetSshExecutable(),", + "lineNumber": 348 + }, + { + "text": " WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),", + "lineNumber": 349 + }, + { + "text": " Arguments = argsString,", + "lineNumber": 350 + }, + { + "text": " UseShellExecute = true,", + "lineNumber": 351 + }, + { + "text": " CreateNoWindow = false", + "lineNumber": 352 + }, + { + "text": " }", + "lineNumber": 353 + }, + { + "text": " };", + "lineNumber": 354 + }, + { + "lineNumber": 355 + }, + { + "text": " process.Start();", + "lineNumber": 356 + }, + { + "text": " return process;", + "lineNumber": 357 + }, + { + "text": " }", + "lineNumber": 358 + }, + { + "text": " }", + "lineNumber": 359 + }, + { + "text": "}", + "lineNumber": 360 + } + ] + }, + "score": 0.16222965717315674 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 95 + }, + "endPosition": { + "line": 154 + } + }, + "contents": " bool requestPty = false;\n\n var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };\n\n for (int i = 0; i < tokens.Length; i++)\n {\n var token = tokens[i];\n if (token == \"-t\" || token == \"-tt\")\n {\n requestPty = true;\n continue;\n }\n\n if (token == \"-p\" && i + 1 < tokens.Length)\n {\n if (int.TryParse(tokens[i + 1], out var portValue))\n {\n port = portValue;\n i++;\n }\n continue;\n }\n\n if (token.StartsWith(\"-\"))\n {\n options.Add(token);\n if (optionsWithArgs.Contains(token) && i + 1 < tokens.Length)\n {\n options.Add(tokens[i + 1]);\n i++;\n }\n continue;\n }\n\n if (targetHost == null)\n {\n targetHost = token;\n continue;\n }\n\n options.Add(token);\n }\n\n parts.Host = targetHost ?? defaultHost;\n parts.Options = options;\n parts.Port = port;\n parts.RequestPty = requestPty;\n return parts;\n }\n\n public List<string> BuildSshArgs(SshConnectionParts parts, bool interactive)\n {\n var args = new List<string>\n {\n \"-o\", \"ServerAliveInterval=60\",\n \"-o\", \"ServerAliveCountMax=30\"\n };\n\n if (interactive && parts.RequestPty)", + "signatures": {}, + "detailedLines": [ + { + "text": " bool requestPty = false;", + "lineNumber": 96 + }, + { + "lineNumber": 97 + }, + { + "text": " var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };", + "lineNumber": 98 + }, + { + "lineNumber": 99 + }, + { + "text": " for (int i = 0; i < tokens.Length; i++)", + "lineNumber": 100 + }, + { + "text": " {", + "lineNumber": 101 + }, + { + "text": " var token = tokens[i];", + "lineNumber": 102 + }, + { + "text": " if (token == \"-t\" || token == \"-tt\")", + "lineNumber": 103 + }, + { + "text": " {", + "lineNumber": 104 + }, + { + "text": " requestPty = true;", + "lineNumber": 105 + }, + { + "text": " continue;", + "lineNumber": 106 + }, + { + "text": " }", + "lineNumber": 107 + }, + { + "lineNumber": 108 + }, + { + "text": " if (token == \"-p\" && i + 1 < tokens.Length)", + "lineNumber": 109 + }, + { + "text": " {", + "lineNumber": 110 + }, + { + "text": " if (int.TryParse(tokens[i + 1], out var portValue))", + "lineNumber": 111 + }, + { + "text": " {", + "lineNumber": 112 + }, + { + "text": " port = portValue;", + "lineNumber": 113 + }, + { + "text": " i++;", + "lineNumber": 114 + }, + { + "text": " }", + "lineNumber": 115 + }, + { + "text": " continue;", + "lineNumber": 116 + }, + { + "text": " }", + "lineNumber": 117 + }, + { + "lineNumber": 118 + }, + { + "text": " if (token.StartsWith(\"-\"))", + "lineNumber": 119 + }, + { + "text": " {", + "lineNumber": 120 + }, + { + "text": " options.Add(token);", + "lineNumber": 121 + }, + { + "text": " if (optionsWithArgs.Contains(token) && i + 1 < tokens.Length)", + "lineNumber": 122 + }, + { + "text": " {", + "lineNumber": 123 + }, + { + "text": " options.Add(tokens[i + 1]);", + "lineNumber": 124 + }, + { + "text": " i++;", + "lineNumber": 125 + }, + { + "text": " }", + "lineNumber": 126 + }, + { + "text": " continue;", + "lineNumber": 127 + }, + { + "text": " }", + "lineNumber": 128 + }, + { + "lineNumber": 129 + }, + { + "text": " if (targetHost == null)", + "lineNumber": 130 + }, + { + "text": " {", + "lineNumber": 131 + }, + { + "text": " targetHost = token;", + "lineNumber": 132 + }, + { + "text": " continue;", + "lineNumber": 133 + }, + { + "text": " }", + "lineNumber": 134 + }, + { + "lineNumber": 135 + }, + { + "text": " options.Add(token);", + "lineNumber": 136 + }, + { + "text": " }", + "lineNumber": 137 + }, + { + "lineNumber": 138 + }, + { + "text": " parts.Host = targetHost ?? defaultHost;", + "lineNumber": 139 + }, + { + "text": " parts.Options = options;", + "lineNumber": 140 + }, + { + "text": " parts.Port = port;", + "lineNumber": 141 + }, + { + "text": " parts.RequestPty = requestPty;", + "lineNumber": 142 + }, + { + "text": " return parts;", + "lineNumber": 143 + }, + { + "text": " }", + "lineNumber": 144 + }, + { + "lineNumber": 145 + }, + { + "text": " public List<string> BuildSshArgs(SshConnectionParts parts, bool interactive)", + "lineNumber": 146 + }, + { + "text": " {", + "lineNumber": 147 + }, + { + "text": " var args = new List<string>", + "lineNumber": 148 + }, + { + "text": " {", + "lineNumber": 149 + }, + { + "text": " \"-o\", \"ServerAliveInterval=60\",", + "lineNumber": 150 + }, + { + "text": " \"-o\", \"ServerAliveCountMax=30\"", + "lineNumber": 151 + }, + { + "text": " };", + "lineNumber": 152 + }, + { + "lineNumber": 153 + }, + { + "text": " if (interactive && parts.RequestPty)", + "lineNumber": 154 + } + ] + }, + "score": 0.13423819839954376 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\ConfigService.cs", + "range": { + "startPosition": { + "line": 119 + }, + "endPosition": { + "line": 128 + } + }, + "contents": " public int GetNextWorkerId()\n {\n var config = Load();\n return config.Workers.Count > 0 ? config.Workers.Max(w => w.Id) + 1 : 1;\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": " public int GetNextWorkerId()", + "lineNumber": 121 + }, + { + "text": " {", + "lineNumber": 122 + }, + { + "text": " var config = Load();", + "lineNumber": 123 + }, + { + "text": " return config.Workers.Count > 0 ? config.Workers.Max(w => w.Id) + 1 : 1;", + "lineNumber": 124 + }, + { + "text": " }", + "lineNumber": 125 + }, + { + "text": " }", + "lineNumber": 126 + }, + { + "text": "}", + "lineNumber": 127 + } + ] + }, + "score": 0.07008743286132812 + } + ] +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Services/SshService.cs + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +Adding a method to read the worker logs and updating the error message to include log details: + + +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "e413d4a2-ac58-424e-9fd8-bcc0fb27e24a" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "96fd4e63-4d97-493e-b7dc-53ae1fc62ee7" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:01.27 + +``` +
+
+ +--- + + +Added log reading functionality. When a worker fails to start, the error message includes the last 20 lines of the log file to help diagnose the issue. + +The next time you try to start a worker and it fails, the error dialog will show: +- The worker status +- Any error messages from the metadata +- The last 20 lines of the worker log file + +This should make it easier to see what's preventing the worker from starting. Try starting the worker again; if it fails, the error message should include the relevant log entries. + +If you want to check the logs manually right now, you can use the Attach button, which will show the full log output in the attach window. + +--- + +_**User**_ + +nothin + +--- + +_**Agent (model default, mode Agent)**_ + + +Enhancing error reporting to capture more diagnostic information, including controller errors and metadata file status: + + +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Scripts/remote_worker_controller.ps1 + +
+
+ +--- + + +Improving error diagnostics to check metadata file existence, controller process status, and capture more details: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "ee87949f-9ece-4fef-9529-d02fa58f5727" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "14610314-e6b0-4319-ae0f-b8f6a7cdc573" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:01.21 + +``` +
+
+ +--- + + +Enhanced error reporting now includes: + +1. **File existence checks**: Whether the metadata file, log file, and controller script exist +2. **Metadata details**: Status, controller PID, worker PID, and any error messages +3. **Process status**: Whether the controller process is running +4. **Recent log entries**: Last 30 lines of the log file + +When a worker fails to start, the error dialog will show these diagnostics to help identify the issue. Try starting the worker again; the error message should include this information. + +--- + +_**User**_ + + + +--- + +_**Agent (model default, mode Agent)**_ + + +Diagnostics show: +- Metadata file exists with status "launching" +- Controller PID and Worker PID are empty +- Log file exists but is empty + +This suggests the controller process may not be starting or is crashing immediately. Adding diagnostics to verify the controller process started and capture any startup errors: + + +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +
+Tool use: **semantic_search_full** + + +Parameters: + +```json +{ + "query": "How does the ensureScript start the controller process with Start-Process?", + "includePattern": "Services", + "topK": 10, + "explanation": "" +} +``` +Result: + +```json +{ + "codeResults": [ + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 320 + }, + "endPosition": { + "line": 358 + } + }, + "contents": " }}\n\n $controllerArgs = @(\n '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',\n '-File',\"\"$controllerPath\"\",\n '-WorkerName',\"\"$workerName\"\",\n '-WorkerType',\"\"$workerType\"\",\n '-PayloadBase64Path',\"\"$payloadBase64Path\"\"\n )\n\n Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden | Out-Null\n Write-Host \"\"Worker $workerName started under controller.\"\" -ForegroundColor Green\n}}\n\";\n\n // Pipe script through stdin to avoid command line length limits\n await _sshService.ExecuteRemoteScriptAsync(worker, ensureScript);\n\n // Wait a moment for the controller to start and update metadata\n await Task.Delay(2000);\n\n // Verify the worker actually started by checking metadata\n // Try up to 3 times with delays to account for slower startup\n WorkerStatus? status = null;\n for (int i = 0; i < 3; i++)\n {\n status = await GetWorkerStatusAsync(worker, workerType);\n if (status != null && (status.Status == \"running\" || status.Status == \"launching\"))\n {\n break;\n }\n if (i < 2) // Don't delay on last attempt\n {\n await Task.Delay(1000);\n }\n }\n\n if (status == null || (status.Status != \"running\" && status.Status != \"launching\"))", + "signatures": {}, + "detailedLines": [ + { + "text": " }}", + "lineNumber": 321 + }, + { + "lineNumber": 322 + }, + { + "text": " $controllerArgs = @(", + "lineNumber": 323 + }, + { + "text": " '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',", + "lineNumber": 324 + }, + { + "text": " '-File',\"\"$controllerPath\"\",", + "lineNumber": 325 + }, + { + "text": " '-WorkerName',\"\"$workerName\"\",", + "lineNumber": 326 + }, + { + "text": " '-WorkerType',\"\"$workerType\"\",", + "lineNumber": 327 + }, + { + "text": " '-PayloadBase64Path',\"\"$payloadBase64Path\"\"", + "lineNumber": 328 + }, + { + "text": " )", + "lineNumber": 329 + }, + { + "lineNumber": 330 + }, + { + "text": " Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden | Out-Null", + "lineNumber": 331 + }, + { + "text": " Write-Host \"\"Worker $workerName started under controller.\"\" -ForegroundColor Green", + "lineNumber": 332 + }, + { + "text": "}}", + "lineNumber": 333 + }, + { + "text": "\";", + "lineNumber": 334 + }, + { + "lineNumber": 335 + }, + { + "text": " // Pipe script through stdin to avoid command line length limits", + "lineNumber": 336 + }, + { + "text": " await _sshService.ExecuteRemoteScriptAsync(worker, ensureScript);", + "lineNumber": 337 + }, + { + "lineNumber": 338 + }, + { + "text": " // Wait a moment for the controller to start and update metadata", + "lineNumber": 339 + }, + { + "text": " await Task.Delay(2000);", + "lineNumber": 340 + }, + { + "lineNumber": 341 + }, + { + "text": " // Verify the worker actually started by checking metadata", + "lineNumber": 342 + }, + { + "text": " // Try up to 3 times with delays to account for slower startup", + "lineNumber": 343 + }, + { + "text": " WorkerStatus? status = null;", + "lineNumber": 344 + }, + { + "text": " for (int i = 0; i < 3; i++)", + "lineNumber": 345 + }, + { + "text": " {", + "lineNumber": 346 + }, + { + "text": " status = await GetWorkerStatusAsync(worker, workerType);", + "lineNumber": 347 + }, + { + "text": " if (status != null && (status.Status == \"running\" || status.Status == \"launching\"))", + "lineNumber": 348 + }, + { + "text": " {", + "lineNumber": 349 + }, + { + "text": " break;", + "lineNumber": 350 + }, + { + "text": " }", + "lineNumber": 351 + }, + { + "text": " if (i < 2) // Don't delay on last attempt", + "lineNumber": 352 + }, + { + "text": " {", + "lineNumber": 353 + }, + { + "text": " await Task.Delay(1000);", + "lineNumber": 354 + }, + { + "text": " }", + "lineNumber": 355 + }, + { + "text": " }", + "lineNumber": 356 + }, + { + "lineNumber": 357 + }, + { + "text": " if (status == null || (status.Status != \"running\" && status.Status != \"launching\"))", + "lineNumber": 358 + } + ] + }, + "score": 0.5897942185401917 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 230 + }, + "endPosition": { + "line": 275 + } + }, + "contents": "\";\n }\n\n public async Task StartWorkerAsync(WorkerConfig worker, string workerType)\n {\n await DeployControllerAsync(worker);\n\n string payloadScript;\n if (workerType == \"sheepit\")\n {\n payloadScript = GenerateSheepItPayload(worker);\n }\n else if (workerType == \"flamenco\")\n {\n payloadScript = GenerateFlamencoPayload(worker);\n }\n else\n {\n throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));\n }\n\n var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n\n var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";\n var jsonParamsBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(jsonParams));\n\n var ensureScript = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{jsonParamsBase64}')))\n$workerName = $params.WorkerName\n$workerType = $params.WorkerType\n$payloadBase64 = $params.PayloadBase64\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\n$instanceRoot = Join-Path (Join-Path $dataRoot $workerType) $workerName\n$logsRoot = Join-Path $instanceRoot 'logs'\n$stateRoot = Join-Path $instanceRoot 'state'\nNew-Item -ItemType Directory -Path $logsRoot -Force | Out-Null\nNew-Item -ItemType Directory -Path $stateRoot -Force | Out-Null\n$logPath = Join-Path $logsRoot 'worker.log'\n$commandPath = Join-Path $stateRoot 'commands.txt'\n$payloadPath = Join-Path $stateRoot 'payload.ps1'\n$payloadBase64Path = Join-Path $stateRoot 'payload.b64'\nif (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}\nif (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "signatures": {}, + "detailedLines": [ + { + "text": "\";", + "lineNumber": 231 + }, + { + "text": " }", + "lineNumber": 232 + }, + { + "lineNumber": 233 + }, + { + "text": " public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 234 + }, + { + "text": " {", + "lineNumber": 235 + }, + { + "text": " await DeployControllerAsync(worker);", + "lineNumber": 236 + }, + { + "lineNumber": 237 + }, + { + "text": " string payloadScript;", + "lineNumber": 238 + }, + { + "text": " if (workerType == \"sheepit\")", + "lineNumber": 239 + }, + { + "text": " {", + "lineNumber": 240 + }, + { + "text": " payloadScript = GenerateSheepItPayload(worker);", + "lineNumber": 241 + }, + { + "text": " }", + "lineNumber": 242 + }, + { + "text": " else if (workerType == \"flamenco\")", + "lineNumber": 243 + }, + { + "text": " {", + "lineNumber": 244 + }, + { + "text": " payloadScript = GenerateFlamencoPayload(worker);", + "lineNumber": 245 + }, + { + "text": " }", + "lineNumber": 246 + }, + { + "text": " else", + "lineNumber": 247 + }, + { + "text": " {", + "lineNumber": 248 + }, + { + "text": " throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));", + "lineNumber": 249 + }, + { + "text": " }", + "lineNumber": 250 + }, + { + "lineNumber": 251 + }, + { + "text": " var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));", + "lineNumber": 252 + }, + { + "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);", + "lineNumber": 253 + }, + { + "lineNumber": 254 + }, + { + "text": " var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";", + "lineNumber": 255 + }, + { + "text": " var jsonParamsBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(jsonParams));", + "lineNumber": 256 + }, + { + "lineNumber": 257 + }, + { + "text": " var ensureScript = $@\"", + "lineNumber": 258 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 259 + }, + { + "text": "$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{jsonParamsBase64}')))", + "lineNumber": 260 + }, + { + "text": "$workerName = $params.WorkerName", + "lineNumber": 261 + }, + { + "text": "$workerType = $params.WorkerType", + "lineNumber": 262 + }, + { + "text": "$payloadBase64 = $params.PayloadBase64", + "lineNumber": 263 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 264 + }, + { + "text": "$instanceRoot = Join-Path (Join-Path $dataRoot $workerType) $workerName", + "lineNumber": 265 + }, + { + "text": "$logsRoot = Join-Path $instanceRoot 'logs'", + "lineNumber": 266 + }, + { + "text": "$stateRoot = Join-Path $instanceRoot 'state'", + "lineNumber": 267 + }, + { + "text": "New-Item -ItemType Directory -Path $logsRoot -Force | Out-Null", + "lineNumber": 268 + }, + { + "text": "New-Item -ItemType Directory -Path $stateRoot -Force | Out-Null", + "lineNumber": 269 + }, + { + "text": "$logPath = Join-Path $logsRoot 'worker.log'", + "lineNumber": 270 + }, + { + "text": "$commandPath = Join-Path $stateRoot 'commands.txt'", + "lineNumber": 271 + }, + { + "text": "$payloadPath = Join-Path $stateRoot 'payload.ps1'", + "lineNumber": 272 + }, + { + "text": "$payloadBase64Path = Join-Path $stateRoot 'payload.b64'", + "lineNumber": 273 + }, + { + "text": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}", + "lineNumber": 274 + }, + { + "text": "if (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "lineNumber": 275 + } + ] + }, + "score": 0.4734037518501282 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 273 + }, + "endPosition": { + "line": 323, + "column": 8 + } + }, + "contents": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}\nif (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}\n[IO.File]::WriteAllText($payloadBase64Path, $payloadBase64, [System.Text.Encoding]::UTF8)\n$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'\n$controllerPath = Join-Path $dataRoot 'controller.ps1'\n\nif (-not (Test-Path $controllerPath)) {{\n throw \"\"Controller missing at $controllerPath\"\"\n}}\n\n$shouldStart = $true\nif (Test-Path $metaPath) {{\n try {{\n $meta = Get-Content $metaPath -Raw | ConvertFrom-Json\n if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{\n if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{\n Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"\n $shouldStart = $false\n }}\n }}\n }} catch {{\n Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow\n }}\n}}\n\nif ($shouldStart) {{\n $initialMeta = [pscustomobject]@{{\n WorkerName = $workerName\n WorkerType = $workerType\n Status = 'launching'\n ControllerPid = $null\n WorkerPid = $null\n Restarts = 0\n LastExitCode = $null\n LogPath = $logPath\n CommandPath = $commandPath\n PayloadPath = $payloadPath\n UpdatedAtUtc = (Get-Date).ToUniversalTime()\n }} | ConvertTo-Json -Depth 5\n $initialMeta | Set-Content -Path $metaPath -Encoding UTF8\n\n $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue\n if ($pwsh) {{\n $psExe = $pwsh.Source\n }}\n else {{\n $psExe = (Get-Command powershell -ErrorAction Stop).Source\n }}\n\n $controllerArgs = @(\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}", + "lineNumber": 274 + }, + { + "text": "if (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "lineNumber": 275 + }, + { + "text": "[IO.File]::WriteAllText($payloadBase64Path, $payloadBase64, [System.Text.Encoding]::UTF8)", + "lineNumber": 276 + }, + { + "text": "$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'", + "lineNumber": 277 + }, + { + "text": "$controllerPath = Join-Path $dataRoot 'controller.ps1'", + "lineNumber": 278 + }, + { + "lineNumber": 279 + }, + { + "text": "if (-not (Test-Path $controllerPath)) {{", + "lineNumber": 280 + }, + { + "text": " throw \"\"Controller missing at $controllerPath\"\"", + "lineNumber": 281 + }, + { + "text": "}}", + "lineNumber": 282 + }, + { + "lineNumber": 283 + }, + { + "text": "$shouldStart = $true", + "lineNumber": 284 + }, + { + "text": "if (Test-Path $metaPath) {{", + "lineNumber": 285 + }, + { + "text": " try {{", + "lineNumber": 286 + }, + { + "text": " $meta = Get-Content $metaPath -Raw | ConvertFrom-Json", + "lineNumber": 287 + }, + { + "text": " if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{", + "lineNumber": 288 + }, + { + "text": " if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{", + "lineNumber": 289 + }, + { + "text": " Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"", + "lineNumber": 290 + }, + { + "text": " $shouldStart = $false", + "lineNumber": 291 + }, + { + "text": " }}", + "lineNumber": 292 + }, + { + "text": " }}", + "lineNumber": 293 + }, + { + "text": " }} catch {{", + "lineNumber": 294 + }, + { + "text": " Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow", + "lineNumber": 295 + }, + { + "text": " }}", + "lineNumber": 296 + }, + { + "text": "}}", + "lineNumber": 297 + }, + { + "lineNumber": 298 + }, + { + "text": "if ($shouldStart) {{", + "lineNumber": 299 + }, + { + "text": " $initialMeta = [pscustomobject]@{{", + "lineNumber": 300 + }, + { + "text": " WorkerName = $workerName", + "lineNumber": 301 + }, + { + "text": " WorkerType = $workerType", + "lineNumber": 302 + }, + { + "text": " Status = 'launching'", + "lineNumber": 303 + }, + { + "text": " ControllerPid = $null", + "lineNumber": 304 + }, + { + "text": " WorkerPid = $null", + "lineNumber": 305 + }, + { + "text": " Restarts = 0", + "lineNumber": 306 + }, + { + "text": " LastExitCode = $null", + "lineNumber": 307 + }, + { + "text": " LogPath = $logPath", + "lineNumber": 308 + }, + { + "text": " CommandPath = $commandPath", + "lineNumber": 309 + }, + { + "text": " PayloadPath = $payloadPath", + "lineNumber": 310 + }, + { + "text": " UpdatedAtUtc = (Get-Date).ToUniversalTime()", + "lineNumber": 311 + }, + { + "text": " }} | ConvertTo-Json -Depth 5", + "lineNumber": 312 + }, + { + "text": " $initialMeta | Set-Content -Path $metaPath -Encoding UTF8", + "lineNumber": 313 + }, + { + "lineNumber": 314 + }, + { + "text": " $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue", + "lineNumber": 315 + }, + { + "text": " if ($pwsh) {{", + "lineNumber": 316 + }, + { + "text": " $psExe = $pwsh.Source", + "lineNumber": 317 + }, + { + "text": " }}", + "lineNumber": 318 + }, + { + "text": " else {{", + "lineNumber": 319 + }, + { + "text": " $psExe = (Get-Command powershell -ErrorAction Stop).Source", + "lineNumber": 320 + }, + { + "text": " }}", + "lineNumber": 321 + }, + { + "lineNumber": 322 + }, + { + "text": " $controllerArgs = @(", + "lineNumber": 323 + }, + { + "text": " ", + "lineNumber": 324 + } + ] + }, + "score": 0.44869720935821533 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\AttachService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 46 + } + }, + "contents": "using System.Diagnostics;\nusing System.IO;\nusing System.Threading.Tasks;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class AttachService\n {\n private readonly SshService _sshService;\n private readonly WorkerControllerService _controllerService;\n\n public AttachService(SshService sshService, WorkerControllerService controllerService)\n {\n _sshService = sshService;\n _controllerService = controllerService;\n }\n\n public async Task AttachToWorkerAsync(WorkerConfig worker, string workerType, bool commandOnly = false, string? command = null)\n {\n await _controllerService.DeployAttachHelperAsync(worker);\n\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n var remoteHelper = Path.Combine(remoteBasePath, \"attach-helper.ps1\");\n\n var paramsBlock = $\"-WorkerName \\\"{worker.Name}\\\" -WorkerType \\\"{workerType}\\\"\";\n if (commandOnly)\n {\n paramsBlock += \" -CommandOnly\";\n }\n if (!string.IsNullOrEmpty(command))\n {\n paramsBlock += $\" -Command \\\"{command}\\\"\";\n }\n\n // Use Windows path format (backslashes) and ensure it's properly quoted\n // Add -NoExit to keep window open and ensure output is visible\n // Use -Command with & to properly invoke the script and avoid pipeline input issues\n var escapedHelper = remoteHelper.Replace(\"'\", \"''\");\n var remoteCmd = $\"powershell.exe -NoLogo -NoProfile -NoExit -ExecutionPolicy Bypass -Command \\\"& '{escapedHelper}' {paramsBlock}\\\"\";\n\n _sshService.StartInteractiveSsh(worker, remoteCmd);\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": "using System.Diagnostics;", + "lineNumber": 1 + }, + { + "text": "using System.IO;", + "lineNumber": 2 + }, + { + "text": "using System.Threading.Tasks;", + "lineNumber": 3 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 4 + }, + { + "lineNumber": 5 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 6 + }, + { + "text": "{", + "lineNumber": 7 + }, + { + "text": " public class AttachService", + "lineNumber": 8 + }, + { + "text": " {", + "lineNumber": 9 + }, + { + "text": " private readonly SshService _sshService;", + "lineNumber": 10 + }, + { + "text": " private readonly WorkerControllerService _controllerService;", + "lineNumber": 11 + }, + { + "lineNumber": 12 + }, + { + "text": " public AttachService(SshService sshService, WorkerControllerService controllerService)", + "lineNumber": 13 + }, + { + "text": " {", + "lineNumber": 14 + }, + { + "text": " _sshService = sshService;", + "lineNumber": 15 + }, + { + "text": " _controllerService = controllerService;", + "lineNumber": 16 + }, + { + "text": " }", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": " public async Task AttachToWorkerAsync(WorkerConfig worker, string workerType, bool commandOnly = false, string? command = null)", + "lineNumber": 19 + }, + { + "text": " {", + "lineNumber": 20 + }, + { + "text": " await _controllerService.DeployAttachHelperAsync(worker);", + "lineNumber": 21 + }, + { + "lineNumber": 22 + }, + { + "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);", + "lineNumber": 23 + }, + { + "text": " var remoteHelper = Path.Combine(remoteBasePath, \"attach-helper.ps1\");", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": " var paramsBlock = $\"-WorkerName \\\"{worker.Name}\\\" -WorkerType \\\"{workerType}\\\"\";", + "lineNumber": 26 + }, + { + "text": " if (commandOnly)", + "lineNumber": 27 + }, + { + "text": " {", + "lineNumber": 28 + }, + { + "text": " paramsBlock += \" -CommandOnly\";", + "lineNumber": 29 + }, + { + "text": " }", + "lineNumber": 30 + }, + { + "text": " if (!string.IsNullOrEmpty(command))", + "lineNumber": 31 + }, + { + "text": " {", + "lineNumber": 32 + }, + { + "text": " paramsBlock += $\" -Command \\\"{command}\\\"\";", + "lineNumber": 33 + }, + { + "text": " }", + "lineNumber": 34 + }, + { + "lineNumber": 35 + }, + { + "text": " // Use Windows path format (backslashes) and ensure it's properly quoted", + "lineNumber": 36 + }, + { + "text": " // Add -NoExit to keep window open and ensure output is visible", + "lineNumber": 37 + }, + { + "text": " // Use -Command with & to properly invoke the script and avoid pipeline input issues", + "lineNumber": 38 + }, + { + "text": " var escapedHelper = remoteHelper.Replace(\"'\", \"''\");", + "lineNumber": 39 + }, + { + "text": " var remoteCmd = $\"powershell.exe -NoLogo -NoProfile -NoExit -ExecutionPolicy Bypass -Command \\\"& '{escapedHelper}' {paramsBlock}\\\"\";", + "lineNumber": 40 + }, + { + "lineNumber": 41 + }, + { + "text": " _sshService.StartInteractiveSsh(worker, remoteCmd);", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "text": " }", + "lineNumber": 44 + }, + { + "text": "}", + "lineNumber": 45 + } + ] + }, + "score": 0.41845008730888367 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 188 + }, + "endPosition": { + "line": 234 + } + }, + "contents": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')\n\ntry {{\n Write-Host \"\"Setting up network connections...\"\" -ForegroundColor Cyan\n\n $drives = @({drives})\n $networkPaths = @({paths})\n\n # Disconnect all existing connections\n Write-Host \"\"Disconnecting existing network connections...\"\" -ForegroundColor Yellow\n foreach ($path in $networkPaths) {{ net use $path /delete /y 2>$null }}\n foreach ($drive in $drives) {{ net use $drive /delete /y 2>$null }}\n Write-Host \"\"All network connections cleared.\"\" -ForegroundColor Green\n\n # Connect to network shares (simplified - credentials should be stored securely)\n Write-Host \"\"Establishing network connections...\"\" -ForegroundColor Cyan\n # TODO: Add credential handling for network shares\n\n # Start worker\n Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan\n Set-Location '{workerPath}'\n if (Test-Path 'flamenco-worker.exe') {{\n Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green\n $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait\n $exitCode = $workerProcess.ExitCode\n Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow\n exit $exitCode\n }} else {{\n Write-Host \"\"Error: flamenco-worker.exe not found in {workerPath}\"\" -ForegroundColor Red\n [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {workerPath}\"\")\n exit 1\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "signatures": {}, + "detailedLines": [ + { + "text": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "lineNumber": 189 + }, + { + "lineNumber": 190 + }, + { + "text": "try {{", + "lineNumber": 191 + }, + { + "text": " Write-Host \"\"Setting up network connections...\"\" -ForegroundColor Cyan", + "lineNumber": 192 + }, + { + "lineNumber": 193 + }, + { + "text": " $drives = @({drives})", + "lineNumber": 194 + }, + { + "text": " $networkPaths = @({paths})", + "lineNumber": 195 + }, + { + "lineNumber": 196 + }, + { + "text": " # Disconnect all existing connections", + "lineNumber": 197 + }, + { + "text": " Write-Host \"\"Disconnecting existing network connections...\"\" -ForegroundColor Yellow", + "lineNumber": 198 + }, + { + "text": " foreach ($path in $networkPaths) {{ net use $path /delete /y 2>$null }}", + "lineNumber": 199 + }, + { + "text": " foreach ($drive in $drives) {{ net use $drive /delete /y 2>$null }}", + "lineNumber": 200 + }, + { + "text": " Write-Host \"\"All network connections cleared.\"\" -ForegroundColor Green", + "lineNumber": 201 + }, + { + "lineNumber": 202 + }, + { + "text": " # Connect to network shares (simplified - credentials should be stored securely)", + "lineNumber": 203 + }, + { + "text": " Write-Host \"\"Establishing network connections...\"\" -ForegroundColor Cyan", + "lineNumber": 204 + }, + { + "text": " # TODO: Add credential handling for network shares", + "lineNumber": 205 + }, + { + "lineNumber": 206 + }, + { + "text": " # Start worker", + "lineNumber": 207 + }, + { + "text": " Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan", + "lineNumber": 208 + }, + { + "text": " Set-Location '{workerPath}'", + "lineNumber": 209 + }, + { + "text": " if (Test-Path 'flamenco-worker.exe') {{", + "lineNumber": 210 + }, + { + "text": " Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green", + "lineNumber": 211 + }, + { + "text": " $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait", + "lineNumber": 212 + }, + { + "text": " $exitCode = $workerProcess.ExitCode", + "lineNumber": 213 + }, + { + "text": " Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow", + "lineNumber": 214 + }, + { + "text": " exit $exitCode", + "lineNumber": 215 + }, + { + "text": " }} else {{", + "lineNumber": 216 + }, + { + "text": " Write-Host \"\"Error: flamenco-worker.exe not found in {workerPath}\"\" -ForegroundColor Red", + "lineNumber": 217 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {workerPath}\"\")", + "lineNumber": 218 + }, + { + "text": " exit 1", + "lineNumber": 219 + }, + { + "text": " }}", + "lineNumber": 220 + }, + { + "text": "}}", + "lineNumber": 221 + }, + { + "text": "catch {{", + "lineNumber": 222 + }, + { + "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)", + "lineNumber": 223 + }, + { + "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)", + "lineNumber": 224 + }, + { + "text": " Write-Host $errorMsg -ForegroundColor Red", + "lineNumber": 225 + }, + { + "text": " Write-Host $stackMsg -ForegroundColor DarkRed", + "lineNumber": 226 + }, + { + "text": " [Console]::Error.WriteLine($errorMsg)", + "lineNumber": 227 + }, + { + "text": " [Console]::Error.WriteLine($stackMsg)", + "lineNumber": 228 + }, + { + "text": " exit 1", + "lineNumber": 229 + }, + { + "text": "}}", + "lineNumber": 230 + }, + { + "text": "\";", + "lineNumber": 231 + }, + { + "text": " }", + "lineNumber": 232 + }, + { + "lineNumber": 233 + }, + { + "text": " public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 234 + } + ] + }, + "score": 0.41603338718414307 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 141 + }, + "endPosition": { + "line": 190 + } + }, + "contents": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')\n Set-Location $sheepDir\n \n $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, \n '-ui', 'text', '--log-stdout', '--verbose', \n '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')\n \n try {{\n & java @javaArgs\n }}\n catch {{\n Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red\n Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow\n [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")\n throw\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public string GenerateFlamencoPayload(WorkerConfig worker)\n {\n if (worker.WorkerTypes.Flamenco == null)\n throw new InvalidOperationException(\"Worker does not have Flamenco configuration\");\n\n var config = _configService.Load();\n var flamenco = worker.WorkerTypes.Flamenco;\n var globalSettings = config.GlobalSettings;\n var workerPath = globalSettings.FlamencoWorkerPath.Replace(\"'\", \"''\");\n var drives = string.Join(\", \", Array.ConvertAll(flamenco.NetworkDrives.ToArray(), d => $\"'{d}'\"));\n var paths = string.Join(\", \", Array.ConvertAll(flamenco.NetworkPaths.ToArray(), p => $\"'{p.Replace(\"\\\\\", \"\\\\\\\\\")}'\"));\n\n return $@\"\n$ProgressPreference = 'SilentlyContinue'\n$ErrorActionPreference = 'Stop'\n\n# Write startup message to stderr so controller can capture it\n[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "signatures": {}, + "detailedLines": [ + { + "text": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')", + "lineNumber": 143 + }, + { + "text": " Set-Location $sheepDir", + "lineNumber": 144 + }, + { + "text": " ", + "lineNumber": 145 + }, + { + "text": " $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, ", + "lineNumber": 146 + }, + { + "text": " '-ui', 'text', '--log-stdout', '--verbose', ", + "lineNumber": 147 + }, + { + "text": " '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')", + "lineNumber": 148 + }, + { + "text": " ", + "lineNumber": 149 + }, + { + "text": " try {{", + "lineNumber": 150 + }, + { + "text": " & java @javaArgs", + "lineNumber": 151 + }, + { + "text": " }}", + "lineNumber": 152 + }, + { + "text": " catch {{", + "lineNumber": 153 + }, + { + "text": " Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red", + "lineNumber": 154 + }, + { + "text": " Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow", + "lineNumber": 155 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")", + "lineNumber": 156 + }, + { + "text": " throw", + "lineNumber": 157 + }, + { + "text": " }}", + "lineNumber": 158 + }, + { + "text": "}}", + "lineNumber": 159 + }, + { + "text": "catch {{", + "lineNumber": 160 + }, + { + "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)", + "lineNumber": 161 + }, + { + "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)", + "lineNumber": 162 + }, + { + "text": " Write-Host $errorMsg -ForegroundColor Red", + "lineNumber": 163 + }, + { + "text": " Write-Host $stackMsg -ForegroundColor DarkRed", + "lineNumber": 164 + }, + { + "text": " [Console]::Error.WriteLine($errorMsg)", + "lineNumber": 165 + }, + { + "text": " [Console]::Error.WriteLine($stackMsg)", + "lineNumber": 166 + }, + { + "text": " exit 1", + "lineNumber": 167 + }, + { + "text": "}}", + "lineNumber": 168 + }, + { + "text": "\";", + "lineNumber": 169 + }, + { + "text": " }", + "lineNumber": 170 + }, + { + "lineNumber": 171 + }, + { + "text": " public string GenerateFlamencoPayload(WorkerConfig worker)", + "lineNumber": 172 + }, + { + "text": " {", + "lineNumber": 173 + }, + { + "text": " if (worker.WorkerTypes.Flamenco == null)", + "lineNumber": 174 + }, + { + "text": " throw new InvalidOperationException(\"Worker does not have Flamenco configuration\");", + "lineNumber": 175 + }, + { + "lineNumber": 176 + }, + { + "text": " var config = _configService.Load();", + "lineNumber": 177 + }, + { + "text": " var flamenco = worker.WorkerTypes.Flamenco;", + "lineNumber": 178 + }, + { + "text": " var globalSettings = config.GlobalSettings;", + "lineNumber": 179 + }, + { + "text": " var workerPath = globalSettings.FlamencoWorkerPath.Replace(\"'\", \"''\");", + "lineNumber": 180 + }, + { + "text": " var drives = string.Join(\", \", Array.ConvertAll(flamenco.NetworkDrives.ToArray(), d => $\"'{d}'\"));", + "lineNumber": 181 + }, + { + "text": " var paths = string.Join(\", \", Array.ConvertAll(flamenco.NetworkPaths.ToArray(), p => $\"'{p.Replace(\"\\\\\", \"\\\\\\\\\")}'\"));", + "lineNumber": 182 + }, + { + "lineNumber": 183 + }, + { + "text": " return $@\"", + "lineNumber": 184 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 185 + }, + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 186 + }, + { + "lineNumber": 187 + }, + { + "text": "# Write startup message to stderr so controller can capture it", + "lineNumber": 188 + }, + { + "text": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "lineNumber": 189 + } + ] + }, + "score": 0.4001624882221222 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 213 + }, + "endPosition": { + "line": 260 + } + }, + "contents": " };\n\n var output = new StringBuilder();\n var error = new StringBuilder();\n\n process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };\n process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };\n\n process.Start();\n process.BeginOutputReadLine();\n process.BeginErrorReadLine();\n\n await process.WaitForExitAsync();\n\n if (process.ExitCode != 0 && !interactive)\n {\n throw new InvalidOperationException($\"SSH command failed with exit code {process.ExitCode}: {error}\");\n }\n\n return output.ToString();\n }\n\n public async Task<string> ExecuteRemoteScriptAsync(WorkerConfig worker, string script, bool interactive = false)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var sshArgs = BuildSshArgs(parts, interactive);\n // Add PowerShell command as a single quoted argument for remote execution\n sshArgs.Add(\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -Command -\");\n\n var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));\n \n var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetSshExecutable(),\n WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),\n Arguments = argsString,\n UseShellExecute = false,\n RedirectStandardInput = true,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = !interactive\n }\n };\n\n var output = new StringBuilder();", + "signatures": {}, + "detailedLines": [ + { + "text": " };", + "lineNumber": 214 + }, + { + "lineNumber": 215 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 216 + }, + { + "text": " var error = new StringBuilder();", + "lineNumber": 217 + }, + { + "lineNumber": 218 + }, + { + "text": " process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };", + "lineNumber": 219 + }, + { + "text": " process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };", + "lineNumber": 220 + }, + { + "lineNumber": 221 + }, + { + "text": " process.Start();", + "lineNumber": 222 + }, + { + "text": " process.BeginOutputReadLine();", + "lineNumber": 223 + }, + { + "text": " process.BeginErrorReadLine();", + "lineNumber": 224 + }, + { + "lineNumber": 225 + }, + { + "text": " await process.WaitForExitAsync();", + "lineNumber": 226 + }, + { + "lineNumber": 227 + }, + { + "text": " if (process.ExitCode != 0 && !interactive)", + "lineNumber": 228 + }, + { + "text": " {", + "lineNumber": 229 + }, + { + "text": " throw new InvalidOperationException($\"SSH command failed with exit code {process.ExitCode}: {error}\");", + "lineNumber": 230 + }, + { + "text": " }", + "lineNumber": 231 + }, + { + "lineNumber": 232 + }, + { + "text": " return output.ToString();", + "lineNumber": 233 + }, + { + "text": " }", + "lineNumber": 234 + }, + { + "lineNumber": 235 + }, + { + "text": " public async Task<string> ExecuteRemoteScriptAsync(WorkerConfig worker, string script, bool interactive = false)", + "lineNumber": 236 + }, + { + "text": " {", + "lineNumber": 237 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 238 + }, + { + "text": " var sshArgs = BuildSshArgs(parts, interactive);", + "lineNumber": 239 + }, + { + "text": " // Add PowerShell command as a single quoted argument for remote execution", + "lineNumber": 240 + }, + { + "text": " sshArgs.Add(\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -Command -\");", + "lineNumber": 241 + }, + { + "lineNumber": 242 + }, + { + "text": " var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));", + "lineNumber": 243 + }, + { + "text": " ", + "lineNumber": 244 + }, + { + "text": " var process = new Process", + "lineNumber": 245 + }, + { + "text": " {", + "lineNumber": 246 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 247 + }, + { + "text": " {", + "lineNumber": 248 + }, + { + "text": " FileName = GetSshExecutable(),", + "lineNumber": 249 + }, + { + "text": " WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),", + "lineNumber": 250 + }, + { + "text": " Arguments = argsString,", + "lineNumber": 251 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 252 + }, + { + "text": " RedirectStandardInput = true,", + "lineNumber": 253 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 254 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 255 + }, + { + "text": " CreateNoWindow = !interactive", + "lineNumber": 256 + }, + { + "text": " }", + "lineNumber": 257 + }, + { + "text": " };", + "lineNumber": 258 + }, + { + "lineNumber": 259 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 260 + } + ] + }, + "score": 0.38839781284332275 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 52, + "column": 16 + }, + "endPosition": { + "line": 93 + } + }, + "contents": "throw new InvalidOperationException($\"Resource {resourceName} not found\");\n\n using var reader = new BinaryReader(stream);\n _attachHelperScriptBytes = reader.ReadBytes((int)stream.Length);\n return _attachHelperScriptBytes;\n }\n\n public async Task DeployControllerAsync(WorkerConfig worker)\n {\n var controllerBase64 = Convert.ToBase64String(GetControllerScriptBytes());\n var script = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\nNew-Item -ItemType Directory -Path $dataRoot -Force | Out-Null\n$controllerPath = Join-Path $dataRoot 'controller.ps1'\n[IO.File]::WriteAllBytes($controllerPath, [Convert]::FromBase64String('{controllerBase64}'))\n\";\n\n await _sshService.ExecuteRemoteScriptAsync(worker, script);\n }\n\n public async Task DeployAttachHelperAsync(WorkerConfig worker)\n {\n var helperBase64 = Convert.ToBase64String(GetAttachHelperScriptBytes());\n var script = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\nNew-Item -ItemType Directory -Path $dataRoot -Force | Out-Null\n$attachPath = Join-Path $dataRoot 'attach-helper.ps1'\n[IO.File]::WriteAllBytes($attachPath, [Convert]::FromBase64String('{helperBase64}'))\n\";\n\n await _sshService.ExecuteRemoteScriptAsync(worker, script);\n }\n\n public string GenerateSheepItPayload(WorkerConfig worker)\n {\n if (worker.WorkerTypes.SheepIt == null)\n throw new InvalidOperationException(\"Worker does not have SheepIt configuration\");\n\n var config = _configService.Load();", + "signatures": {}, + "detailedLines": [ + { + "text": "throw new InvalidOperationException($\"Resource {resourceName} not found\");", + "lineNumber": 53 + }, + { + "lineNumber": 54 + }, + { + "text": " using var reader = new BinaryReader(stream);", + "lineNumber": 55 + }, + { + "text": " _attachHelperScriptBytes = reader.ReadBytes((int)stream.Length);", + "lineNumber": 56 + }, + { + "text": " return _attachHelperScriptBytes;", + "lineNumber": 57 + }, + { + "text": " }", + "lineNumber": 58 + }, + { + "lineNumber": 59 + }, + { + "text": " public async Task DeployControllerAsync(WorkerConfig worker)", + "lineNumber": 60 + }, + { + "text": " {", + "lineNumber": 61 + }, + { + "text": " var controllerBase64 = Convert.ToBase64String(GetControllerScriptBytes());", + "lineNumber": 62 + }, + { + "text": " var script = $@\"", + "lineNumber": 63 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 64 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 65 + }, + { + "text": "New-Item -ItemType Directory -Path $dataRoot -Force | Out-Null", + "lineNumber": 66 + }, + { + "text": "$controllerPath = Join-Path $dataRoot 'controller.ps1'", + "lineNumber": 67 + }, + { + "text": "[IO.File]::WriteAllBytes($controllerPath, [Convert]::FromBase64String('{controllerBase64}'))", + "lineNumber": 68 + }, + { + "text": "\";", + "lineNumber": 69 + }, + { + "lineNumber": 70 + }, + { + "text": " await _sshService.ExecuteRemoteScriptAsync(worker, script);", + "lineNumber": 71 + }, + { + "text": " }", + "lineNumber": 72 + }, + { + "lineNumber": 73 + }, + { + "text": " public async Task DeployAttachHelperAsync(WorkerConfig worker)", + "lineNumber": 74 + }, + { + "text": " {", + "lineNumber": 75 + }, + { + "text": " var helperBase64 = Convert.ToBase64String(GetAttachHelperScriptBytes());", + "lineNumber": 76 + }, + { + "text": " var script = $@\"", + "lineNumber": 77 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 78 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 79 + }, + { + "text": "New-Item -ItemType Directory -Path $dataRoot -Force | Out-Null", + "lineNumber": 80 + }, + { + "text": "$attachPath = Join-Path $dataRoot 'attach-helper.ps1'", + "lineNumber": 81 + }, + { + "text": "[IO.File]::WriteAllBytes($attachPath, [Convert]::FromBase64String('{helperBase64}'))", + "lineNumber": 82 + }, + { + "text": "\";", + "lineNumber": 83 + }, + { + "lineNumber": 84 + }, + { + "text": " await _sshService.ExecuteRemoteScriptAsync(worker, script);", + "lineNumber": 85 + }, + { + "text": " }", + "lineNumber": 86 + }, + { + "lineNumber": 87 + }, + { + "text": " public string GenerateSheepItPayload(WorkerConfig worker)", + "lineNumber": 88 + }, + { + "text": " {", + "lineNumber": 89 + }, + { + "text": " if (worker.WorkerTypes.SheepIt == null)", + "lineNumber": 90 + }, + { + "text": " throw new InvalidOperationException(\"Worker does not have SheepIt configuration\");", + "lineNumber": 91 + }, + { + "lineNumber": 92 + }, + { + "text": " var config = _configService.Load();", + "lineNumber": 93 + } + ] + }, + "score": 0.36816906929016113 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 91 + }, + "endPosition": { + "line": 143 + } + }, + "contents": " var config = _configService.Load();\n var sheepIt = worker.WorkerTypes.SheepIt;\n var globalSettings = config.GlobalSettings;\n var safeKey = globalSettings.SheepItRenderKey.Replace(\"'\", \"''\");\n var safeUser = globalSettings.SheepItUsername.Replace(\"'\", \"''\");\n var urls = globalSettings.SheepItJarUrls;\n var urlLiteral = \"@(\" + string.Join(\", \", Array.ConvertAll(urls.ToArray(), url => $\"'{url}'\")) + \")\";\n\n return $@\"\n$ProgressPreference = 'SilentlyContinue'\n$ErrorActionPreference = 'Stop'\n\n# Write startup message to stderr so controller can capture it\n[Console]::Error.WriteLine('[SHEEPIT] Payload script starting...')\n\ntry {{\n $appData = [Environment]::GetFolderPath('ApplicationData')\n $sheepDir = Join-Path $appData 'sheepit'\n if (-not (Test-Path $sheepDir)) {{\n New-Item -Path $sheepDir -ItemType Directory -Force | Out-Null\n }}\n\n $jarPath = Join-Path $sheepDir 'sheepit-client.jar'\n $urls = {urlLiteral}\n $headers = @{{ 'User-Agent' = 'Mozilla/5.0' }}\n\n if (Test-Path $jarPath) {{\n Write-Host \"\"SheepIt client already present at $jarPath. Skipping download.\"\" -ForegroundColor Green\n }}\n else {{\n $downloaded = $false\n\n foreach ($url in $urls) {{\n Write-Host \"\"Downloading SheepIt client from $url...\"\" -ForegroundColor Cyan\n try {{\n Invoke-WebRequest -Uri $url -OutFile $jarPath -UseBasicParsing -Headers $headers\n $downloaded = $true\n Write-Host \"\"Download complete.\"\" -ForegroundColor Green\n break\n }}\n catch {{\n Write-Host (\"\"Download failed from {{0}}: {{1}}\"\" -f $url, $_.Exception.Message) -ForegroundColor Yellow\n }}\n }}\n\n if (-not $downloaded) {{\n throw 'Unable to download SheepIt client from any known URL.'\n }}\n }}\n\n [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')", + "signatures": {}, + "detailedLines": [ + { + "text": " var config = _configService.Load();", + "lineNumber": 93 + }, + { + "text": " var sheepIt = worker.WorkerTypes.SheepIt;", + "lineNumber": 94 + }, + { + "text": " var globalSettings = config.GlobalSettings;", + "lineNumber": 95 + }, + { + "text": " var safeKey = globalSettings.SheepItRenderKey.Replace(\"'\", \"''\");", + "lineNumber": 96 + }, + { + "text": " var safeUser = globalSettings.SheepItUsername.Replace(\"'\", \"''\");", + "lineNumber": 97 + }, + { + "text": " var urls = globalSettings.SheepItJarUrls;", + "lineNumber": 98 + }, + { + "text": " var urlLiteral = \"@(\" + string.Join(\", \", Array.ConvertAll(urls.ToArray(), url => $\"'{url}'\")) + \")\";", + "lineNumber": 99 + }, + { + "lineNumber": 100 + }, + { + "text": " return $@\"", + "lineNumber": 101 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 102 + }, + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 103 + }, + { + "lineNumber": 104 + }, + { + "text": "# Write startup message to stderr so controller can capture it", + "lineNumber": 105 + }, + { + "text": "[Console]::Error.WriteLine('[SHEEPIT] Payload script starting...')", + "lineNumber": 106 + }, + { + "lineNumber": 107 + }, + { + "text": "try {{", + "lineNumber": 108 + }, + { + "text": " $appData = [Environment]::GetFolderPath('ApplicationData')", + "lineNumber": 109 + }, + { + "text": " $sheepDir = Join-Path $appData 'sheepit'", + "lineNumber": 110 + }, + { + "text": " if (-not (Test-Path $sheepDir)) {{", + "lineNumber": 111 + }, + { + "text": " New-Item -Path $sheepDir -ItemType Directory -Force | Out-Null", + "lineNumber": 112 + }, + { + "text": " }}", + "lineNumber": 113 + }, + { + "lineNumber": 114 + }, + { + "text": " $jarPath = Join-Path $sheepDir 'sheepit-client.jar'", + "lineNumber": 115 + }, + { + "text": " $urls = {urlLiteral}", + "lineNumber": 116 + }, + { + "text": " $headers = @{{ 'User-Agent' = 'Mozilla/5.0' }}", + "lineNumber": 117 + }, + { + "lineNumber": 118 + }, + { + "text": " if (Test-Path $jarPath) {{", + "lineNumber": 119 + }, + { + "text": " Write-Host \"\"SheepIt client already present at $jarPath. Skipping download.\"\" -ForegroundColor Green", + "lineNumber": 120 + }, + { + "text": " }}", + "lineNumber": 121 + }, + { + "text": " else {{", + "lineNumber": 122 + }, + { + "text": " $downloaded = $false", + "lineNumber": 123 + }, + { + "lineNumber": 124 + }, + { + "text": " foreach ($url in $urls) {{", + "lineNumber": 125 + }, + { + "text": " Write-Host \"\"Downloading SheepIt client from $url...\"\" -ForegroundColor Cyan", + "lineNumber": 126 + }, + { + "text": " try {{", + "lineNumber": 127 + }, + { + "text": " Invoke-WebRequest -Uri $url -OutFile $jarPath -UseBasicParsing -Headers $headers", + "lineNumber": 128 + }, + { + "text": " $downloaded = $true", + "lineNumber": 129 + }, + { + "text": " Write-Host \"\"Download complete.\"\" -ForegroundColor Green", + "lineNumber": 130 + }, + { + "text": " break", + "lineNumber": 131 + }, + { + "text": " }}", + "lineNumber": 132 + }, + { + "text": " catch {{", + "lineNumber": 133 + }, + { + "text": " Write-Host (\"\"Download failed from {{0}}: {{1}}\"\" -f $url, $_.Exception.Message) -ForegroundColor Yellow", + "lineNumber": 134 + }, + { + "text": " }}", + "lineNumber": 135 + }, + { + "text": " }}", + "lineNumber": 136 + }, + { + "lineNumber": 137 + }, + { + "text": " if (-not $downloaded) {{", + "lineNumber": 138 + }, + { + "text": " throw 'Unable to download SheepIt client from any known URL.'", + "lineNumber": 139 + }, + { + "text": " }}", + "lineNumber": 140 + }, + { + "text": " }}", + "lineNumber": 141 + }, + { + "lineNumber": 142 + }, + { + "text": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')", + "lineNumber": 143 + } + ] + }, + "score": 0.36490750312805176 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 299 + }, + "endPosition": { + "line": 345, + "column": 16 + } + }, + "contents": " var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");\n _workerBasePathCache[worker.Name] = finalPath;\n return finalPath;\n }\n\n public async Task CopyFileToRemoteAsync(WorkerConfig worker, string localPath, string remotePath)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var scpArgs = BuildScpArgs(parts);\n scpArgs.Add(localPath);\n scpArgs.Add($\"{parts.Host}:\\\"{remotePath.Replace(\"\\\\\", \"/\")}\\\"\");\n\n var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetScpExecutable(),\n Arguments = string.Join(\" \", scpArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\")),\n UseShellExecute = false,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = true\n }\n };\n\n process.Start();\n await process.WaitForExitAsync();\n\n if (process.ExitCode != 0)\n {\n var error = await process.StandardError.ReadToEndAsync();\n throw new InvalidOperationException($\"SCP failed with exit code {process.ExitCode}: {error}\");\n }\n }\n\n public Process StartInteractiveSsh(WorkerConfig worker, string command)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var sshArgs = BuildSshArgs(parts, true);\n sshArgs.Add(command);\n\n var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));\n\n var process = new Process\n {\n ", + "signatures": {}, + "detailedLines": [ + { + "text": " var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");", + "lineNumber": 301 + }, + { + "text": " _workerBasePathCache[worker.Name] = finalPath;", + "lineNumber": 302 + }, + { + "text": " return finalPath;", + "lineNumber": 303 + }, + { + "text": " }", + "lineNumber": 304 + }, + { + "lineNumber": 305 + }, + { + "text": " public async Task CopyFileToRemoteAsync(WorkerConfig worker, string localPath, string remotePath)", + "lineNumber": 306 + }, + { + "text": " {", + "lineNumber": 307 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 308 + }, + { + "text": " var scpArgs = BuildScpArgs(parts);", + "lineNumber": 309 + }, + { + "text": " scpArgs.Add(localPath);", + "lineNumber": 310 + }, + { + "text": " scpArgs.Add($\"{parts.Host}:\\\"{remotePath.Replace(\"\\\\\", \"/\")}\\\"\");", + "lineNumber": 311 + }, + { + "lineNumber": 312 + }, + { + "text": " var process = new Process", + "lineNumber": 313 + }, + { + "text": " {", + "lineNumber": 314 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 315 + }, + { + "text": " {", + "lineNumber": 316 + }, + { + "text": " FileName = GetScpExecutable(),", + "lineNumber": 317 + }, + { + "text": " Arguments = string.Join(\" \", scpArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\")),", + "lineNumber": 318 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 319 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 320 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 321 + }, + { + "text": " CreateNoWindow = true", + "lineNumber": 322 + }, + { + "text": " }", + "lineNumber": 323 + }, + { + "text": " };", + "lineNumber": 324 + }, + { + "lineNumber": 325 + }, + { + "text": " process.Start();", + "lineNumber": 326 + }, + { + "text": " await process.WaitForExitAsync();", + "lineNumber": 327 + }, + { + "lineNumber": 328 + }, + { + "text": " if (process.ExitCode != 0)", + "lineNumber": 329 + }, + { + "text": " {", + "lineNumber": 330 + }, + { + "text": " var error = await process.StandardError.ReadToEndAsync();", + "lineNumber": 331 + }, + { + "text": " throw new InvalidOperationException($\"SCP failed with exit code {process.ExitCode}: {error}\");", + "lineNumber": 332 + }, + { + "text": " }", + "lineNumber": 333 + }, + { + "text": " }", + "lineNumber": 334 + }, + { + "lineNumber": 335 + }, + { + "text": " public Process StartInteractiveSsh(WorkerConfig worker, string command)", + "lineNumber": 336 + }, + { + "text": " {", + "lineNumber": 337 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 338 + }, + { + "text": " var sshArgs = BuildSshArgs(parts, true);", + "lineNumber": 339 + }, + { + "text": " sshArgs.Add(command);", + "lineNumber": 340 + }, + { + "lineNumber": 341 + }, + { + "text": " var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));", + "lineNumber": 342 + }, + { + "lineNumber": 343 + }, + { + "text": " var process = new Process", + "lineNumber": 344 + }, + { + "text": " {", + "lineNumber": 345 + }, + { + "text": " ", + "lineNumber": 346 + } + ] + }, + "score": 0.3509736657142639 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 150 + }, + "endPosition": { + "line": 216 + } + }, + "contents": " \"-o\", \"ServerAliveCountMax=30\"\n };\n\n if (interactive && parts.RequestPty)\n {\n args.Add(\"-t\");\n }\n else if (!interactive)\n {\n args.Add(\"-T\");\n }\n\n args.AddRange(parts.Options);\n\n if (parts.Port.HasValue)\n {\n args.Add(\"-p\");\n args.Add(parts.Port.Value.ToString());\n }\n\n args.Add(parts.Host);\n return args;\n }\n\n public List<string> BuildScpArgs(SshConnectionParts parts)\n {\n var args = new List<string>\n {\n \"-o\", \"ServerAliveInterval=60\",\n \"-o\", \"ServerAliveCountMax=30\"\n };\n\n args.AddRange(parts.Options);\n\n if (parts.Port.HasValue)\n {\n args.Add(\"-P\");\n args.Add(parts.Port.Value.ToString());\n }\n\n return args;\n }\n\n public async Task<string> ExecuteRemoteCommandAsync(WorkerConfig worker, string command, bool interactive = false)\n {\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var sshArgs = BuildSshArgs(parts, interactive);\n sshArgs.Add(command);\n\n var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));\n \n var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetSshExecutable(),\n WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),\n Arguments = argsString,\n UseShellExecute = false,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = !interactive\n }\n };\n\n var output = new StringBuilder();", + "signatures": {}, + "detailedLines": [ + { + "text": " \"-o\", \"ServerAliveCountMax=30\"", + "lineNumber": 151 + }, + { + "text": " };", + "lineNumber": 152 + }, + { + "lineNumber": 153 + }, + { + "text": " if (interactive && parts.RequestPty)", + "lineNumber": 154 + }, + { + "text": " {", + "lineNumber": 155 + }, + { + "text": " args.Add(\"-t\");", + "lineNumber": 156 + }, + { + "text": " }", + "lineNumber": 157 + }, + { + "text": " else if (!interactive)", + "lineNumber": 158 + }, + { + "text": " {", + "lineNumber": 159 + }, + { + "text": " args.Add(\"-T\");", + "lineNumber": 160 + }, + { + "text": " }", + "lineNumber": 161 + }, + { + "lineNumber": 162 + }, + { + "text": " args.AddRange(parts.Options);", + "lineNumber": 163 + }, + { + "lineNumber": 164 + }, + { + "text": " if (parts.Port.HasValue)", + "lineNumber": 165 + }, + { + "text": " {", + "lineNumber": 166 + }, + { + "text": " args.Add(\"-p\");", + "lineNumber": 167 + }, + { + "text": " args.Add(parts.Port.Value.ToString());", + "lineNumber": 168 + }, + { + "text": " }", + "lineNumber": 169 + }, + { + "lineNumber": 170 + }, + { + "text": " args.Add(parts.Host);", + "lineNumber": 171 + }, + { + "text": " return args;", + "lineNumber": 172 + }, + { + "text": " }", + "lineNumber": 173 + }, + { + "lineNumber": 174 + }, + { + "text": " public List<string> BuildScpArgs(SshConnectionParts parts)", + "lineNumber": 175 + }, + { + "text": " {", + "lineNumber": 176 + }, + { + "text": " var args = new List<string>", + "lineNumber": 177 + }, + { + "text": " {", + "lineNumber": 178 + }, + { + "text": " \"-o\", \"ServerAliveInterval=60\",", + "lineNumber": 179 + }, + { + "text": " \"-o\", \"ServerAliveCountMax=30\"", + "lineNumber": 180 + }, + { + "text": " };", + "lineNumber": 181 + }, + { + "lineNumber": 182 + }, + { + "text": " args.AddRange(parts.Options);", + "lineNumber": 183 + }, + { + "lineNumber": 184 + }, + { + "text": " if (parts.Port.HasValue)", + "lineNumber": 185 + }, + { + "text": " {", + "lineNumber": 186 + }, + { + "text": " args.Add(\"-P\");", + "lineNumber": 187 + }, + { + "text": " args.Add(parts.Port.Value.ToString());", + "lineNumber": 188 + }, + { + "text": " }", + "lineNumber": 189 + }, + { + "lineNumber": 190 + }, + { + "text": " return args;", + "lineNumber": 191 + }, + { + "text": " }", + "lineNumber": 192 + }, + { + "lineNumber": 193 + }, + { + "text": " public async Task<string> ExecuteRemoteCommandAsync(WorkerConfig worker, string command, bool interactive = false)", + "lineNumber": 194 + }, + { + "text": " {", + "lineNumber": 195 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 196 + }, + { + "text": " var sshArgs = BuildSshArgs(parts, interactive);", + "lineNumber": 197 + }, + { + "text": " sshArgs.Add(command);", + "lineNumber": 198 + }, + { + "lineNumber": 199 + }, + { + "text": " var argsString = string.Join(\" \", sshArgs.Select(arg => $\"\\\"{arg.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\"));", + "lineNumber": 200 + }, + { + "text": " ", + "lineNumber": 201 + }, + { + "text": " var process = new Process", + "lineNumber": 202 + }, + { + "text": " {", + "lineNumber": 203 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 204 + }, + { + "text": " {", + "lineNumber": 205 + }, + { + "text": " FileName = GetSshExecutable(),", + "lineNumber": 206 + }, + { + "text": " WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),", + "lineNumber": 207 + }, + { + "text": " Arguments = argsString,", + "lineNumber": 208 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 209 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 210 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 211 + }, + { + "text": " CreateNoWindow = !interactive", + "lineNumber": 212 + }, + { + "text": " }", + "lineNumber": 213 + }, + { + "text": " };", + "lineNumber": 214 + }, + { + "lineNumber": 215 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 216 + } + ] + }, + "score": 0.34909260272979736 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 55 + } + }, + "contents": "using System;\nusing System.IO;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text;\nusing System.Text.Json;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class WorkerControllerService\n {\n private readonly SshService _sshService;\n private readonly ConfigService _configService;\n private byte[]? _controllerScriptBytes;\n private byte[]? _attachHelperScriptBytes;\n\n public WorkerControllerService(SshService sshService, ConfigService configService)\n {\n _sshService = sshService;\n _configService = configService;\n }\n\n private byte[] GetControllerScriptBytes()\n {\n if (_controllerScriptBytes != null)\n return _controllerScriptBytes;\n\n var assembly = Assembly.GetExecutingAssembly();\n var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_controller.ps1\";\n\n using var stream = assembly.GetManifestResourceStream(resourceName);\n if (stream == null)\n throw new InvalidOperationException($\"Resource {resourceName} not found\");\n\n using var reader = new BinaryReader(stream);\n _controllerScriptBytes = reader.ReadBytes((int)stream.Length);\n return _controllerScriptBytes;\n }\n\n private byte[] GetAttachHelperScriptBytes()\n {\n if (_attachHelperScriptBytes != null)\n return _attachHelperScriptBytes;\n\n var assembly = Assembly.GetExecutingAssembly();\n var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_attach.ps1\";\n\n using var stream = assembly.GetManifestResourceStream(resourceName);\n if (stream == null)\n throw new InvalidOperationException($\"Resource {resourceName} not found\");\n\n using var reader = new BinaryReader(stream);", + "signatures": {}, + "detailedLines": [ + { + "text": "using System;", + "lineNumber": 1 + }, + { + "text": "using System.IO;", + "lineNumber": 2 + }, + { + "text": "using System.Linq;", + "lineNumber": 3 + }, + { + "text": "using System.Reflection;", + "lineNumber": 4 + }, + { + "text": "using System.Text;", + "lineNumber": 5 + }, + { + "text": "using System.Text.Json;", + "lineNumber": 6 + }, + { + "text": "using System.Threading;", + "lineNumber": 7 + }, + { + "text": "using System.Threading.Tasks;", + "lineNumber": 8 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 9 + }, + { + "lineNumber": 10 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 11 + }, + { + "text": "{", + "lineNumber": 12 + }, + { + "text": " public class WorkerControllerService", + "lineNumber": 13 + }, + { + "text": " {", + "lineNumber": 14 + }, + { + "text": " private readonly SshService _sshService;", + "lineNumber": 15 + }, + { + "text": " private readonly ConfigService _configService;", + "lineNumber": 16 + }, + { + "text": " private byte[]? _controllerScriptBytes;", + "lineNumber": 17 + }, + { + "text": " private byte[]? _attachHelperScriptBytes;", + "lineNumber": 18 + }, + { + "lineNumber": 19 + }, + { + "text": " public WorkerControllerService(SshService sshService, ConfigService configService)", + "lineNumber": 20 + }, + { + "text": " {", + "lineNumber": 21 + }, + { + "text": " _sshService = sshService;", + "lineNumber": 22 + }, + { + "text": " _configService = configService;", + "lineNumber": 23 + }, + { + "text": " }", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": " private byte[] GetControllerScriptBytes()", + "lineNumber": 26 + }, + { + "text": " {", + "lineNumber": 27 + }, + { + "text": " if (_controllerScriptBytes != null)", + "lineNumber": 28 + }, + { + "text": " return _controllerScriptBytes;", + "lineNumber": 29 + }, + { + "lineNumber": 30 + }, + { + "text": " var assembly = Assembly.GetExecutingAssembly();", + "lineNumber": 31 + }, + { + "text": " var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_controller.ps1\";", + "lineNumber": 32 + }, + { + "lineNumber": 33 + }, + { + "text": " using var stream = assembly.GetManifestResourceStream(resourceName);", + "lineNumber": 34 + }, + { + "text": " if (stream == null)", + "lineNumber": 35 + }, + { + "text": " throw new InvalidOperationException($\"Resource {resourceName} not found\");", + "lineNumber": 36 + }, + { + "lineNumber": 37 + }, + { + "text": " using var reader = new BinaryReader(stream);", + "lineNumber": 38 + }, + { + "text": " _controllerScriptBytes = reader.ReadBytes((int)stream.Length);", + "lineNumber": 39 + }, + { + "text": " return _controllerScriptBytes;", + "lineNumber": 40 + }, + { + "text": " }", + "lineNumber": 41 + }, + { + "lineNumber": 42 + }, + { + "text": " private byte[] GetAttachHelperScriptBytes()", + "lineNumber": 43 + }, + { + "text": " {", + "lineNumber": 44 + }, + { + "text": " if (_attachHelperScriptBytes != null)", + "lineNumber": 45 + }, + { + "text": " return _attachHelperScriptBytes;", + "lineNumber": 46 + }, + { + "lineNumber": 47 + }, + { + "text": " var assembly = Assembly.GetExecutingAssembly();", + "lineNumber": 48 + }, + { + "text": " var resourceName = \"UnifiedFarmLauncher.Scripts.remote_worker_attach.ps1\";", + "lineNumber": 49 + }, + { + "lineNumber": 50 + }, + { + "text": " using var stream = assembly.GetManifestResourceStream(resourceName);", + "lineNumber": 51 + }, + { + "text": " if (stream == null)", + "lineNumber": 52 + }, + { + "text": " throw new InvalidOperationException($\"Resource {resourceName} not found\");", + "lineNumber": 53 + }, + { + "lineNumber": 54 + }, + { + "text": " using var reader = new BinaryReader(stream);", + "lineNumber": 55 + } + ] + }, + "score": 0.34012264013290405 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 342 + }, + "endPosition": { + "line": 361 + } + }, + "contents": " var process = new Process\n {\n StartInfo = new ProcessStartInfo\n {\n FileName = GetSshExecutable(),\n WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),\n Arguments = argsString,\n UseShellExecute = true,\n CreateNoWindow = false\n }\n };\n\n process.Start();\n return process;\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": " var process = new Process", + "lineNumber": 344 + }, + { + "text": " {", + "lineNumber": 345 + }, + { + "text": " StartInfo = new ProcessStartInfo", + "lineNumber": 346 + }, + { + "text": " {", + "lineNumber": 347 + }, + { + "text": " FileName = GetSshExecutable(),", + "lineNumber": 348 + }, + { + "text": " WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),", + "lineNumber": 349 + }, + { + "text": " Arguments = argsString,", + "lineNumber": 350 + }, + { + "text": " UseShellExecute = true,", + "lineNumber": 351 + }, + { + "text": " CreateNoWindow = false", + "lineNumber": 352 + }, + { + "text": " }", + "lineNumber": 353 + }, + { + "text": " };", + "lineNumber": 354 + }, + { + "lineNumber": 355 + }, + { + "text": " process.Start();", + "lineNumber": 356 + }, + { + "text": " return process;", + "lineNumber": 357 + }, + { + "text": " }", + "lineNumber": 358 + }, + { + "text": " }", + "lineNumber": 359 + }, + { + "text": "}", + "lineNumber": 360 + } + ] + }, + "score": 0.3396768569946289 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 257 + }, + "endPosition": { + "line": 301 + } + }, + "contents": " };\n\n var output = new StringBuilder();\n var error = new StringBuilder();\n\n process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };\n process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };\n\n process.Start();\n process.BeginOutputReadLine();\n process.BeginErrorReadLine();\n\n // Write script to stdin\n await process.StandardInput.WriteAsync(script);\n process.StandardInput.Close();\n\n await process.WaitForExitAsync();\n\n if (process.ExitCode != 0 && !interactive)\n {\n throw new InvalidOperationException($\"SSH script execution failed with exit code {process.ExitCode}: {error}\");\n }\n\n return output.ToString();\n }\n\n public async Task<string> GetWorkerBasePathAsync(WorkerConfig worker)\n {\n if (_workerBasePathCache.TryGetValue(worker.Name, out var cached))\n return cached;\n\n var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);\n var scriptBlock = \"$ProgressPreference='SilentlyContinue'; [Environment]::GetFolderPath('LocalApplicationData')\";\n var encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock));\n var remoteCmd = $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {encoded}\";\n\n var output = await ExecuteRemoteCommandAsync(worker, remoteCmd);\n var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);\n var basePath = lines.LastOrDefault()?.Trim();\n\n if (string.IsNullOrEmpty(basePath))\n throw new InvalidOperationException($\"Unable to read LocalAppData path on {worker.Name}.\");\n\n var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");", + "signatures": {}, + "detailedLines": [ + { + "text": " };", + "lineNumber": 258 + }, + { + "lineNumber": 259 + }, + { + "text": " var output = new StringBuilder();", + "lineNumber": 260 + }, + { + "text": " var error = new StringBuilder();", + "lineNumber": 261 + }, + { + "lineNumber": 262 + }, + { + "text": " process.OutputDataReceived += (s, e) => { if (e.Data != null) output.AppendLine(e.Data); };", + "lineNumber": 263 + }, + { + "text": " process.ErrorDataReceived += (s, e) => { if (e.Data != null) error.AppendLine(e.Data); };", + "lineNumber": 264 + }, + { + "lineNumber": 265 + }, + { + "text": " process.Start();", + "lineNumber": 266 + }, + { + "text": " process.BeginOutputReadLine();", + "lineNumber": 267 + }, + { + "text": " process.BeginErrorReadLine();", + "lineNumber": 268 + }, + { + "lineNumber": 269 + }, + { + "text": " // Write script to stdin", + "lineNumber": 270 + }, + { + "text": " await process.StandardInput.WriteAsync(script);", + "lineNumber": 271 + }, + { + "text": " process.StandardInput.Close();", + "lineNumber": 272 + }, + { + "lineNumber": 273 + }, + { + "text": " await process.WaitForExitAsync();", + "lineNumber": 274 + }, + { + "lineNumber": 275 + }, + { + "text": " if (process.ExitCode != 0 && !interactive)", + "lineNumber": 276 + }, + { + "text": " {", + "lineNumber": 277 + }, + { + "text": " throw new InvalidOperationException($\"SSH script execution failed with exit code {process.ExitCode}: {error}\");", + "lineNumber": 278 + }, + { + "text": " }", + "lineNumber": 279 + }, + { + "lineNumber": 280 + }, + { + "text": " return output.ToString();", + "lineNumber": 281 + }, + { + "text": " }", + "lineNumber": 282 + }, + { + "lineNumber": 283 + }, + { + "text": " public async Task<string> GetWorkerBasePathAsync(WorkerConfig worker)", + "lineNumber": 284 + }, + { + "text": " {", + "lineNumber": 285 + }, + { + "text": " if (_workerBasePathCache.TryGetValue(worker.Name, out var cached))", + "lineNumber": 286 + }, + { + "text": " return cached;", + "lineNumber": 287 + }, + { + "lineNumber": 288 + }, + { + "text": " var parts = ParseConnectionParts(worker.Ssh.Args, worker.Ssh.Host);", + "lineNumber": 289 + }, + { + "text": " var scriptBlock = \"$ProgressPreference='SilentlyContinue'; [Environment]::GetFolderPath('LocalApplicationData')\";", + "lineNumber": 290 + }, + { + "text": " var encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock));", + "lineNumber": 291 + }, + { + "text": " var remoteCmd = $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {encoded}\";", + "lineNumber": 292 + }, + { + "lineNumber": 293 + }, + { + "text": " var output = await ExecuteRemoteCommandAsync(worker, remoteCmd);", + "lineNumber": 294 + }, + { + "text": " var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);", + "lineNumber": 295 + }, + { + "text": " var basePath = lines.LastOrDefault()?.Trim();", + "lineNumber": 296 + }, + { + "lineNumber": 297 + }, + { + "text": " if (string.IsNullOrEmpty(basePath))", + "lineNumber": 298 + }, + { + "text": " throw new InvalidOperationException($\"Unable to read LocalAppData path on {worker.Name}.\");", + "lineNumber": 299 + }, + { + "lineNumber": 300 + }, + { + "text": " var finalPath = Path.Combine(basePath, \"UnifiedWorkers\");", + "lineNumber": 301 + } + ] + }, + "score": 0.3372674584388733 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 354, + "column": 16 + }, + "endPosition": { + "line": 398 + } + }, + "contents": "}\n }\n\n if (status == null || (status.Status != \"running\" && status.Status != \"launching\"))\n {\n // Gather comprehensive diagnostic information\n var diagnostics = await GatherDiagnosticsAsync(worker, workerType);\n var errorMessage = status?.ErrorMessage ?? \"Worker failed to start. Check logs for details.\";\n var statusText = status?.Status ?? \"unknown\";\n throw new InvalidOperationException($\"Worker did not start successfully. Status: {statusText}. {errorMessage}\\n\\n{diagnostics}\");\n }\n }\n\n private async Task<WorkerStatus?> GetWorkerStatusAsync(WorkerConfig worker, string workerType)\n {\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n var instanceRoot = Path.Combine(remoteBasePath, workerType, worker.Name);\n var metaPath = Path.Combine(instanceRoot, \"state\", \"worker-info.json\");\n\n var script = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\n$instanceRoot = Join-Path (Join-Path $dataRoot '{workerType}') '{worker.Name}'\n$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'\nif (Test-Path $metaPath) {{\n try {{\n $meta = Get-Content $metaPath -Raw | ConvertFrom-Json\n $meta | ConvertTo-Json -Depth 5 -Compress\n }} catch {{\n Write-Error \"\"Error reading metadata: $($_.Exception.Message)\"\"\n }}\n}} else {{\n Write-Error \"\"Metadata file not found at $metaPath\"\"\n}}\n\";\n\n try\n {\n var output = await _sshService.ExecuteRemoteScriptAsync(worker, script);\n if (string.IsNullOrWhiteSpace(output))\n return null;\n\n var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);\n // Look for JSON line - it might be on a single line or multiple lines", + "signatures": {}, + "detailedLines": [ + { + "text": "}", + "lineNumber": 355 + }, + { + "text": " }", + "lineNumber": 356 + }, + { + "lineNumber": 357 + }, + { + "text": " if (status == null || (status.Status != \"running\" && status.Status != \"launching\"))", + "lineNumber": 358 + }, + { + "text": " {", + "lineNumber": 359 + }, + { + "text": " // Gather comprehensive diagnostic information", + "lineNumber": 360 + }, + { + "text": " var diagnostics = await GatherDiagnosticsAsync(worker, workerType);", + "lineNumber": 361 + }, + { + "text": " var errorMessage = status?.ErrorMessage ?? \"Worker failed to start. Check logs for details.\";", + "lineNumber": 362 + }, + { + "text": " var statusText = status?.Status ?? \"unknown\";", + "lineNumber": 363 + }, + { + "text": " throw new InvalidOperationException($\"Worker did not start successfully. Status: {statusText}. {errorMessage}\\n\\n{diagnostics}\");", + "lineNumber": 364 + }, + { + "text": " }", + "lineNumber": 365 + }, + { + "text": " }", + "lineNumber": 366 + }, + { + "lineNumber": 367 + }, + { + "text": " private async Task<WorkerStatus?> GetWorkerStatusAsync(WorkerConfig worker, string workerType)", + "lineNumber": 368 + }, + { + "text": " {", + "lineNumber": 369 + }, + { + "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);", + "lineNumber": 370 + }, + { + "text": " var instanceRoot = Path.Combine(remoteBasePath, workerType, worker.Name);", + "lineNumber": 371 + }, + { + "text": " var metaPath = Path.Combine(instanceRoot, \"state\", \"worker-info.json\");", + "lineNumber": 372 + }, + { + "lineNumber": 373 + }, + { + "text": " var script = $@\"", + "lineNumber": 374 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 375 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 376 + }, + { + "text": "$instanceRoot = Join-Path (Join-Path $dataRoot '{workerType}') '{worker.Name}'", + "lineNumber": 377 + }, + { + "text": "$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'", + "lineNumber": 378 + }, + { + "text": "if (Test-Path $metaPath) {{", + "lineNumber": 379 + }, + { + "text": " try {{", + "lineNumber": 380 + }, + { + "text": " $meta = Get-Content $metaPath -Raw | ConvertFrom-Json", + "lineNumber": 381 + }, + { + "text": " $meta | ConvertTo-Json -Depth 5 -Compress", + "lineNumber": 382 + }, + { + "text": " }} catch {{", + "lineNumber": 383 + }, + { + "text": " Write-Error \"\"Error reading metadata: $($_.Exception.Message)\"\"", + "lineNumber": 384 + }, + { + "text": " }}", + "lineNumber": 385 + }, + { + "text": "}} else {{", + "lineNumber": 386 + }, + { + "text": " Write-Error \"\"Metadata file not found at $metaPath\"\"", + "lineNumber": 387 + }, + { + "text": "}}", + "lineNumber": 388 + }, + { + "text": "\";", + "lineNumber": 389 + }, + { + "lineNumber": 390 + }, + { + "text": " try", + "lineNumber": 391 + }, + { + "text": " {", + "lineNumber": 392 + }, + { + "text": " var output = await _sshService.ExecuteRemoteScriptAsync(worker, script);", + "lineNumber": 393 + }, + { + "text": " if (string.IsNullOrWhiteSpace(output))", + "lineNumber": 394 + }, + { + "text": " return null;", + "lineNumber": 395 + }, + { + "lineNumber": 396 + }, + { + "text": " var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);", + "lineNumber": 397 + }, + { + "text": " // Look for JSON line - it might be on a single line or multiple lines", + "lineNumber": 398 + } + ] + }, + "score": 0.32565149664878845 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 443 + }, + "endPosition": { + "line": 495 + } + }, + "contents": " var script = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\n$instanceRoot = Join-Path (Join-Path $dataRoot '{workerType}') '{worker.Name}'\n$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'\n$logPath = Join-Path $instanceRoot 'logs\\worker.log'\n$controllerPath = Join-Path $dataRoot 'controller.ps1'\n\nWrite-Host \"\"=== Diagnostics ===\"\" -ForegroundColor Cyan\nWrite-Host \"\"Metadata file exists: $((Test-Path $metaPath))\"\"\nWrite-Host \"\"Log file exists: $((Test-Path $logPath))\"\"\nWrite-Host \"\"Controller script exists: $((Test-Path $controllerPath))\"\"\n\nif (Test-Path $metaPath) {{\n try {{\n $meta = Get-Content $metaPath -Raw | ConvertFrom-Json\n Write-Host \"\"Metadata Status: $($meta.Status)\"\"\n Write-Host \"\"Controller PID: $($meta.ControllerPid)\"\"\n Write-Host \"\"Worker PID: $($meta.WorkerPid)\"\"\n if ($meta.ErrorMessage) {{\n Write-Host \"\"Error Message: $($meta.ErrorMessage)\"\" -ForegroundColor Red\n }}\n }} catch {{\n Write-Host \"\"Error reading metadata: $($_.Exception.Message)\"\" -ForegroundColor Red\n }}\n}} else {{\n Write-Host \"\"Metadata file not found - controller may not have started\"\" -ForegroundColor Yellow\n}}\n\n# Check if controller process is running\n$controllerRunning = $false\nif (Test-Path $metaPath) {{\n try {{\n $meta = Get-Content $metaPath -Raw | ConvertFrom-Json\n if ($meta.ControllerPid) {{\n $proc = Get-Process -Id $meta.ControllerPid -ErrorAction SilentlyContinue\n $controllerRunning = ($null -ne $proc)\n Write-Host \"\"Controller process running: $controllerRunning\"\"\n }}\n }} catch {{\n Write-Host \"\"Could not check controller process: $($_.Exception.Message)\"\"\n }}\n}}\n\nWrite-Host \"\"`n=== Recent Log Entries (last 30 lines) ===\"\" -ForegroundColor Cyan\nif (Test-Path $logPath) {{\n try {{\n $logLines = Get-Content $logPath -Tail 30 -ErrorAction SilentlyContinue\n if ($logLines) {{\n $logLines | ForEach-Object {{ Write-Host $_ }}\n }} else {{\n Write-Host \"\"Log file exists but is empty\"\" -ForegroundColor Yellow", + "signatures": {}, + "detailedLines": [ + { + "text": " var script = $@\"", + "lineNumber": 444 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 445 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 446 + }, + { + "text": "$instanceRoot = Join-Path (Join-Path $dataRoot '{workerType}') '{worker.Name}'", + "lineNumber": 447 + }, + { + "text": "$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'", + "lineNumber": 448 + }, + { + "text": "$logPath = Join-Path $instanceRoot 'logs\\worker.log'", + "lineNumber": 449 + }, + { + "text": "$controllerPath = Join-Path $dataRoot 'controller.ps1'", + "lineNumber": 450 + }, + { + "lineNumber": 451 + }, + { + "text": "Write-Host \"\"=== Diagnostics ===\"\" -ForegroundColor Cyan", + "lineNumber": 452 + }, + { + "text": "Write-Host \"\"Metadata file exists: $((Test-Path $metaPath))\"\"", + "lineNumber": 453 + }, + { + "text": "Write-Host \"\"Log file exists: $((Test-Path $logPath))\"\"", + "lineNumber": 454 + }, + { + "text": "Write-Host \"\"Controller script exists: $((Test-Path $controllerPath))\"\"", + "lineNumber": 455 + }, + { + "lineNumber": 456 + }, + { + "text": "if (Test-Path $metaPath) {{", + "lineNumber": 457 + }, + { + "text": " try {{", + "lineNumber": 458 + }, + { + "text": " $meta = Get-Content $metaPath -Raw | ConvertFrom-Json", + "lineNumber": 459 + }, + { + "text": " Write-Host \"\"Metadata Status: $($meta.Status)\"\"", + "lineNumber": 460 + }, + { + "text": " Write-Host \"\"Controller PID: $($meta.ControllerPid)\"\"", + "lineNumber": 461 + }, + { + "text": " Write-Host \"\"Worker PID: $($meta.WorkerPid)\"\"", + "lineNumber": 462 + }, + { + "text": " if ($meta.ErrorMessage) {{", + "lineNumber": 463 + }, + { + "text": " Write-Host \"\"Error Message: $($meta.ErrorMessage)\"\" -ForegroundColor Red", + "lineNumber": 464 + }, + { + "text": " }}", + "lineNumber": 465 + }, + { + "text": " }} catch {{", + "lineNumber": 466 + }, + { + "text": " Write-Host \"\"Error reading metadata: $($_.Exception.Message)\"\" -ForegroundColor Red", + "lineNumber": 467 + }, + { + "text": " }}", + "lineNumber": 468 + }, + { + "text": "}} else {{", + "lineNumber": 469 + }, + { + "text": " Write-Host \"\"Metadata file not found - controller may not have started\"\" -ForegroundColor Yellow", + "lineNumber": 470 + }, + { + "text": "}}", + "lineNumber": 471 + }, + { + "lineNumber": 472 + }, + { + "text": "# Check if controller process is running", + "lineNumber": 473 + }, + { + "text": "$controllerRunning = $false", + "lineNumber": 474 + }, + { + "text": "if (Test-Path $metaPath) {{", + "lineNumber": 475 + }, + { + "text": " try {{", + "lineNumber": 476 + }, + { + "text": " $meta = Get-Content $metaPath -Raw | ConvertFrom-Json", + "lineNumber": 477 + }, + { + "text": " if ($meta.ControllerPid) {{", + "lineNumber": 478 + }, + { + "text": " $proc = Get-Process -Id $meta.ControllerPid -ErrorAction SilentlyContinue", + "lineNumber": 479 + }, + { + "text": " $controllerRunning = ($null -ne $proc)", + "lineNumber": 480 + }, + { + "text": " Write-Host \"\"Controller process running: $controllerRunning\"\"", + "lineNumber": 481 + }, + { + "text": " }}", + "lineNumber": 482 + }, + { + "text": " }} catch {{", + "lineNumber": 483 + }, + { + "text": " Write-Host \"\"Could not check controller process: $($_.Exception.Message)\"\"", + "lineNumber": 484 + }, + { + "text": " }}", + "lineNumber": 485 + }, + { + "text": "}}", + "lineNumber": 486 + }, + { + "lineNumber": 487 + }, + { + "text": "Write-Host \"\"`n=== Recent Log Entries (last 30 lines) ===\"\" -ForegroundColor Cyan", + "lineNumber": 488 + }, + { + "text": "if (Test-Path $logPath) {{", + "lineNumber": 489 + }, + { + "text": " try {{", + "lineNumber": 490 + }, + { + "text": " $logLines = Get-Content $logPath -Tail 30 -ErrorAction SilentlyContinue", + "lineNumber": 491 + }, + { + "text": " if ($logLines) {{", + "lineNumber": 492 + }, + { + "text": " $logLines | ForEach-Object {{ Write-Host $_ }}", + "lineNumber": 493 + }, + { + "text": " }} else {{", + "lineNumber": 494 + }, + { + "text": " Write-Host \"\"Log file exists but is empty\"\" -ForegroundColor Yellow", + "lineNumber": 495 + } + ] + }, + "score": 0.2959611415863037 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 394, + "column": 20 + }, + "endPosition": { + "line": 446 + } + }, + "contents": "return null;\n\n var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);\n // Look for JSON line - it might be on a single line or multiple lines\n var jsonLines = lines.Where(line => line.Trim().StartsWith(\"{\")).ToList();\n \n string? jsonText = null;\n if (jsonLines.Count > 0)\n {\n // Use the first JSON line found\n jsonText = jsonLines[0].Trim();\n }\n else\n {\n // Try to find JSON that might span multiple lines\n var fullText = string.Join(\"\", lines);\n var startIdx = fullText.IndexOf('{');\n if (startIdx >= 0)\n {\n var endIdx = fullText.LastIndexOf('}');\n if (endIdx > startIdx)\n {\n jsonText = fullText.Substring(startIdx, endIdx - startIdx + 1);\n }\n }\n }\n\n if (string.IsNullOrEmpty(jsonText))\n return null;\n\n var options = new JsonSerializerOptions\n {\n PropertyNameCaseInsensitive = true\n };\n \n return JsonSerializer.Deserialize<WorkerStatus>(jsonText, options);\n }\n catch (Exception ex)\n {\n // Log the exception for debugging but return null\n System.Diagnostics.Debug.WriteLine($\"Error parsing worker status: {ex.Message}\");\n return null;\n }\n }\n\n private async Task<string> GatherDiagnosticsAsync(WorkerConfig worker, string workerType)\n {\n var diagnostics = new StringBuilder();\n \n var script = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "signatures": {}, + "detailedLines": [ + { + "text": "return null;", + "lineNumber": 395 + }, + { + "lineNumber": 396 + }, + { + "text": " var lines = output.Split(new[] { '\\r', '\\n' }, StringSplitOptions.RemoveEmptyEntries);", + "lineNumber": 397 + }, + { + "text": " // Look for JSON line - it might be on a single line or multiple lines", + "lineNumber": 398 + }, + { + "text": " var jsonLines = lines.Where(line => line.Trim().StartsWith(\"{\")).ToList();", + "lineNumber": 399 + }, + { + "text": " ", + "lineNumber": 400 + }, + { + "text": " string? jsonText = null;", + "lineNumber": 401 + }, + { + "text": " if (jsonLines.Count > 0)", + "lineNumber": 402 + }, + { + "text": " {", + "lineNumber": 403 + }, + { + "text": " // Use the first JSON line found", + "lineNumber": 404 + }, + { + "text": " jsonText = jsonLines[0].Trim();", + "lineNumber": 405 + }, + { + "text": " }", + "lineNumber": 406 + }, + { + "text": " else", + "lineNumber": 407 + }, + { + "text": " {", + "lineNumber": 408 + }, + { + "text": " // Try to find JSON that might span multiple lines", + "lineNumber": 409 + }, + { + "text": " var fullText = string.Join(\"\", lines);", + "lineNumber": 410 + }, + { + "text": " var startIdx = fullText.IndexOf('{');", + "lineNumber": 411 + }, + { + "text": " if (startIdx >= 0)", + "lineNumber": 412 + }, + { + "text": " {", + "lineNumber": 413 + }, + { + "text": " var endIdx = fullText.LastIndexOf('}');", + "lineNumber": 414 + }, + { + "text": " if (endIdx > startIdx)", + "lineNumber": 415 + }, + { + "text": " {", + "lineNumber": 416 + }, + { + "text": " jsonText = fullText.Substring(startIdx, endIdx - startIdx + 1);", + "lineNumber": 417 + }, + { + "text": " }", + "lineNumber": 418 + }, + { + "text": " }", + "lineNumber": 419 + }, + { + "text": " }", + "lineNumber": 420 + }, + { + "lineNumber": 421 + }, + { + "text": " if (string.IsNullOrEmpty(jsonText))", + "lineNumber": 422 + }, + { + "text": " return null;", + "lineNumber": 423 + }, + { + "lineNumber": 424 + }, + { + "text": " var options = new JsonSerializerOptions", + "lineNumber": 425 + }, + { + "text": " {", + "lineNumber": 426 + }, + { + "text": " PropertyNameCaseInsensitive = true", + "lineNumber": 427 + }, + { + "text": " };", + "lineNumber": 428 + }, + { + "text": " ", + "lineNumber": 429 + }, + { + "text": " return JsonSerializer.Deserialize<WorkerStatus>(jsonText, options);", + "lineNumber": 430 + }, + { + "text": " }", + "lineNumber": 431 + }, + { + "text": " catch (Exception ex)", + "lineNumber": 432 + }, + { + "text": " {", + "lineNumber": 433 + }, + { + "text": " // Log the exception for debugging but return null", + "lineNumber": 434 + }, + { + "text": " System.Diagnostics.Debug.WriteLine($\"Error parsing worker status: {ex.Message}\");", + "lineNumber": 435 + }, + { + "text": " return null;", + "lineNumber": 436 + }, + { + "text": " }", + "lineNumber": 437 + }, + { + "text": " }", + "lineNumber": 438 + }, + { + "lineNumber": 439 + }, + { + "text": " private async Task<string> GatherDiagnosticsAsync(WorkerConfig worker, string workerType)", + "lineNumber": 440 + }, + { + "text": " {", + "lineNumber": 441 + }, + { + "text": " var diagnostics = new StringBuilder();", + "lineNumber": 442 + }, + { + "text": " ", + "lineNumber": 443 + }, + { + "text": " var script = $@\"", + "lineNumber": 444 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 445 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 446 + } + ] + }, + "score": 0.2423451989889145 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 40 + }, + "endPosition": { + "line": 98 + } + }, + "contents": " }\n return \"ssh\";\n }\n\n private static string GetScpExecutable()\n {\n if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))\n {\n var paths = new[]\n {\n Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"scp.exe\"),\n \"scp.exe\"\n };\n foreach (var path in paths)\n {\n if (File.Exists(path) || IsInPath(\"scp.exe\"))\n return \"scp.exe\";\n }\n return \"scp.exe\";\n }\n return \"scp\";\n }\n\n private static bool IsInPath(string executable)\n {\n try\n {\n var process = Process.Start(new ProcessStartInfo\n {\n FileName = executable,\n Arguments = \"-V\",\n UseShellExecute = false,\n RedirectStandardOutput = true,\n RedirectStandardError = true,\n CreateNoWindow = true\n });\n process?.WaitForExit(1000);\n return process?.ExitCode == 0 || process?.ExitCode == 1; // SSH/SCP typically exit with 1 for version info\n }\n catch\n {\n return false;\n }\n }\n\n public SshConnectionParts ParseConnectionParts(string rawArgs, string defaultHost)\n {\n var parts = new SshConnectionParts { Host = defaultHost };\n if (string.IsNullOrWhiteSpace(rawArgs))\n return parts;\n\n var tokens = rawArgs.Split(new[] { ' ', '\\t' }, StringSplitOptions.RemoveEmptyEntries);\n var options = new List<string>();\n string? targetHost = null;\n int? port = null;\n bool requestPty = false;\n\n var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };", + "signatures": {}, + "detailedLines": [ + { + "text": " }", + "lineNumber": 41 + }, + { + "text": " return \"ssh\";", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": " private static string GetScpExecutable()", + "lineNumber": 45 + }, + { + "text": " {", + "lineNumber": 46 + }, + { + "text": " if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))", + "lineNumber": 47 + }, + { + "text": " {", + "lineNumber": 48 + }, + { + "text": " var paths = new[]", + "lineNumber": 49 + }, + { + "text": " {", + "lineNumber": 50 + }, + { + "text": " Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"scp.exe\"),", + "lineNumber": 51 + }, + { + "text": " \"scp.exe\"", + "lineNumber": 52 + }, + { + "text": " };", + "lineNumber": 53 + }, + { + "text": " foreach (var path in paths)", + "lineNumber": 54 + }, + { + "text": " {", + "lineNumber": 55 + }, + { + "text": " if (File.Exists(path) || IsInPath(\"scp.exe\"))", + "lineNumber": 56 + }, + { + "text": " return \"scp.exe\";", + "lineNumber": 57 + }, + { + "text": " }", + "lineNumber": 58 + }, + { + "text": " return \"scp.exe\";", + "lineNumber": 59 + }, + { + "text": " }", + "lineNumber": 60 + }, + { + "text": " return \"scp\";", + "lineNumber": 61 + }, + { + "text": " }", + "lineNumber": 62 + }, + { + "lineNumber": 63 + }, + { + "text": " private static bool IsInPath(string executable)", + "lineNumber": 64 + }, + { + "text": " {", + "lineNumber": 65 + }, + { + "text": " try", + "lineNumber": 66 + }, + { + "text": " {", + "lineNumber": 67 + }, + { + "text": " var process = Process.Start(new ProcessStartInfo", + "lineNumber": 68 + }, + { + "text": " {", + "lineNumber": 69 + }, + { + "text": " FileName = executable,", + "lineNumber": 70 + }, + { + "text": " Arguments = \"-V\",", + "lineNumber": 71 + }, + { + "text": " UseShellExecute = false,", + "lineNumber": 72 + }, + { + "text": " RedirectStandardOutput = true,", + "lineNumber": 73 + }, + { + "text": " RedirectStandardError = true,", + "lineNumber": 74 + }, + { + "text": " CreateNoWindow = true", + "lineNumber": 75 + }, + { + "text": " });", + "lineNumber": 76 + }, + { + "text": " process?.WaitForExit(1000);", + "lineNumber": 77 + }, + { + "text": " return process?.ExitCode == 0 || process?.ExitCode == 1; // SSH/SCP typically exit with 1 for version info", + "lineNumber": 78 + }, + { + "text": " }", + "lineNumber": 79 + }, + { + "text": " catch", + "lineNumber": 80 + }, + { + "text": " {", + "lineNumber": 81 + }, + { + "text": " return false;", + "lineNumber": 82 + }, + { + "text": " }", + "lineNumber": 83 + }, + { + "text": " }", + "lineNumber": 84 + }, + { + "lineNumber": 85 + }, + { + "text": " public SshConnectionParts ParseConnectionParts(string rawArgs, string defaultHost)", + "lineNumber": 86 + }, + { + "text": " {", + "lineNumber": 87 + }, + { + "text": " var parts = new SshConnectionParts { Host = defaultHost };", + "lineNumber": 88 + }, + { + "text": " if (string.IsNullOrWhiteSpace(rawArgs))", + "lineNumber": 89 + }, + { + "text": " return parts;", + "lineNumber": 90 + }, + { + "lineNumber": 91 + }, + { + "text": " var tokens = rawArgs.Split(new[] { ' ', '\\t' }, StringSplitOptions.RemoveEmptyEntries);", + "lineNumber": 92 + }, + { + "text": " var options = new List<string>();", + "lineNumber": 93 + }, + { + "text": " string? targetHost = null;", + "lineNumber": 94 + }, + { + "text": " int? port = null;", + "lineNumber": 95 + }, + { + "text": " bool requestPty = false;", + "lineNumber": 96 + }, + { + "lineNumber": 97 + }, + { + "text": " var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };", + "lineNumber": 98 + } + ] + }, + "score": 0.2259330004453659 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 45 + } + }, + "contents": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Text;\nusing System.Threading.Tasks;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class SshConnectionParts\n {\n public string Host { get; set; } = string.Empty;\n public List<string> Options { get; set; } = new();\n public int? Port { get; set; }\n public bool RequestPty { get; set; }\n }\n\n public class SshService\n {\n private readonly Dictionary<string, string> _workerBasePathCache = new();\n\n private static string GetSshExecutable()\n {\n if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))\n {\n // Try common Windows OpenSSH locations\n var paths = new[]\n {\n Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"ssh.exe\"),\n \"ssh.exe\" // In PATH\n };\n foreach (var path in paths)\n {\n if (File.Exists(path) || IsInPath(\"ssh.exe\"))\n return \"ssh.exe\";\n }\n return \"ssh.exe\";\n }\n return \"ssh\";\n }\n\n private static string GetScpExecutable()", + "signatures": {}, + "detailedLines": [ + { + "text": "using System;", + "lineNumber": 1 + }, + { + "text": "using System.Collections.Generic;", + "lineNumber": 2 + }, + { + "text": "using System.Diagnostics;", + "lineNumber": 3 + }, + { + "text": "using System.IO;", + "lineNumber": 4 + }, + { + "text": "using System.Linq;", + "lineNumber": 5 + }, + { + "text": "using System.Runtime.InteropServices;", + "lineNumber": 6 + }, + { + "text": "using System.Text;", + "lineNumber": 7 + }, + { + "text": "using System.Threading.Tasks;", + "lineNumber": 8 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 9 + }, + { + "lineNumber": 10 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 11 + }, + { + "text": "{", + "lineNumber": 12 + }, + { + "text": " public class SshConnectionParts", + "lineNumber": 13 + }, + { + "text": " {", + "lineNumber": 14 + }, + { + "text": " public string Host { get; set; } = string.Empty;", + "lineNumber": 15 + }, + { + "text": " public List<string> Options { get; set; } = new();", + "lineNumber": 16 + }, + { + "text": " public int? Port { get; set; }", + "lineNumber": 17 + }, + { + "text": " public bool RequestPty { get; set; }", + "lineNumber": 18 + }, + { + "text": " }", + "lineNumber": 19 + }, + { + "lineNumber": 20 + }, + { + "text": " public class SshService", + "lineNumber": 21 + }, + { + "text": " {", + "lineNumber": 22 + }, + { + "text": " private readonly Dictionary<string, string> _workerBasePathCache = new();", + "lineNumber": 23 + }, + { + "lineNumber": 24 + }, + { + "text": " private static string GetSshExecutable()", + "lineNumber": 25 + }, + { + "text": " {", + "lineNumber": 26 + }, + { + "text": " if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))", + "lineNumber": 27 + }, + { + "text": " {", + "lineNumber": 28 + }, + { + "text": " // Try common Windows OpenSSH locations", + "lineNumber": 29 + }, + { + "text": " var paths = new[]", + "lineNumber": 30 + }, + { + "text": " {", + "lineNumber": 31 + }, + { + "text": " Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"OpenSSH\", \"ssh.exe\"),", + "lineNumber": 32 + }, + { + "text": " \"ssh.exe\" // In PATH", + "lineNumber": 33 + }, + { + "text": " };", + "lineNumber": 34 + }, + { + "text": " foreach (var path in paths)", + "lineNumber": 35 + }, + { + "text": " {", + "lineNumber": 36 + }, + { + "text": " if (File.Exists(path) || IsInPath(\"ssh.exe\"))", + "lineNumber": 37 + }, + { + "text": " return \"ssh.exe\";", + "lineNumber": 38 + }, + { + "text": " }", + "lineNumber": 39 + }, + { + "text": " return \"ssh.exe\";", + "lineNumber": 40 + }, + { + "text": " }", + "lineNumber": 41 + }, + { + "text": " return \"ssh\";", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": " private static string GetScpExecutable()", + "lineNumber": 45 + } + ] + }, + "score": 0.2189311385154724 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\SshService.cs", + "range": { + "startPosition": { + "line": 95 + }, + "endPosition": { + "line": 154 + } + }, + "contents": " bool requestPty = false;\n\n var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };\n\n for (int i = 0; i < tokens.Length; i++)\n {\n var token = tokens[i];\n if (token == \"-t\" || token == \"-tt\")\n {\n requestPty = true;\n continue;\n }\n\n if (token == \"-p\" && i + 1 < tokens.Length)\n {\n if (int.TryParse(tokens[i + 1], out var portValue))\n {\n port = portValue;\n i++;\n }\n continue;\n }\n\n if (token.StartsWith(\"-\"))\n {\n options.Add(token);\n if (optionsWithArgs.Contains(token) && i + 1 < tokens.Length)\n {\n options.Add(tokens[i + 1]);\n i++;\n }\n continue;\n }\n\n if (targetHost == null)\n {\n targetHost = token;\n continue;\n }\n\n options.Add(token);\n }\n\n parts.Host = targetHost ?? defaultHost;\n parts.Options = options;\n parts.Port = port;\n parts.RequestPty = requestPty;\n return parts;\n }\n\n public List<string> BuildSshArgs(SshConnectionParts parts, bool interactive)\n {\n var args = new List<string>\n {\n \"-o\", \"ServerAliveInterval=60\",\n \"-o\", \"ServerAliveCountMax=30\"\n };\n\n if (interactive && parts.RequestPty)", + "signatures": {}, + "detailedLines": [ + { + "text": " bool requestPty = false;", + "lineNumber": 96 + }, + { + "lineNumber": 97 + }, + { + "text": " var optionsWithArgs = new HashSet<string> { \"-i\", \"-o\", \"-c\", \"-D\", \"-E\", \"-F\", \"-I\", \"-J\", \"-L\", \"-l\", \"-m\", \"-O\", \"-Q\", \"-R\", \"-S\", \"-W\", \"-w\" };", + "lineNumber": 98 + }, + { + "lineNumber": 99 + }, + { + "text": " for (int i = 0; i < tokens.Length; i++)", + "lineNumber": 100 + }, + { + "text": " {", + "lineNumber": 101 + }, + { + "text": " var token = tokens[i];", + "lineNumber": 102 + }, + { + "text": " if (token == \"-t\" || token == \"-tt\")", + "lineNumber": 103 + }, + { + "text": " {", + "lineNumber": 104 + }, + { + "text": " requestPty = true;", + "lineNumber": 105 + }, + { + "text": " continue;", + "lineNumber": 106 + }, + { + "text": " }", + "lineNumber": 107 + }, + { + "lineNumber": 108 + }, + { + "text": " if (token == \"-p\" && i + 1 < tokens.Length)", + "lineNumber": 109 + }, + { + "text": " {", + "lineNumber": 110 + }, + { + "text": " if (int.TryParse(tokens[i + 1], out var portValue))", + "lineNumber": 111 + }, + { + "text": " {", + "lineNumber": 112 + }, + { + "text": " port = portValue;", + "lineNumber": 113 + }, + { + "text": " i++;", + "lineNumber": 114 + }, + { + "text": " }", + "lineNumber": 115 + }, + { + "text": " continue;", + "lineNumber": 116 + }, + { + "text": " }", + "lineNumber": 117 + }, + { + "lineNumber": 118 + }, + { + "text": " if (token.StartsWith(\"-\"))", + "lineNumber": 119 + }, + { + "text": " {", + "lineNumber": 120 + }, + { + "text": " options.Add(token);", + "lineNumber": 121 + }, + { + "text": " if (optionsWithArgs.Contains(token) && i + 1 < tokens.Length)", + "lineNumber": 122 + }, + { + "text": " {", + "lineNumber": 123 + }, + { + "text": " options.Add(tokens[i + 1]);", + "lineNumber": 124 + }, + { + "text": " i++;", + "lineNumber": 125 + }, + { + "text": " }", + "lineNumber": 126 + }, + { + "text": " continue;", + "lineNumber": 127 + }, + { + "text": " }", + "lineNumber": 128 + }, + { + "lineNumber": 129 + }, + { + "text": " if (targetHost == null)", + "lineNumber": 130 + }, + { + "text": " {", + "lineNumber": 131 + }, + { + "text": " targetHost = token;", + "lineNumber": 132 + }, + { + "text": " continue;", + "lineNumber": 133 + }, + { + "text": " }", + "lineNumber": 134 + }, + { + "lineNumber": 135 + }, + { + "text": " options.Add(token);", + "lineNumber": 136 + }, + { + "text": " }", + "lineNumber": 137 + }, + { + "lineNumber": 138 + }, + { + "text": " parts.Host = targetHost ?? defaultHost;", + "lineNumber": 139 + }, + { + "text": " parts.Options = options;", + "lineNumber": 140 + }, + { + "text": " parts.Port = port;", + "lineNumber": 141 + }, + { + "text": " parts.RequestPty = requestPty;", + "lineNumber": 142 + }, + { + "text": " return parts;", + "lineNumber": 143 + }, + { + "text": " }", + "lineNumber": 144 + }, + { + "lineNumber": 145 + }, + { + "text": " public List<string> BuildSshArgs(SshConnectionParts parts, bool interactive)", + "lineNumber": 146 + }, + { + "text": " {", + "lineNumber": 147 + }, + { + "text": " var args = new List<string>", + "lineNumber": 148 + }, + { + "text": " {", + "lineNumber": 149 + }, + { + "text": " \"-o\", \"ServerAliveInterval=60\",", + "lineNumber": 150 + }, + { + "text": " \"-o\", \"ServerAliveCountMax=30\"", + "lineNumber": 151 + }, + { + "text": " };", + "lineNumber": 152 + }, + { + "lineNumber": 153 + }, + { + "text": " if (interactive && parts.RequestPty)", + "lineNumber": 154 + } + ] + }, + "score": 0.1710781455039978 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\ConfigService.cs", + "range": { + "startPosition": {}, + "endPosition": { + "line": 62 + } + }, + "contents": "using System;\nusing System.IO;\nusing System.Linq;\nusing System.Text.Json;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class ConfigService\n {\n private static readonly JsonSerializerOptions JsonOptions = new()\n {\n WriteIndented = true,\n PropertyNamingPolicy = JsonNamingPolicy.CamelCase\n };\n\n private readonly string _configPath;\n private ConfigRoot? _config;\n\n public ConfigService()\n {\n var appDataPath = GetAppDataPath();\n Directory.CreateDirectory(appDataPath);\n _configPath = Path.Combine(appDataPath, \"workers.json\");\n }\n\n private static string GetAppDataPath()\n {\n var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);\n return Path.Combine(localAppData, \"UnifiedFarmLauncher\");\n }\n\n public ConfigRoot Load()\n {\n if (_config != null)\n return _config;\n\n if (!File.Exists(_configPath))\n {\n _config = new ConfigRoot();\n Save(_config);\n return _config;\n }\n\n try\n {\n var json = File.ReadAllText(_configPath);\n _config = JsonSerializer.Deserialize<ConfigRoot>(json, JsonOptions) ?? new ConfigRoot();\n return _config;\n }\n catch (Exception ex)\n {\n throw new InvalidOperationException($\"Failed to load configuration from {_configPath}: {ex.Message}\", ex);\n }\n }\n\n public void Save(ConfigRoot? config = null)\n {\n config ??= _config ?? new ConfigRoot();\n _config = config;\n\n try", + "signatures": {}, + "detailedLines": [ + { + "text": "using System;", + "lineNumber": 1 + }, + { + "text": "using System.IO;", + "lineNumber": 2 + }, + { + "text": "using System.Linq;", + "lineNumber": 3 + }, + { + "text": "using System.Text.Json;", + "lineNumber": 4 + }, + { + "text": "using UnifiedFarmLauncher.Models;", + "lineNumber": 5 + }, + { + "lineNumber": 6 + }, + { + "text": "namespace UnifiedFarmLauncher.Services", + "lineNumber": 7 + }, + { + "text": "{", + "lineNumber": 8 + }, + { + "text": " public class ConfigService", + "lineNumber": 9 + }, + { + "text": " {", + "lineNumber": 10 + }, + { + "text": " private static readonly JsonSerializerOptions JsonOptions = new()", + "lineNumber": 11 + }, + { + "text": " {", + "lineNumber": 12 + }, + { + "text": " WriteIndented = true,", + "lineNumber": 13 + }, + { + "text": " PropertyNamingPolicy = JsonNamingPolicy.CamelCase", + "lineNumber": 14 + }, + { + "text": " };", + "lineNumber": 15 + }, + { + "lineNumber": 16 + }, + { + "text": " private readonly string _configPath;", + "lineNumber": 17 + }, + { + "text": " private ConfigRoot? _config;", + "lineNumber": 18 + }, + { + "lineNumber": 19 + }, + { + "text": " public ConfigService()", + "lineNumber": 20 + }, + { + "text": " {", + "lineNumber": 21 + }, + { + "text": " var appDataPath = GetAppDataPath();", + "lineNumber": 22 + }, + { + "text": " Directory.CreateDirectory(appDataPath);", + "lineNumber": 23 + }, + { + "text": " _configPath = Path.Combine(appDataPath, \"workers.json\");", + "lineNumber": 24 + }, + { + "text": " }", + "lineNumber": 25 + }, + { + "lineNumber": 26 + }, + { + "text": " private static string GetAppDataPath()", + "lineNumber": 27 + }, + { + "text": " {", + "lineNumber": 28 + }, + { + "text": " var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);", + "lineNumber": 29 + }, + { + "text": " return Path.Combine(localAppData, \"UnifiedFarmLauncher\");", + "lineNumber": 30 + }, + { + "text": " }", + "lineNumber": 31 + }, + { + "lineNumber": 32 + }, + { + "text": " public ConfigRoot Load()", + "lineNumber": 33 + }, + { + "text": " {", + "lineNumber": 34 + }, + { + "text": " if (_config != null)", + "lineNumber": 35 + }, + { + "text": " return _config;", + "lineNumber": 36 + }, + { + "lineNumber": 37 + }, + { + "text": " if (!File.Exists(_configPath))", + "lineNumber": 38 + }, + { + "text": " {", + "lineNumber": 39 + }, + { + "text": " _config = new ConfigRoot();", + "lineNumber": 40 + }, + { + "text": " Save(_config);", + "lineNumber": 41 + }, + { + "text": " return _config;", + "lineNumber": 42 + }, + { + "text": " }", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": " try", + "lineNumber": 45 + }, + { + "text": " {", + "lineNumber": 46 + }, + { + "text": " var json = File.ReadAllText(_configPath);", + "lineNumber": 47 + }, + { + "text": " _config = JsonSerializer.Deserialize<ConfigRoot>(json, JsonOptions) ?? new ConfigRoot();", + "lineNumber": 48 + }, + { + "text": " return _config;", + "lineNumber": 49 + }, + { + "text": " }", + "lineNumber": 50 + }, + { + "text": " catch (Exception ex)", + "lineNumber": 51 + }, + { + "text": " {", + "lineNumber": 52 + }, + { + "text": " throw new InvalidOperationException($\"Failed to load configuration from {_configPath}: {ex.Message}\", ex);", + "lineNumber": 53 + }, + { + "text": " }", + "lineNumber": 54 + }, + { + "text": " }", + "lineNumber": 55 + }, + { + "lineNumber": 56 + }, + { + "text": " public void Save(ConfigRoot? config = null)", + "lineNumber": 57 + }, + { + "text": " {", + "lineNumber": 58 + }, + { + "text": " config ??= _config ?? new ConfigRoot();", + "lineNumber": 59 + }, + { + "text": " _config = config;", + "lineNumber": 60 + }, + { + "lineNumber": 61 + }, + { + "text": " try", + "lineNumber": 62 + } + ] + }, + "score": 0.15005089342594147 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\ConfigService.cs", + "range": { + "startPosition": { + "line": 59 + }, + "endPosition": { + "line": 122, + "column": 12 + } + }, + "contents": " _config = config;\n\n try\n {\n var json = JsonSerializer.Serialize(config, JsonOptions);\n File.WriteAllText(_configPath, json);\n }\n catch (Exception ex)\n {\n throw new InvalidOperationException($\"Failed to save configuration to {_configPath}: {ex.Message}\", ex);\n }\n }\n\n public void Reload()\n {\n _config = null;\n Load();\n }\n\n public WorkerConfig? GetWorker(int id)\n {\n return Load().Workers.FirstOrDefault(w => w.Id == id);\n }\n\n public WorkerConfig? GetWorkerByName(string name)\n {\n return Load().Workers.FirstOrDefault(w => w.Name == name);\n }\n\n public void AddWorker(WorkerConfig worker)\n {\n var config = Load();\n if (config.Workers.Any(w => w.Id == worker.Id || w.Name == worker.Name))\n throw new InvalidOperationException($\"Worker with ID {worker.Id} or name '{worker.Name}' already exists\");\n\n config.Workers.Add(worker);\n Save(config);\n }\n\n public void UpdateWorker(WorkerConfig worker)\n {\n var config = Load();\n var index = config.Workers.FindIndex(w => w.Id == worker.Id);\n if (index < 0)\n throw new InvalidOperationException($\"Worker with ID {worker.Id} not found\");\n\n config.Workers[index] = worker;\n Save(config);\n }\n\n public void DeleteWorker(int id)\n {\n var config = Load();\n var worker = config.Workers.FirstOrDefault(w => w.Id == id);\n if (worker == null)\n throw new InvalidOperationException($\"Worker with ID {id} not found\");\n\n config.Workers.Remove(worker);\n Save(config);\n }\n\n public int GetNextWorkerId()\n {\n ", + "signatures": {}, + "detailedLines": [ + { + "text": " _config = config;", + "lineNumber": 60 + }, + { + "lineNumber": 61 + }, + { + "text": " try", + "lineNumber": 62 + }, + { + "text": " {", + "lineNumber": 63 + }, + { + "text": " var json = JsonSerializer.Serialize(config, JsonOptions);", + "lineNumber": 64 + }, + { + "text": " File.WriteAllText(_configPath, json);", + "lineNumber": 65 + }, + { + "text": " }", + "lineNumber": 66 + }, + { + "text": " catch (Exception ex)", + "lineNumber": 67 + }, + { + "text": " {", + "lineNumber": 68 + }, + { + "text": " throw new InvalidOperationException($\"Failed to save configuration to {_configPath}: {ex.Message}\", ex);", + "lineNumber": 69 + }, + { + "text": " }", + "lineNumber": 70 + }, + { + "text": " }", + "lineNumber": 71 + }, + { + "lineNumber": 72 + }, + { + "text": " public void Reload()", + "lineNumber": 73 + }, + { + "text": " {", + "lineNumber": 74 + }, + { + "text": " _config = null;", + "lineNumber": 75 + }, + { + "text": " Load();", + "lineNumber": 76 + }, + { + "text": " }", + "lineNumber": 77 + }, + { + "lineNumber": 78 + }, + { + "text": " public WorkerConfig? GetWorker(int id)", + "lineNumber": 79 + }, + { + "text": " {", + "lineNumber": 80 + }, + { + "text": " return Load().Workers.FirstOrDefault(w => w.Id == id);", + "lineNumber": 81 + }, + { + "text": " }", + "lineNumber": 82 + }, + { + "lineNumber": 83 + }, + { + "text": " public WorkerConfig? GetWorkerByName(string name)", + "lineNumber": 84 + }, + { + "text": " {", + "lineNumber": 85 + }, + { + "text": " return Load().Workers.FirstOrDefault(w => w.Name == name);", + "lineNumber": 86 + }, + { + "text": " }", + "lineNumber": 87 + }, + { + "lineNumber": 88 + }, + { + "text": " public void AddWorker(WorkerConfig worker)", + "lineNumber": 89 + }, + { + "text": " {", + "lineNumber": 90 + }, + { + "text": " var config = Load();", + "lineNumber": 91 + }, + { + "text": " if (config.Workers.Any(w => w.Id == worker.Id || w.Name == worker.Name))", + "lineNumber": 92 + }, + { + "text": " throw new InvalidOperationException($\"Worker with ID {worker.Id} or name '{worker.Name}' already exists\");", + "lineNumber": 93 + }, + { + "lineNumber": 94 + }, + { + "text": " config.Workers.Add(worker);", + "lineNumber": 95 + }, + { + "text": " Save(config);", + "lineNumber": 96 + }, + { + "text": " }", + "lineNumber": 97 + }, + { + "lineNumber": 98 + }, + { + "text": " public void UpdateWorker(WorkerConfig worker)", + "lineNumber": 99 + }, + { + "text": " {", + "lineNumber": 100 + }, + { + "text": " var config = Load();", + "lineNumber": 101 + }, + { + "text": " var index = config.Workers.FindIndex(w => w.Id == worker.Id);", + "lineNumber": 102 + }, + { + "text": " if (index < 0)", + "lineNumber": 103 + }, + { + "text": " throw new InvalidOperationException($\"Worker with ID {worker.Id} not found\");", + "lineNumber": 104 + }, + { + "lineNumber": 105 + }, + { + "text": " config.Workers[index] = worker;", + "lineNumber": 106 + }, + { + "text": " Save(config);", + "lineNumber": 107 + }, + { + "text": " }", + "lineNumber": 108 + }, + { + "lineNumber": 109 + }, + { + "text": " public void DeleteWorker(int id)", + "lineNumber": 110 + }, + { + "text": " {", + "lineNumber": 111 + }, + { + "text": " var config = Load();", + "lineNumber": 112 + }, + { + "text": " var worker = config.Workers.FirstOrDefault(w => w.Id == id);", + "lineNumber": 113 + }, + { + "text": " if (worker == null)", + "lineNumber": 114 + }, + { + "text": " throw new InvalidOperationException($\"Worker with ID {id} not found\");", + "lineNumber": 115 + }, + { + "lineNumber": 116 + }, + { + "text": " config.Workers.Remove(worker);", + "lineNumber": 117 + }, + { + "text": " Save(config);", + "lineNumber": 118 + }, + { + "text": " }", + "lineNumber": 119 + }, + { + "lineNumber": 120 + }, + { + "text": " public int GetNextWorkerId()", + "lineNumber": 121 + }, + { + "text": " {", + "lineNumber": 122 + }, + { + "text": " ", + "lineNumber": 123 + } + ] + }, + "score": 0.0880109965801239 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\ConfigService.cs", + "range": { + "startPosition": { + "line": 119 + }, + "endPosition": { + "line": 128 + } + }, + "contents": " public int GetNextWorkerId()\n {\n var config = Load();\n return config.Workers.Count > 0 ? config.Workers.Max(w => w.Id) + 1 : 1;\n }\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": " public int GetNextWorkerId()", + "lineNumber": 121 + }, + { + "text": " {", + "lineNumber": 122 + }, + { + "text": " var config = Load();", + "lineNumber": 123 + }, + { + "text": " return config.Workers.Count > 0 ? config.Workers.Max(w => w.Id) + 1 : 1;", + "lineNumber": 124 + }, + { + "text": " }", + "lineNumber": 125 + }, + { + "text": " }", + "lineNumber": 126 + }, + { + "text": "}", + "lineNumber": 127 + } + ] + }, + "score": 0.04890263080596924 + } + ] +} +``` +Status: **completed** + + +
+
+ +--- + + +The `Start-Process` call doesn't capture the process or verify it started. Updating the script to capture the controller process PID and verify startup: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "4ff50c9e-c3fc-4dd8-b866-a05e1e8ed7de" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "d9ffc03a-4f56-4f27-9e97-6cfa39870c61" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:00.88 + +``` +
+
+ +--- + + +Made these changes: + +1. **Capture controller process PID**: Using `-PassThru` to get the process object and immediately update the metadata with the controller PID +2. **Error handling**: Wrapped `Start-Process` in try-catch to catch startup failures and update metadata with error details +3. **Enhanced diagnostics**: Improved controller process status reporting + +When you start the worker again, you should see: +- The controller PID in the metadata immediately after startup +- Clearer error messages if the controller fails to start +- More detailed diagnostics about the controller process status + +This should help identify whether: +- The controller process starts but crashes immediately +- The controller process fails to start due to permissions or path issues +- There's a different problem preventing the worker from launching + +Try starting the worker again and share what the diagnostics show. + +--- + +_**User**_ + + + +--- + +_**Agent (model default, mode Agent)**_ + + +Diagnostics show the controller PID isn't being set, suggesting `Start-Process` may be failing silently. Adding error capture and verifying the controller script execution: + + +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Services/SshService.cs + +
+
+ +--- + + +Improving error capture: including stderr in diagnostics and adding more verbose error output to identify why the controller isn't starting: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "46e9ad92-865e-47c0-b55a-b2aa29934d11" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "dc218287-a84e-4d87-87a0-327a5b57daac" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "bc6536a1-1985-48b2-9ae4-dc4016e530e4" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:00.83 + +``` +
+
+ +--- + + +Made several improvements to capture more diagnostic information: + +1. **Verbose logging before Start-Process**: Logs the PowerShell executable path, controller script path, and arguments +2. **Process verification**: Checks if the controller process is still running 500ms after start +3. **Enhanced error capture**: Includes full exception details in error messages and writes to stderr +4. **Better diagnostics**: Shows controller script size, first line, and raw metadata content if JSON parsing fails + +When you try starting the worker again, the diagnostics should show: +- What command is being executed to start the controller +- Whether the controller process starts but immediately exits +- The exact error message if Start-Process fails +- More details about the controller script and metadata file + +This should help identify why the controller PID isn't being set. Try starting the worker again and share what the diagnostics show. + +--- + +_**User**_ + +do we need to make it so it can scroll to see the log lines? + +--- + +_**Agent (model default, mode Agent)**_ + + +Checking how the error dialog is displayed and making it scrollable to show all log entries: + + +--- + + +
+Tool use: **semantic_search_full** + + +Parameters: + +```json +{ + "query": "Where is the error message displayed when worker fails to start with InvalidOperationException?", + "topK": 10, + "explanation": "" +} +``` +Result: + +```json +{ + "codeResults": [ + { + "codeBlock": { + "relativeWorkspacePath": ".\\Views\\MainWindow.axaml.cs", + "range": { + "startPosition": { + "line": 101 + }, + "endPosition": { + "line": 139 + } + }, + "contents": " string? workerType = vm.OperationMode;\n\n // Verify the worker supports the selected operation mode\n if (workerType == \"sheepit\" && worker.WorkerTypes.SheepIt == null)\n {\n var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", \n $\"Worker '{worker.Name}' does not have SheepIt configured.\", \n ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);\n await box.ShowAsync();\n return;\n }\n if (workerType == \"flamenco\" && worker.WorkerTypes.Flamenco == null)\n {\n var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", \n $\"Worker '{worker.Name}' does not have Flamenco configured.\", \n ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);\n await box.ShowAsync();\n return;\n }\n\n await _controllerService.StartWorkerAsync(worker, workerType);\n var successBox = MessageBoxManager.GetMessageBoxStandard(\"Start Worker\", \n $\"Worker '{worker.Name}' ({vm.OperationModeDisplayName}) started successfully.\", \n ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Success);\n await successBox.ShowAsync();\n vm.RefreshWorkers();\n }\n catch (System.Exception ex)\n {\n var errorBox = MessageBoxManager.GetMessageBoxStandard(\"Error\", \n $\"Failed to start worker: {ex.Message}\", \n ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);\n await errorBox.ShowAsync();\n }\n }\n }\n\n private async void StopWorkerButton_Click(object? sender, RoutedEventArgs e)", + "signatures": {}, + "detailedLines": [ + { + "text": " string? workerType = vm.OperationMode;", + "lineNumber": 102 + }, + { + "lineNumber": 103 + }, + { + "text": " // Verify the worker supports the selected operation mode", + "lineNumber": 104 + }, + { + "text": " if (workerType == \"sheepit\" && worker.WorkerTypes.SheepIt == null)", + "lineNumber": 105 + }, + { + "text": " {", + "lineNumber": 106 + }, + { + "text": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", ", + "lineNumber": 107 + }, + { + "text": " $\"Worker '{worker.Name}' does not have SheepIt configured.\", ", + "lineNumber": 108 + }, + { + "text": " ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);", + "lineNumber": 109 + }, + { + "text": " await box.ShowAsync();", + "lineNumber": 110 + }, + { + "text": " return;", + "lineNumber": 111 + }, + { + "text": " }", + "lineNumber": 112 + }, + { + "text": " if (workerType == \"flamenco\" && worker.WorkerTypes.Flamenco == null)", + "lineNumber": 113 + }, + { + "text": " {", + "lineNumber": 114 + }, + { + "text": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", ", + "lineNumber": 115 + }, + { + "text": " $\"Worker '{worker.Name}' does not have Flamenco configured.\", ", + "lineNumber": 116 + }, + { + "text": " ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);", + "lineNumber": 117 + }, + { + "text": " await box.ShowAsync();", + "lineNumber": 118 + }, + { + "text": " return;", + "lineNumber": 119 + }, + { + "text": " }", + "lineNumber": 120 + }, + { + "lineNumber": 121 + }, + { + "text": " await _controllerService.StartWorkerAsync(worker, workerType);", + "lineNumber": 122 + }, + { + "text": " var successBox = MessageBoxManager.GetMessageBoxStandard(\"Start Worker\", ", + "lineNumber": 123 + }, + { + "text": " $\"Worker '{worker.Name}' ({vm.OperationModeDisplayName}) started successfully.\", ", + "lineNumber": 124 + }, + { + "text": " ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Success);", + "lineNumber": 125 + }, + { + "text": " await successBox.ShowAsync();", + "lineNumber": 126 + }, + { + "text": " vm.RefreshWorkers();", + "lineNumber": 127 + }, + { + "text": " }", + "lineNumber": 128 + }, + { + "text": " catch (System.Exception ex)", + "lineNumber": 129 + }, + { + "text": " {", + "lineNumber": 130 + }, + { + "text": " var errorBox = MessageBoxManager.GetMessageBoxStandard(\"Error\", ", + "lineNumber": 131 + }, + { + "text": " $\"Failed to start worker: {ex.Message}\", ", + "lineNumber": 132 + }, + { + "text": " ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);", + "lineNumber": 133 + }, + { + "text": " await errorBox.ShowAsync();", + "lineNumber": 134 + }, + { + "text": " }", + "lineNumber": 135 + }, + { + "text": " }", + "lineNumber": 136 + }, + { + "text": " }", + "lineNumber": 137 + }, + { + "lineNumber": 138 + }, + { + "text": " private async void StopWorkerButton_Click(object? sender, RoutedEventArgs e)", + "lineNumber": 139 + } + ] + }, + "score": 0.4698915481567383 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 355 + }, + "endPosition": { + "line": 399 + } + }, + "contents": " WorkerPid = $null\n Restarts = 0\n LastExitCode = $null\n LogPath = $logPath\n CommandPath = $commandPath\n PayloadPath = $payloadPath\n UpdatedAtUtc = (Get-Date).ToUniversalTime()\n }} | ConvertTo-Json -Depth 5\n $updatedMeta | Set-Content -Path $metaPath -Encoding UTF8\n \n Write-Host \"\"Worker $workerName started under controller (PID: $controllerPid).\"\" -ForegroundColor Green\n }} else {{\n throw \"\"Start-Process returned null - controller failed to start\"\"\n }}\n }} catch {{\n $errorMsg = $_.Exception.Message\n $errorDetails = $_.Exception.ToString()\n Write-Host \"\"Failed to start controller: $errorMsg\"\" -ForegroundColor Red\n Write-Host \"\"Error details: $errorDetails\"\" -ForegroundColor Red\n [Console]::Error.WriteLine(\"\"Controller startup error: $errorMsg\"\")\n [Console]::Error.WriteLine(\"\"Full error: $errorDetails\"\")\n \n # Update metadata with error\n $errorMeta = [pscustomobject]@{{\n WorkerName = $workerName\n WorkerType = $workerType\n Status = 'error'\n ControllerPid = $null\n WorkerPid = $null\n Restarts = 0\n LastExitCode = 1\n LogPath = $logPath\n CommandPath = $commandPath\n PayloadPath = $payloadPath\n UpdatedAtUtc = (Get-Date).ToUniversalTime()\n ErrorMessage = \"\"Controller startup failed: $errorMsg\"\"\n }} | ConvertTo-Json -Depth 5\n $errorMeta | Set-Content -Path $metaPath -Encoding UTF8\n throw\n }}\n}}\n\";\n\n // Pipe script through stdin to avoid command line length limits", + "signatures": {}, + "detailedLines": [ + { + "text": " WorkerPid = $null", + "lineNumber": 356 + }, + { + "text": " Restarts = 0", + "lineNumber": 357 + }, + { + "text": " LastExitCode = $null", + "lineNumber": 358 + }, + { + "text": " LogPath = $logPath", + "lineNumber": 359 + }, + { + "text": " CommandPath = $commandPath", + "lineNumber": 360 + }, + { + "text": " PayloadPath = $payloadPath", + "lineNumber": 361 + }, + { + "text": " UpdatedAtUtc = (Get-Date).ToUniversalTime()", + "lineNumber": 362 + }, + { + "text": " }} | ConvertTo-Json -Depth 5", + "lineNumber": 363 + }, + { + "text": " $updatedMeta | Set-Content -Path $metaPath -Encoding UTF8", + "lineNumber": 364 + }, + { + "text": " ", + "lineNumber": 365 + }, + { + "text": " Write-Host \"\"Worker $workerName started under controller (PID: $controllerPid).\"\" -ForegroundColor Green", + "lineNumber": 366 + }, + { + "text": " }} else {{", + "lineNumber": 367 + }, + { + "text": " throw \"\"Start-Process returned null - controller failed to start\"\"", + "lineNumber": 368 + }, + { + "text": " }}", + "lineNumber": 369 + }, + { + "text": " }} catch {{", + "lineNumber": 370 + }, + { + "text": " $errorMsg = $_.Exception.Message", + "lineNumber": 371 + }, + { + "text": " $errorDetails = $_.Exception.ToString()", + "lineNumber": 372 + }, + { + "text": " Write-Host \"\"Failed to start controller: $errorMsg\"\" -ForegroundColor Red", + "lineNumber": 373 + }, + { + "text": " Write-Host \"\"Error details: $errorDetails\"\" -ForegroundColor Red", + "lineNumber": 374 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Controller startup error: $errorMsg\"\")", + "lineNumber": 375 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Full error: $errorDetails\"\")", + "lineNumber": 376 + }, + { + "text": " ", + "lineNumber": 377 + }, + { + "text": " # Update metadata with error", + "lineNumber": 378 + }, + { + "text": " $errorMeta = [pscustomobject]@{{", + "lineNumber": 379 + }, + { + "text": " WorkerName = $workerName", + "lineNumber": 380 + }, + { + "text": " WorkerType = $workerType", + "lineNumber": 381 + }, + { + "text": " Status = 'error'", + "lineNumber": 382 + }, + { + "text": " ControllerPid = $null", + "lineNumber": 383 + }, + { + "text": " WorkerPid = $null", + "lineNumber": 384 + }, + { + "text": " Restarts = 0", + "lineNumber": 385 + }, + { + "text": " LastExitCode = 1", + "lineNumber": 386 + }, + { + "text": " LogPath = $logPath", + "lineNumber": 387 + }, + { + "text": " CommandPath = $commandPath", + "lineNumber": 388 + }, + { + "text": " PayloadPath = $payloadPath", + "lineNumber": 389 + }, + { + "text": " UpdatedAtUtc = (Get-Date).ToUniversalTime()", + "lineNumber": 390 + }, + { + "text": " ErrorMessage = \"\"Controller startup failed: $errorMsg\"\"", + "lineNumber": 391 + }, + { + "text": " }} | ConvertTo-Json -Depth 5", + "lineNumber": 392 + }, + { + "text": " $errorMeta | Set-Content -Path $metaPath -Encoding UTF8", + "lineNumber": 393 + }, + { + "text": " throw", + "lineNumber": 394 + }, + { + "text": " }}", + "lineNumber": 395 + }, + { + "text": "}}", + "lineNumber": 396 + }, + { + "text": "\";", + "lineNumber": 397 + }, + { + "lineNumber": 398 + }, + { + "text": " // Pipe script through stdin to avoid command line length limits", + "lineNumber": 399 + } + ] + }, + "score": 0.4442903399467468 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 141 + }, + "endPosition": { + "line": 190 + } + }, + "contents": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')\n Set-Location $sheepDir\n \n $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, \n '-ui', 'text', '--log-stdout', '--verbose', \n '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')\n \n try {{\n & java @javaArgs\n }}\n catch {{\n Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red\n Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow\n [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")\n throw\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public string GenerateFlamencoPayload(WorkerConfig worker)\n {\n if (worker.WorkerTypes.Flamenco == null)\n throw new InvalidOperationException(\"Worker does not have Flamenco configuration\");\n\n var config = _configService.Load();\n var flamenco = worker.WorkerTypes.Flamenco;\n var globalSettings = config.GlobalSettings;\n var workerPath = globalSettings.FlamencoWorkerPath.Replace(\"'\", \"''\");\n var drives = string.Join(\", \", Array.ConvertAll(flamenco.NetworkDrives.ToArray(), d => $\"'{d}'\"));\n var paths = string.Join(\", \", Array.ConvertAll(flamenco.NetworkPaths.ToArray(), p => $\"'{p.Replace(\"\\\\\", \"\\\\\\\\\")}'\"));\n\n return $@\"\n$ProgressPreference = 'SilentlyContinue'\n$ErrorActionPreference = 'Stop'\n\n# Write startup message to stderr so controller can capture it\n[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "signatures": {}, + "detailedLines": [ + { + "text": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')", + "lineNumber": 143 + }, + { + "text": " Set-Location $sheepDir", + "lineNumber": 144 + }, + { + "text": " ", + "lineNumber": 145 + }, + { + "text": " $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, ", + "lineNumber": 146 + }, + { + "text": " '-ui', 'text', '--log-stdout', '--verbose', ", + "lineNumber": 147 + }, + { + "text": " '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')", + "lineNumber": 148 + }, + { + "text": " ", + "lineNumber": 149 + }, + { + "text": " try {{", + "lineNumber": 150 + }, + { + "text": " & java @javaArgs", + "lineNumber": 151 + }, + { + "text": " }}", + "lineNumber": 152 + }, + { + "text": " catch {{", + "lineNumber": 153 + }, + { + "text": " Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red", + "lineNumber": 154 + }, + { + "text": " Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow", + "lineNumber": 155 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")", + "lineNumber": 156 + }, + { + "text": " throw", + "lineNumber": 157 + }, + { + "text": " }}", + "lineNumber": 158 + }, + { + "text": "}}", + "lineNumber": 159 + }, + { + "text": "catch {{", + "lineNumber": 160 + }, + { + "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)", + "lineNumber": 161 + }, + { + "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)", + "lineNumber": 162 + }, + { + "text": " Write-Host $errorMsg -ForegroundColor Red", + "lineNumber": 163 + }, + { + "text": " Write-Host $stackMsg -ForegroundColor DarkRed", + "lineNumber": 164 + }, + { + "text": " [Console]::Error.WriteLine($errorMsg)", + "lineNumber": 165 + }, + { + "text": " [Console]::Error.WriteLine($stackMsg)", + "lineNumber": 166 + }, + { + "text": " exit 1", + "lineNumber": 167 + }, + { + "text": "}}", + "lineNumber": 168 + }, + { + "text": "\";", + "lineNumber": 169 + }, + { + "text": " }", + "lineNumber": 170 + }, + { + "lineNumber": 171 + }, + { + "text": " public string GenerateFlamencoPayload(WorkerConfig worker)", + "lineNumber": 172 + }, + { + "text": " {", + "lineNumber": 173 + }, + { + "text": " if (worker.WorkerTypes.Flamenco == null)", + "lineNumber": 174 + }, + { + "text": " throw new InvalidOperationException(\"Worker does not have Flamenco configuration\");", + "lineNumber": 175 + }, + { + "lineNumber": 176 + }, + { + "text": " var config = _configService.Load();", + "lineNumber": 177 + }, + { + "text": " var flamenco = worker.WorkerTypes.Flamenco;", + "lineNumber": 178 + }, + { + "text": " var globalSettings = config.GlobalSettings;", + "lineNumber": 179 + }, + { + "text": " var workerPath = globalSettings.FlamencoWorkerPath.Replace(\"'\", \"''\");", + "lineNumber": 180 + }, + { + "text": " var drives = string.Join(\", \", Array.ConvertAll(flamenco.NetworkDrives.ToArray(), d => $\"'{d}'\"));", + "lineNumber": 181 + }, + { + "text": " var paths = string.Join(\", \", Array.ConvertAll(flamenco.NetworkPaths.ToArray(), p => $\"'{p.Replace(\"\\\\\", \"\\\\\\\\\")}'\"));", + "lineNumber": 182 + }, + { + "lineNumber": 183 + }, + { + "text": " return $@\"", + "lineNumber": 184 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 185 + }, + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 186 + }, + { + "lineNumber": 187 + }, + { + "text": "# Write startup message to stderr so controller can capture it", + "lineNumber": 188 + }, + { + "text": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "lineNumber": 189 + } + ] + }, + "score": 0.38318970799446106 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Views\\MainWindow.axaml.cs", + "range": { + "startPosition": { + "line": 169 + }, + "endPosition": { + "line": 211 + } + }, + "contents": " await successBox.ShowAsync();\n }\n catch (System.Exception ex)\n {\n var errorBox = MessageBoxManager.GetMessageBoxStandard(\"Error\", \n $\"Failed to stop worker: {ex.Message}\", \n ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);\n await errorBox.ShowAsync();\n }\n }\n }\n\n private async void AttachWorkerButton_Click(object? sender, RoutedEventArgs e)\n {\n if (this.FindControl<DataGrid>(\"WorkersGrid\")?.SelectedItem is WorkerConfig worker)\n {\n try\n {\n var vm = (MainWindowViewModel)DataContext!;\n string workerType = vm.OperationMode;\n\n // Verify the worker supports the selected operation mode\n if (workerType == \"sheepit\" && worker.WorkerTypes.SheepIt == null)\n {\n var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", \n $\"Worker '{worker.Name}' does not have SheepIt configured.\", \n ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);\n await box.ShowAsync();\n return;\n }\n if (workerType == \"flamenco\" && worker.WorkerTypes.Flamenco == null)\n {\n var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", \n $\"Worker '{worker.Name}' does not have Flamenco configured.\", \n ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);\n await box.ShowAsync();\n return;\n }\n\n await _attachService.AttachToWorkerAsync(worker, workerType);\n }\n catch (System.Exception ex)", + "signatures": {}, + "detailedLines": [ + { + "text": " await successBox.ShowAsync();", + "lineNumber": 170 + }, + { + "text": " }", + "lineNumber": 171 + }, + { + "text": " catch (System.Exception ex)", + "lineNumber": 172 + }, + { + "text": " {", + "lineNumber": 173 + }, + { + "text": " var errorBox = MessageBoxManager.GetMessageBoxStandard(\"Error\", ", + "lineNumber": 174 + }, + { + "text": " $\"Failed to stop worker: {ex.Message}\", ", + "lineNumber": 175 + }, + { + "text": " ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);", + "lineNumber": 176 + }, + { + "text": " await errorBox.ShowAsync();", + "lineNumber": 177 + }, + { + "text": " }", + "lineNumber": 178 + }, + { + "text": " }", + "lineNumber": 179 + }, + { + "text": " }", + "lineNumber": 180 + }, + { + "lineNumber": 181 + }, + { + "text": " private async void AttachWorkerButton_Click(object? sender, RoutedEventArgs e)", + "lineNumber": 182 + }, + { + "text": " {", + "lineNumber": 183 + }, + { + "text": " if (this.FindControl<DataGrid>(\"WorkersGrid\")?.SelectedItem is WorkerConfig worker)", + "lineNumber": 184 + }, + { + "text": " {", + "lineNumber": 185 + }, + { + "text": " try", + "lineNumber": 186 + }, + { + "text": " {", + "lineNumber": 187 + }, + { + "text": " var vm = (MainWindowViewModel)DataContext!;", + "lineNumber": 188 + }, + { + "text": " string workerType = vm.OperationMode;", + "lineNumber": 189 + }, + { + "lineNumber": 190 + }, + { + "text": " // Verify the worker supports the selected operation mode", + "lineNumber": 191 + }, + { + "text": " if (workerType == \"sheepit\" && worker.WorkerTypes.SheepIt == null)", + "lineNumber": 192 + }, + { + "text": " {", + "lineNumber": 193 + }, + { + "text": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", ", + "lineNumber": 194 + }, + { + "text": " $\"Worker '{worker.Name}' does not have SheepIt configured.\", ", + "lineNumber": 195 + }, + { + "text": " ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);", + "lineNumber": 196 + }, + { + "text": " await box.ShowAsync();", + "lineNumber": 197 + }, + { + "text": " return;", + "lineNumber": 198 + }, + { + "text": " }", + "lineNumber": 199 + }, + { + "text": " if (workerType == \"flamenco\" && worker.WorkerTypes.Flamenco == null)", + "lineNumber": 200 + }, + { + "text": " {", + "lineNumber": 201 + }, + { + "text": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\", ", + "lineNumber": 202 + }, + { + "text": " $\"Worker '{worker.Name}' does not have Flamenco configured.\", ", + "lineNumber": 203 + }, + { + "text": " ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error);", + "lineNumber": 204 + }, + { + "text": " await box.ShowAsync();", + "lineNumber": 205 + }, + { + "text": " return;", + "lineNumber": 206 + }, + { + "text": " }", + "lineNumber": 207 + }, + { + "lineNumber": 208 + }, + { + "text": " await _attachService.AttachToWorkerAsync(worker, workerType);", + "lineNumber": 209 + }, + { + "text": " }", + "lineNumber": 210 + }, + { + "text": " catch (System.Exception ex)", + "lineNumber": 211 + } + ] + }, + "score": 0.37145599722862244 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Scripts\\remote_worker_controller.ps1", + "range": { + "startPosition": { + "line": 53 + }, + "endPosition": { + "line": 86 + } + }, + "contents": " $logWriter.AutoFlush = $true\n}\ncatch {\n # If we can't open the log file, write error to metadata and exit\n $errorMeta = [pscustomobject]@{\n WorkerName = $WorkerName\n WorkerType = $WorkerType\n Status = 'error'\n ControllerPid = $PID\n WorkerPid = $null\n Restarts = 0\n LastExitCode = 1\n LogPath = $logPath\n CommandPath = $commandPath\n PayloadPath = $payloadPath\n UpdatedAtUtc = (Get-Date).ToUniversalTime()\n ErrorMessage = \"Failed to open log file: $($_.Exception.Message)\"\n }\n $errorMeta | ConvertTo-Json -Depth 5 | Set-Content -Path $metaPath -Encoding UTF8 -ErrorAction SilentlyContinue\n Write-Error \"Controller failed to initialize: $($_.Exception.Message)\"\n exit 1\n}\n\n# Create C# event handler class that doesn't require PowerShell runspace\nif (-not (\"UnifiedWorkers.ProcessLogHandler\" -as [type])) {\n $csharpCode = @'\nusing System;\nusing System.Diagnostics;\nusing System.IO;\n\nnamespace UnifiedWorkers\n{\n public sealed class ProcessLogHandler", + "signatures": {}, + "detailedLines": [ + { + "text": " $logWriter.AutoFlush = $true", + "lineNumber": 54 + }, + { + "text": "}", + "lineNumber": 55 + }, + { + "text": "catch {", + "lineNumber": 56 + }, + { + "text": " # If we can't open the log file, write error to metadata and exit", + "lineNumber": 57 + }, + { + "text": " $errorMeta = [pscustomobject]@{", + "lineNumber": 58 + }, + { + "text": " WorkerName = $WorkerName", + "lineNumber": 59 + }, + { + "text": " WorkerType = $WorkerType", + "lineNumber": 60 + }, + { + "text": " Status = 'error'", + "lineNumber": 61 + }, + { + "text": " ControllerPid = $PID", + "lineNumber": 62 + }, + { + "text": " WorkerPid = $null", + "lineNumber": 63 + }, + { + "text": " Restarts = 0", + "lineNumber": 64 + }, + { + "text": " LastExitCode = 1", + "lineNumber": 65 + }, + { + "text": " LogPath = $logPath", + "lineNumber": 66 + }, + { + "text": " CommandPath = $commandPath", + "lineNumber": 67 + }, + { + "text": " PayloadPath = $payloadPath", + "lineNumber": 68 + }, + { + "text": " UpdatedAtUtc = (Get-Date).ToUniversalTime()", + "lineNumber": 69 + }, + { + "text": " ErrorMessage = \"Failed to open log file: $($_.Exception.Message)\"", + "lineNumber": 70 + }, + { + "text": " }", + "lineNumber": 71 + }, + { + "text": " $errorMeta | ConvertTo-Json -Depth 5 | Set-Content -Path $metaPath -Encoding UTF8 -ErrorAction SilentlyContinue", + "lineNumber": 72 + }, + { + "text": " Write-Error \"Controller failed to initialize: $($_.Exception.Message)\"", + "lineNumber": 73 + }, + { + "text": " exit 1", + "lineNumber": 74 + }, + { + "text": "}", + "lineNumber": 75 + }, + { + "lineNumber": 76 + }, + { + "text": "# Create C# event handler class that doesn't require PowerShell runspace", + "lineNumber": 77 + }, + { + "text": "if (-not (\"UnifiedWorkers.ProcessLogHandler\" -as [type])) {", + "lineNumber": 78 + }, + { + "text": " $csharpCode = @'", + "lineNumber": 79 + }, + { + "text": "using System;", + "lineNumber": 80 + }, + { + "text": "using System.Diagnostics;", + "lineNumber": 81 + }, + { + "text": "using System.IO;", + "lineNumber": 82 + }, + { + "lineNumber": 83 + }, + { + "text": "namespace UnifiedWorkers", + "lineNumber": 84 + }, + { + "text": "{", + "lineNumber": 85 + }, + { + "text": " public sealed class ProcessLogHandler", + "lineNumber": 86 + } + ] + }, + "score": 0.3635040521621704 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Scripts\\remote_worker_controller.ps1", + "range": { + "startPosition": { + "line": 254 + }, + "endPosition": { + "line": 300 + } + }, + "contents": " while ($restartCount -le $MaxRestarts) {\n try {\n # Initialize worker process\n $psi = [System.Diagnostics.ProcessStartInfo]::new()\n $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue\n if ($pwsh) {\n $psi.FileName = $pwsh.Source\n }\n else {\n $psi.FileName = (Get-Command powershell -ErrorAction Stop).Source\n }\n\n $psi.Arguments = \"-NoLogo -NoProfile -ExecutionPolicy Bypass -File `\"$payloadPath`\"\"\n $psi.UseShellExecute = $false\n $psi.RedirectStandardInput = $true\n $psi.RedirectStandardOutput = $true\n $psi.RedirectStandardError = $true\n $psi.CreateNoWindow = $true\n\n $workerProcess = New-Object System.Diagnostics.Process\n $workerProcess.StartInfo = $psi\n\n if (-not $workerProcess.Start()) {\n throw \"Failed to start worker process.\"\n }\n\n Write-ControllerLog \"Worker process started with PID $($workerProcess.Id)\"\n Write-Metadata -Status 'running' -WorkerPid $workerProcess.Id -ControllerPid $controllerPid -Restarts $restartCount\n\n # Check if process exited immediately\n if ($workerProcess.HasExited) {\n $exitCode = -1\n try {\n $exitCode = $workerProcess.ExitCode\n }\n catch {\n Write-ControllerLog \"Unable to read immediate exit code: $($_.Exception.Message)\"\n }\n Write-ControllerLog \"Worker process exited immediately after start with code $exitCode\"\n Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode\n if ($exitCode -eq 0) { break }\n # Continue to restart logic below\n }\n else {\n $stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')", + "signatures": {}, + "detailedLines": [ + { + "text": " while ($restartCount -le $MaxRestarts) {", + "lineNumber": 256 + }, + { + "text": " try {", + "lineNumber": 257 + }, + { + "text": " # Initialize worker process", + "lineNumber": 258 + }, + { + "text": " $psi = [System.Diagnostics.ProcessStartInfo]::new()", + "lineNumber": 259 + }, + { + "text": " $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue", + "lineNumber": 260 + }, + { + "text": " if ($pwsh) {", + "lineNumber": 261 + }, + { + "text": " $psi.FileName = $pwsh.Source", + "lineNumber": 262 + }, + { + "text": " }", + "lineNumber": 263 + }, + { + "text": " else {", + "lineNumber": 264 + }, + { + "text": " $psi.FileName = (Get-Command powershell -ErrorAction Stop).Source", + "lineNumber": 265 + }, + { + "text": " }", + "lineNumber": 266 + }, + { + "lineNumber": 267 + }, + { + "text": " $psi.Arguments = \"-NoLogo -NoProfile -ExecutionPolicy Bypass -File `\"$payloadPath`\"\"", + "lineNumber": 268 + }, + { + "text": " $psi.UseShellExecute = $false", + "lineNumber": 269 + }, + { + "text": " $psi.RedirectStandardInput = $true", + "lineNumber": 270 + }, + { + "text": " $psi.RedirectStandardOutput = $true", + "lineNumber": 271 + }, + { + "text": " $psi.RedirectStandardError = $true", + "lineNumber": 272 + }, + { + "text": " $psi.CreateNoWindow = $true", + "lineNumber": 273 + }, + { + "lineNumber": 274 + }, + { + "text": " $workerProcess = New-Object System.Diagnostics.Process", + "lineNumber": 275 + }, + { + "text": " $workerProcess.StartInfo = $psi", + "lineNumber": 276 + }, + { + "lineNumber": 277 + }, + { + "text": " if (-not $workerProcess.Start()) {", + "lineNumber": 278 + }, + { + "text": " throw \"Failed to start worker process.\"", + "lineNumber": 279 + }, + { + "text": " }", + "lineNumber": 280 + }, + { + "lineNumber": 281 + }, + { + "text": " Write-ControllerLog \"Worker process started with PID $($workerProcess.Id)\"", + "lineNumber": 282 + }, + { + "text": " Write-Metadata -Status 'running' -WorkerPid $workerProcess.Id -ControllerPid $controllerPid -Restarts $restartCount", + "lineNumber": 283 + }, + { + "lineNumber": 284 + }, + { + "text": " # Check if process exited immediately", + "lineNumber": 285 + }, + { + "text": " if ($workerProcess.HasExited) {", + "lineNumber": 286 + }, + { + "text": " $exitCode = -1", + "lineNumber": 287 + }, + { + "text": " try {", + "lineNumber": 288 + }, + { + "text": " $exitCode = $workerProcess.ExitCode", + "lineNumber": 289 + }, + { + "text": " }", + "lineNumber": 290 + }, + { + "text": " catch {", + "lineNumber": 291 + }, + { + "text": " Write-ControllerLog \"Unable to read immediate exit code: $($_.Exception.Message)\"", + "lineNumber": 292 + }, + { + "text": " }", + "lineNumber": 293 + }, + { + "text": " Write-ControllerLog \"Worker process exited immediately after start with code $exitCode\"", + "lineNumber": 294 + }, + { + "text": " Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode", + "lineNumber": 295 + }, + { + "text": " if ($exitCode -eq 0) { break }", + "lineNumber": 296 + }, + { + "text": " # Continue to restart logic below", + "lineNumber": 297 + }, + { + "text": " }", + "lineNumber": 298 + }, + { + "text": " else {", + "lineNumber": 299 + }, + { + "text": " $stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')", + "lineNumber": 300 + } + ] + }, + "score": 0.35743409395217896 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Scripts\\remote_worker_controller.ps1", + "range": { + "startPosition": { + "line": 381 + }, + "endPosition": { + "line": 407 + } + }, + "contents": " break\n }\n\n Write-ControllerLog \"Restarting worker in $RestartDelaySeconds seconds (attempt $restartCount of $MaxRestarts).\"\n Start-Sleep -Seconds $RestartDelaySeconds\n }\n\n Write-Metadata -Status 'inactive' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount\n Write-ControllerLog \"Controller exiting.\"\n}\ncatch {\n Write-FatalLog \"Fatal controller error: $($_.Exception.Message)\"\n if ($_.ScriptStackTrace) {\n Write-FatalLog \"Stack: $($_.ScriptStackTrace)\"\n }\n throw\n}\nfinally {\n if ($logWriter) {\n $logWriter.Dispose()\n }\n if ($logStream) {\n $logStream.Dispose()\n }\n}", + "signatures": {}, + "detailedLines": [ + { + "text": " break", + "lineNumber": 382 + }, + { + "text": " }", + "lineNumber": 383 + }, + { + "lineNumber": 384 + }, + { + "text": " Write-ControllerLog \"Restarting worker in $RestartDelaySeconds seconds (attempt $restartCount of $MaxRestarts).\"", + "lineNumber": 385 + }, + { + "text": " Start-Sleep -Seconds $RestartDelaySeconds", + "lineNumber": 386 + }, + { + "text": " }", + "lineNumber": 387 + }, + { + "lineNumber": 388 + }, + { + "text": " Write-Metadata -Status 'inactive' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount", + "lineNumber": 389 + }, + { + "text": " Write-ControllerLog \"Controller exiting.\"", + "lineNumber": 390 + }, + { + "text": "}", + "lineNumber": 391 + }, + { + "text": "catch {", + "lineNumber": 392 + }, + { + "text": " Write-FatalLog \"Fatal controller error: $($_.Exception.Message)\"", + "lineNumber": 393 + }, + { + "text": " if ($_.ScriptStackTrace) {", + "lineNumber": 394 + }, + { + "text": " Write-FatalLog \"Stack: $($_.ScriptStackTrace)\"", + "lineNumber": 395 + }, + { + "text": " }", + "lineNumber": 396 + }, + { + "text": " throw", + "lineNumber": 397 + }, + { + "text": "}", + "lineNumber": 398 + }, + { + "text": "finally {", + "lineNumber": 399 + }, + { + "text": " if ($logWriter) {", + "lineNumber": 400 + }, + { + "text": " $logWriter.Dispose()", + "lineNumber": 401 + }, + { + "text": " }", + "lineNumber": 402 + }, + { + "text": " if ($logStream) {", + "lineNumber": 403 + }, + { + "text": " $logStream.Dispose()", + "lineNumber": 404 + }, + { + "text": " }", + "lineNumber": 405 + }, + { + "text": "}", + "lineNumber": 406 + } + ] + }, + "score": 0.35196453332901 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 188 + }, + "endPosition": { + "line": 234 + } + }, + "contents": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')\n\ntry {{\n Write-Host \"\"Setting up network connections...\"\" -ForegroundColor Cyan\n\n $drives = @({drives})\n $networkPaths = @({paths})\n\n # Disconnect all existing connections\n Write-Host \"\"Disconnecting existing network connections...\"\" -ForegroundColor Yellow\n foreach ($path in $networkPaths) {{ net use $path /delete /y 2>$null }}\n foreach ($drive in $drives) {{ net use $drive /delete /y 2>$null }}\n Write-Host \"\"All network connections cleared.\"\" -ForegroundColor Green\n\n # Connect to network shares (simplified - credentials should be stored securely)\n Write-Host \"\"Establishing network connections...\"\" -ForegroundColor Cyan\n # TODO: Add credential handling for network shares\n\n # Start worker\n Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan\n Set-Location '{workerPath}'\n if (Test-Path 'flamenco-worker.exe') {{\n Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green\n $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait\n $exitCode = $workerProcess.ExitCode\n Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow\n exit $exitCode\n }} else {{\n Write-Host \"\"Error: flamenco-worker.exe not found in {workerPath}\"\" -ForegroundColor Red\n [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {workerPath}\"\")\n exit 1\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "signatures": {}, + "detailedLines": [ + { + "text": "[Console]::Error.WriteLine('[FLAMENCO] Payload script starting...')", + "lineNumber": 189 + }, + { + "lineNumber": 190 + }, + { + "text": "try {{", + "lineNumber": 191 + }, + { + "text": " Write-Host \"\"Setting up network connections...\"\" -ForegroundColor Cyan", + "lineNumber": 192 + }, + { + "lineNumber": 193 + }, + { + "text": " $drives = @({drives})", + "lineNumber": 194 + }, + { + "text": " $networkPaths = @({paths})", + "lineNumber": 195 + }, + { + "lineNumber": 196 + }, + { + "text": " # Disconnect all existing connections", + "lineNumber": 197 + }, + { + "text": " Write-Host \"\"Disconnecting existing network connections...\"\" -ForegroundColor Yellow", + "lineNumber": 198 + }, + { + "text": " foreach ($path in $networkPaths) {{ net use $path /delete /y 2>$null }}", + "lineNumber": 199 + }, + { + "text": " foreach ($drive in $drives) {{ net use $drive /delete /y 2>$null }}", + "lineNumber": 200 + }, + { + "text": " Write-Host \"\"All network connections cleared.\"\" -ForegroundColor Green", + "lineNumber": 201 + }, + { + "lineNumber": 202 + }, + { + "text": " # Connect to network shares (simplified - credentials should be stored securely)", + "lineNumber": 203 + }, + { + "text": " Write-Host \"\"Establishing network connections...\"\" -ForegroundColor Cyan", + "lineNumber": 204 + }, + { + "text": " # TODO: Add credential handling for network shares", + "lineNumber": 205 + }, + { + "lineNumber": 206 + }, + { + "text": " # Start worker", + "lineNumber": 207 + }, + { + "text": " Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan", + "lineNumber": 208 + }, + { + "text": " Set-Location '{workerPath}'", + "lineNumber": 209 + }, + { + "text": " if (Test-Path 'flamenco-worker.exe') {{", + "lineNumber": 210 + }, + { + "text": " Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green", + "lineNumber": 211 + }, + { + "text": " $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait", + "lineNumber": 212 + }, + { + "text": " $exitCode = $workerProcess.ExitCode", + "lineNumber": 213 + }, + { + "text": " Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow", + "lineNumber": 214 + }, + { + "text": " exit $exitCode", + "lineNumber": 215 + }, + { + "text": " }} else {{", + "lineNumber": 216 + }, + { + "text": " Write-Host \"\"Error: flamenco-worker.exe not found in {workerPath}\"\" -ForegroundColor Red", + "lineNumber": 217 + }, + { + "text": " [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {workerPath}\"\")", + "lineNumber": 218 + }, + { + "text": " exit 1", + "lineNumber": 219 + }, + { + "text": " }}", + "lineNumber": 220 + }, + { + "text": "}}", + "lineNumber": 221 + }, + { + "text": "catch {{", + "lineNumber": 222 + }, + { + "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)", + "lineNumber": 223 + }, + { + "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)", + "lineNumber": 224 + }, + { + "text": " Write-Host $errorMsg -ForegroundColor Red", + "lineNumber": 225 + }, + { + "text": " Write-Host $stackMsg -ForegroundColor DarkRed", + "lineNumber": 226 + }, + { + "text": " [Console]::Error.WriteLine($errorMsg)", + "lineNumber": 227 + }, + { + "text": " [Console]::Error.WriteLine($stackMsg)", + "lineNumber": 228 + }, + { + "text": " exit 1", + "lineNumber": 229 + }, + { + "text": "}}", + "lineNumber": 230 + }, + { + "text": "\";", + "lineNumber": 231 + }, + { + "text": " }", + "lineNumber": 232 + }, + { + "lineNumber": 233 + }, + { + "text": " public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 234 + } + ] + }, + "score": 0.337083101272583 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 273 + }, + "endPosition": { + "line": 323, + "column": 8 + } + }, + "contents": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}\nif (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}\n[IO.File]::WriteAllText($payloadBase64Path, $payloadBase64, [System.Text.Encoding]::UTF8)\n$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'\n$controllerPath = Join-Path $dataRoot 'controller.ps1'\n\nif (-not (Test-Path $controllerPath)) {{\n throw \"\"Controller missing at $controllerPath\"\"\n}}\n\n$shouldStart = $true\nif (Test-Path $metaPath) {{\n try {{\n $meta = Get-Content $metaPath -Raw | ConvertFrom-Json\n if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{\n if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{\n Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"\n $shouldStart = $false\n }}\n }}\n }} catch {{\n Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow\n }}\n}}\n\nif ($shouldStart) {{\n $initialMeta = [pscustomobject]@{{\n WorkerName = $workerName\n WorkerType = $workerType\n Status = 'launching'\n ControllerPid = $null\n WorkerPid = $null\n Restarts = 0\n LastExitCode = $null\n LogPath = $logPath\n CommandPath = $commandPath\n PayloadPath = $payloadPath\n UpdatedAtUtc = (Get-Date).ToUniversalTime()\n }} | ConvertTo-Json -Depth 5\n $initialMeta | Set-Content -Path $metaPath -Encoding UTF8\n\n $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue\n if ($pwsh) {{\n $psExe = $pwsh.Source\n }}\n else {{\n $psExe = (Get-Command powershell -ErrorAction Stop).Source\n }}\n\n $controllerArgs = @(\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}", + "lineNumber": 274 + }, + { + "text": "if (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "lineNumber": 275 + }, + { + "text": "[IO.File]::WriteAllText($payloadBase64Path, $payloadBase64, [System.Text.Encoding]::UTF8)", + "lineNumber": 276 + }, + { + "text": "$metaPath = Join-Path $instanceRoot 'state\\worker-info.json'", + "lineNumber": 277 + }, + { + "text": "$controllerPath = Join-Path $dataRoot 'controller.ps1'", + "lineNumber": 278 + }, + { + "lineNumber": 279 + }, + { + "text": "if (-not (Test-Path $controllerPath)) {{", + "lineNumber": 280 + }, + { + "text": " throw \"\"Controller missing at $controllerPath\"\"", + "lineNumber": 281 + }, + { + "text": "}}", + "lineNumber": 282 + }, + { + "lineNumber": 283 + }, + { + "text": "$shouldStart = $true", + "lineNumber": 284 + }, + { + "text": "if (Test-Path $metaPath) {{", + "lineNumber": 285 + }, + { + "text": " try {{", + "lineNumber": 286 + }, + { + "text": " $meta = Get-Content $metaPath -Raw | ConvertFrom-Json", + "lineNumber": 287 + }, + { + "text": " if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{", + "lineNumber": 288 + }, + { + "text": " if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{", + "lineNumber": 289 + }, + { + "text": " Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"", + "lineNumber": 290 + }, + { + "text": " $shouldStart = $false", + "lineNumber": 291 + }, + { + "text": " }}", + "lineNumber": 292 + }, + { + "text": " }}", + "lineNumber": 293 + }, + { + "text": " }} catch {{", + "lineNumber": 294 + }, + { + "text": " Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow", + "lineNumber": 295 + }, + { + "text": " }}", + "lineNumber": 296 + }, + { + "text": "}}", + "lineNumber": 297 + }, + { + "lineNumber": 298 + }, + { + "text": "if ($shouldStart) {{", + "lineNumber": 299 + }, + { + "text": " $initialMeta = [pscustomobject]@{{", + "lineNumber": 300 + }, + { + "text": " WorkerName = $workerName", + "lineNumber": 301 + }, + { + "text": " WorkerType = $workerType", + "lineNumber": 302 + }, + { + "text": " Status = 'launching'", + "lineNumber": 303 + }, + { + "text": " ControllerPid = $null", + "lineNumber": 304 + }, + { + "text": " WorkerPid = $null", + "lineNumber": 305 + }, + { + "text": " Restarts = 0", + "lineNumber": 306 + }, + { + "text": " LastExitCode = $null", + "lineNumber": 307 + }, + { + "text": " LogPath = $logPath", + "lineNumber": 308 + }, + { + "text": " CommandPath = $commandPath", + "lineNumber": 309 + }, + { + "text": " PayloadPath = $payloadPath", + "lineNumber": 310 + }, + { + "text": " UpdatedAtUtc = (Get-Date).ToUniversalTime()", + "lineNumber": 311 + }, + { + "text": " }} | ConvertTo-Json -Depth 5", + "lineNumber": 312 + }, + { + "text": " $initialMeta | Set-Content -Path $metaPath -Encoding UTF8", + "lineNumber": 313 + }, + { + "lineNumber": 314 + }, + { + "text": " $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue", + "lineNumber": 315 + }, + { + "text": " if ($pwsh) {{", + "lineNumber": 316 + }, + { + "text": " $psExe = $pwsh.Source", + "lineNumber": 317 + }, + { + "text": " }}", + "lineNumber": 318 + }, + { + "text": " else {{", + "lineNumber": 319 + }, + { + "text": " $psExe = (Get-Command powershell -ErrorAction Stop).Source", + "lineNumber": 320 + }, + { + "text": " }}", + "lineNumber": 321 + }, + { + "lineNumber": 322 + }, + { + "text": " $controllerArgs = @(", + "lineNumber": 323 + }, + { + "text": " ", + "lineNumber": 324 + } + ] + }, + "score": 0.32781049609184265 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Scripts\\remote_worker_controller.ps1", + "range": { + "startPosition": { + "line": 225 + }, + "endPosition": { + "line": 258 + } + }, + "contents": "try {\n # record initial state before launching worker\n Write-Metadata -Status 'initializing' -WorkerPid $null -ControllerPid $PID -Restarts 0\n\n $resolvedPayloadBase64 = Resolve-PayloadBase64\n $PayloadBase64 = $resolvedPayloadBase64\n\n try {\n # Write payload script to disk\n # The payload is base64-encoded UTF-16 (Unicode), so decode it properly\n Write-ControllerLog \"Decoding payload base64 (length: $($resolvedPayloadBase64.Length))\"\n $payloadBytes = [Convert]::FromBase64String($resolvedPayloadBase64)\n Write-ControllerLog \"Decoded payload to $($payloadBytes.Length) bytes\"\n \n # Convert UTF-16 bytes back to string, then write as UTF-8 (PowerShell's preferred encoding)\n $payloadText = [Text.Encoding]::Unicode.GetString($payloadBytes)\n [IO.File]::WriteAllText($payloadPath, $payloadText, [Text.Encoding]::UTF8)\n Write-ControllerLog \"Payload written to $payloadPath ($($payloadText.Length) characters)\"\n }\n catch {\n Write-FatalLog \"Unable to write payload: $($_.Exception.Message)\"\n if ($_.Exception.InnerException) {\n Write-FatalLog \"Inner exception: $($_.Exception.InnerException.Message)\"\n }\n throw\n }\n\n $restartCount = 0\n $controllerPid = $PID\n\n while ($restartCount -le $MaxRestarts) {\n try {\n # Initialize worker process", + "signatures": {}, + "detailedLines": [ + { + "text": "try {", + "lineNumber": 226 + }, + { + "text": " # record initial state before launching worker", + "lineNumber": 227 + }, + { + "text": " Write-Metadata -Status 'initializing' -WorkerPid $null -ControllerPid $PID -Restarts 0", + "lineNumber": 228 + }, + { + "lineNumber": 229 + }, + { + "text": " $resolvedPayloadBase64 = Resolve-PayloadBase64", + "lineNumber": 230 + }, + { + "text": " $PayloadBase64 = $resolvedPayloadBase64", + "lineNumber": 231 + }, + { + "lineNumber": 232 + }, + { + "text": " try {", + "lineNumber": 233 + }, + { + "text": " # Write payload script to disk", + "lineNumber": 234 + }, + { + "text": " # The payload is base64-encoded UTF-16 (Unicode), so decode it properly", + "lineNumber": 235 + }, + { + "text": " Write-ControllerLog \"Decoding payload base64 (length: $($resolvedPayloadBase64.Length))\"", + "lineNumber": 236 + }, + { + "text": " $payloadBytes = [Convert]::FromBase64String($resolvedPayloadBase64)", + "lineNumber": 237 + }, + { + "text": " Write-ControllerLog \"Decoded payload to $($payloadBytes.Length) bytes\"", + "lineNumber": 238 + }, + { + "text": " ", + "lineNumber": 239 + }, + { + "text": " # Convert UTF-16 bytes back to string, then write as UTF-8 (PowerShell's preferred encoding)", + "lineNumber": 240 + }, + { + "text": " $payloadText = [Text.Encoding]::Unicode.GetString($payloadBytes)", + "lineNumber": 241 + }, + { + "text": " [IO.File]::WriteAllText($payloadPath, $payloadText, [Text.Encoding]::UTF8)", + "lineNumber": 242 + }, + { + "text": " Write-ControllerLog \"Payload written to $payloadPath ($($payloadText.Length) characters)\"", + "lineNumber": 243 + }, + { + "text": " }", + "lineNumber": 244 + }, + { + "text": " catch {", + "lineNumber": 245 + }, + { + "text": " Write-FatalLog \"Unable to write payload: $($_.Exception.Message)\"", + "lineNumber": 246 + }, + { + "text": " if ($_.Exception.InnerException) {", + "lineNumber": 247 + }, + { + "text": " Write-FatalLog \"Inner exception: $($_.Exception.InnerException.Message)\"", + "lineNumber": 248 + }, + { + "text": " }", + "lineNumber": 249 + }, + { + "text": " throw", + "lineNumber": 250 + }, + { + "text": " }", + "lineNumber": 251 + }, + { + "lineNumber": 252 + }, + { + "text": " $restartCount = 0", + "lineNumber": 253 + }, + { + "text": " $controllerPid = $PID", + "lineNumber": 254 + }, + { + "lineNumber": 255 + }, + { + "text": " while ($restartCount -le $MaxRestarts) {", + "lineNumber": 256 + }, + { + "text": " try {", + "lineNumber": 257 + }, + { + "text": " # Initialize worker process", + "lineNumber": 258 + } + ] + }, + "score": 0.3125419020652771 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 320 + }, + "endPosition": { + "line": 359 + } + }, + "contents": " }}\n\n $controllerArgs = @(\n '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',\n '-File',\"\"$controllerPath\"\",\n '-WorkerName',\"\"$workerName\"\",\n '-WorkerType',\"\"$workerType\"\",\n '-PayloadBase64Path',\"\"$payloadBase64Path\"\"\n )\n\n Write-Host \"\"Attempting to start controller with: $psExe\"\" -ForegroundColor Cyan\n Write-Host \"\"Controller path: $controllerPath\"\" -ForegroundColor Cyan\n Write-Host \"\"Arguments: $($controllerArgs -join ' ')\"\" -ForegroundColor Cyan\n \n try {{\n $controllerProcess = Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden -PassThru -ErrorAction Stop\n if ($controllerProcess) {{\n $controllerPid = $controllerProcess.Id\n Write-Host \"\"Controller process started with PID: $controllerPid\"\" -ForegroundColor Green\n \n # Give it a moment to initialize\n Start-Sleep -Milliseconds 500\n \n # Verify process is still running\n $stillRunning = Get-Process -Id $controllerPid -ErrorAction SilentlyContinue\n if (-not $stillRunning) {{\n throw \"\"Controller process (PID: $controllerPid) exited immediately after start\"\"\n }}\n \n # Update metadata with controller PID\n $updatedMeta = [pscustomobject]@{{\n WorkerName = $workerName\n WorkerType = $workerType\n Status = 'launching'\n ControllerPid = $controllerPid\n WorkerPid = $null\n Restarts = 0\n LastExitCode = $null\n LogPath = $logPath", + "signatures": {}, + "detailedLines": [ + { + "text": " }}", + "lineNumber": 321 + }, + { + "lineNumber": 322 + }, + { + "text": " $controllerArgs = @(", + "lineNumber": 323 + }, + { + "text": " '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',", + "lineNumber": 324 + }, + { + "text": " '-File',\"\"$controllerPath\"\",", + "lineNumber": 325 + }, + { + "text": " '-WorkerName',\"\"$workerName\"\",", + "lineNumber": 326 + }, + { + "text": " '-WorkerType',\"\"$workerType\"\",", + "lineNumber": 327 + }, + { + "text": " '-PayloadBase64Path',\"\"$payloadBase64Path\"\"", + "lineNumber": 328 + }, + { + "text": " )", + "lineNumber": 329 + }, + { + "lineNumber": 330 + }, + { + "text": " Write-Host \"\"Attempting to start controller with: $psExe\"\" -ForegroundColor Cyan", + "lineNumber": 331 + }, + { + "text": " Write-Host \"\"Controller path: $controllerPath\"\" -ForegroundColor Cyan", + "lineNumber": 332 + }, + { + "text": " Write-Host \"\"Arguments: $($controllerArgs -join ' ')\"\" -ForegroundColor Cyan", + "lineNumber": 333 + }, + { + "text": " ", + "lineNumber": 334 + }, + { + "text": " try {{", + "lineNumber": 335 + }, + { + "text": " $controllerProcess = Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden -PassThru -ErrorAction Stop", + "lineNumber": 336 + }, + { + "text": " if ($controllerProcess) {{", + "lineNumber": 337 + }, + { + "text": " $controllerPid = $controllerProcess.Id", + "lineNumber": 338 + }, + { + "text": " Write-Host \"\"Controller process started with PID: $controllerPid\"\" -ForegroundColor Green", + "lineNumber": 339 + }, + { + "text": " ", + "lineNumber": 340 + }, + { + "text": " # Give it a moment to initialize", + "lineNumber": 341 + }, + { + "text": " Start-Sleep -Milliseconds 500", + "lineNumber": 342 + }, + { + "text": " ", + "lineNumber": 343 + }, + { + "text": " # Verify process is still running", + "lineNumber": 344 + }, + { + "text": " $stillRunning = Get-Process -Id $controllerPid -ErrorAction SilentlyContinue", + "lineNumber": 345 + }, + { + "text": " if (-not $stillRunning) {{", + "lineNumber": 346 + }, + { + "text": " throw \"\"Controller process (PID: $controllerPid) exited immediately after start\"\"", + "lineNumber": 347 + }, + { + "text": " }}", + "lineNumber": 348 + }, + { + "text": " ", + "lineNumber": 349 + }, + { + "text": " # Update metadata with controller PID", + "lineNumber": 350 + }, + { + "text": " $updatedMeta = [pscustomobject]@{{", + "lineNumber": 351 + }, + { + "text": " WorkerName = $workerName", + "lineNumber": 352 + }, + { + "text": " WorkerType = $workerType", + "lineNumber": 353 + }, + { + "text": " Status = 'launching'", + "lineNumber": 354 + }, + { + "text": " ControllerPid = $controllerPid", + "lineNumber": 355 + }, + { + "text": " WorkerPid = $null", + "lineNumber": 356 + }, + { + "text": " Restarts = 0", + "lineNumber": 357 + }, + { + "text": " LastExitCode = $null", + "lineNumber": 358 + }, + { + "text": " LogPath = $logPath", + "lineNumber": 359 + } + ] + }, + "score": 0.3094896078109741 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Scripts\\remote_worker_controller.ps1", + "range": { + "startPosition": { + "line": 298 + }, + "endPosition": { + "line": 334 + } + }, + "contents": " else {\n $stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')\n $stderrHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'ERR')\n \n $outputHandler = [System.Diagnostics.DataReceivedEventHandler]$stdoutHandler.OnDataReceived\n $errorHandler = [System.Diagnostics.DataReceivedEventHandler]$stderrHandler.OnDataReceived\n\n $workerProcess.add_OutputDataReceived($outputHandler)\n $workerProcess.add_ErrorDataReceived($errorHandler)\n $workerProcess.BeginOutputReadLine()\n $workerProcess.BeginErrorReadLine()\n Write-ControllerLog \"Output handlers set up successfully\"\n\n # Give process a moment to start, then check again\n Start-Sleep -Milliseconds 200\n if ($workerProcess.HasExited) {\n $exitCode = -1\n try {\n $exitCode = $workerProcess.ExitCode\n }\n catch {\n Write-ControllerLog \"Unable to read exit code after delay: $($_.Exception.Message)\"\n }\n Write-ControllerLog \"Worker process exited after 200ms delay with code $exitCode\"\n Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode\n if ($exitCode -eq 0) { break }\n # Continue to restart logic below\n }\n else {\n Write-ControllerLog \"Worker process is running, entering monitoring loop\"\n \n while (-not $workerProcess.HasExited) {\n $commands = Get-PendingCommands\n foreach ($command in $commands) {\n $trimmed = $command.Trim()\n if (-not $trimmed) { continue }", + "signatures": {}, + "detailedLines": [ + { + "text": " else {", + "lineNumber": 299 + }, + { + "text": " $stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')", + "lineNumber": 300 + }, + { + "text": " $stderrHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'ERR')", + "lineNumber": 301 + }, + { + "text": " ", + "lineNumber": 302 + }, + { + "text": " $outputHandler = [System.Diagnostics.DataReceivedEventHandler]$stdoutHandler.OnDataReceived", + "lineNumber": 303 + }, + { + "text": " $errorHandler = [System.Diagnostics.DataReceivedEventHandler]$stderrHandler.OnDataReceived", + "lineNumber": 304 + }, + { + "lineNumber": 305 + }, + { + "text": " $workerProcess.add_OutputDataReceived($outputHandler)", + "lineNumber": 306 + }, + { + "text": " $workerProcess.add_ErrorDataReceived($errorHandler)", + "lineNumber": 307 + }, + { + "text": " $workerProcess.BeginOutputReadLine()", + "lineNumber": 308 + }, + { + "text": " $workerProcess.BeginErrorReadLine()", + "lineNumber": 309 + }, + { + "text": " Write-ControllerLog \"Output handlers set up successfully\"", + "lineNumber": 310 + }, + { + "lineNumber": 311 + }, + { + "text": " # Give process a moment to start, then check again", + "lineNumber": 312 + }, + { + "text": " Start-Sleep -Milliseconds 200", + "lineNumber": 313 + }, + { + "text": " if ($workerProcess.HasExited) {", + "lineNumber": 314 + }, + { + "text": " $exitCode = -1", + "lineNumber": 315 + }, + { + "text": " try {", + "lineNumber": 316 + }, + { + "text": " $exitCode = $workerProcess.ExitCode", + "lineNumber": 317 + }, + { + "text": " }", + "lineNumber": 318 + }, + { + "text": " catch {", + "lineNumber": 319 + }, + { + "text": " Write-ControllerLog \"Unable to read exit code after delay: $($_.Exception.Message)\"", + "lineNumber": 320 + }, + { + "text": " }", + "lineNumber": 321 + }, + { + "text": " Write-ControllerLog \"Worker process exited after 200ms delay with code $exitCode\"", + "lineNumber": 322 + }, + { + "text": " Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode", + "lineNumber": 323 + }, + { + "text": " if ($exitCode -eq 0) { break }", + "lineNumber": 324 + }, + { + "text": " # Continue to restart logic below", + "lineNumber": 325 + }, + { + "text": " }", + "lineNumber": 326 + }, + { + "text": " else {", + "lineNumber": 327 + }, + { + "text": " Write-ControllerLog \"Worker process is running, entering monitoring loop\"", + "lineNumber": 328 + }, + { + "text": " ", + "lineNumber": 329 + }, + { + "text": " while (-not $workerProcess.HasExited) {", + "lineNumber": 330 + }, + { + "text": " $commands = Get-PendingCommands", + "lineNumber": 331 + }, + { + "text": " foreach ($command in $commands) {", + "lineNumber": 332 + }, + { + "text": " $trimmed = $command.Trim()", + "lineNumber": 333 + }, + { + "text": " if (-not $trimmed) { continue }", + "lineNumber": 334 + } + ] + }, + "score": 0.30853116512298584 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs", + "range": { + "startPosition": { + "line": 230 + }, + "endPosition": { + "line": 275 + } + }, + "contents": "\";\n }\n\n public async Task StartWorkerAsync(WorkerConfig worker, string workerType)\n {\n await DeployControllerAsync(worker);\n\n string payloadScript;\n if (workerType == \"sheepit\")\n {\n payloadScript = GenerateSheepItPayload(worker);\n }\n else if (workerType == \"flamenco\")\n {\n payloadScript = GenerateFlamencoPayload(worker);\n }\n else\n {\n throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));\n }\n\n var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n\n var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";\n var jsonParamsBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(jsonParams));\n\n var ensureScript = $@\"\n$ProgressPreference = 'SilentlyContinue'\n$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{jsonParamsBase64}')))\n$workerName = $params.WorkerName\n$workerType = $params.WorkerType\n$payloadBase64 = $params.PayloadBase64\n$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'\n$instanceRoot = Join-Path (Join-Path $dataRoot $workerType) $workerName\n$logsRoot = Join-Path $instanceRoot 'logs'\n$stateRoot = Join-Path $instanceRoot 'state'\nNew-Item -ItemType Directory -Path $logsRoot -Force | Out-Null\nNew-Item -ItemType Directory -Path $stateRoot -Force | Out-Null\n$logPath = Join-Path $logsRoot 'worker.log'\n$commandPath = Join-Path $stateRoot 'commands.txt'\n$payloadPath = Join-Path $stateRoot 'payload.ps1'\n$payloadBase64Path = Join-Path $stateRoot 'payload.b64'\nif (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}\nif (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "signatures": {}, + "detailedLines": [ + { + "text": "\";", + "lineNumber": 231 + }, + { + "text": " }", + "lineNumber": 232 + }, + { + "lineNumber": 233 + }, + { + "text": " public async Task StartWorkerAsync(WorkerConfig worker, string workerType)", + "lineNumber": 234 + }, + { + "text": " {", + "lineNumber": 235 + }, + { + "text": " await DeployControllerAsync(worker);", + "lineNumber": 236 + }, + { + "lineNumber": 237 + }, + { + "text": " string payloadScript;", + "lineNumber": 238 + }, + { + "text": " if (workerType == \"sheepit\")", + "lineNumber": 239 + }, + { + "text": " {", + "lineNumber": 240 + }, + { + "text": " payloadScript = GenerateSheepItPayload(worker);", + "lineNumber": 241 + }, + { + "text": " }", + "lineNumber": 242 + }, + { + "text": " else if (workerType == \"flamenco\")", + "lineNumber": 243 + }, + { + "text": " {", + "lineNumber": 244 + }, + { + "text": " payloadScript = GenerateFlamencoPayload(worker);", + "lineNumber": 245 + }, + { + "text": " }", + "lineNumber": 246 + }, + { + "text": " else", + "lineNumber": 247 + }, + { + "text": " {", + "lineNumber": 248 + }, + { + "text": " throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));", + "lineNumber": 249 + }, + { + "text": " }", + "lineNumber": 250 + }, + { + "lineNumber": 251 + }, + { + "text": " var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));", + "lineNumber": 252 + }, + { + "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);", + "lineNumber": 253 + }, + { + "lineNumber": 254 + }, + { + "text": " var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";", + "lineNumber": 255 + }, + { + "text": " var jsonParamsBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(jsonParams));", + "lineNumber": 256 + }, + { + "lineNumber": 257 + }, + { + "text": " var ensureScript = $@\"", + "lineNumber": 258 + }, + { + "text": "$ProgressPreference = 'SilentlyContinue'", + "lineNumber": 259 + }, + { + "text": "$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{jsonParamsBase64}')))", + "lineNumber": 260 + }, + { + "text": "$workerName = $params.WorkerName", + "lineNumber": 261 + }, + { + "text": "$workerType = $params.WorkerType", + "lineNumber": 262 + }, + { + "text": "$payloadBase64 = $params.PayloadBase64", + "lineNumber": 263 + }, + { + "text": "$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'", + "lineNumber": 264 + }, + { + "text": "$instanceRoot = Join-Path (Join-Path $dataRoot $workerType) $workerName", + "lineNumber": 265 + }, + { + "text": "$logsRoot = Join-Path $instanceRoot 'logs'", + "lineNumber": 266 + }, + { + "text": "$stateRoot = Join-Path $instanceRoot 'state'", + "lineNumber": 267 + }, + { + "text": "New-Item -ItemType Directory -Path $logsRoot -Force | Out-Null", + "lineNumber": 268 + }, + { + "text": "New-Item -ItemType Directory -Path $stateRoot -Force | Out-Null", + "lineNumber": 269 + }, + { + "text": "$logPath = Join-Path $logsRoot 'worker.log'", + "lineNumber": 270 + }, + { + "text": "$commandPath = Join-Path $stateRoot 'commands.txt'", + "lineNumber": 271 + }, + { + "text": "$payloadPath = Join-Path $stateRoot 'payload.ps1'", + "lineNumber": 272 + }, + { + "text": "$payloadBase64Path = Join-Path $stateRoot 'payload.b64'", + "lineNumber": 273 + }, + { + "text": "if (-not (Test-Path $logPath)) {{ New-Item -Path $logPath -ItemType File -Force | Out-Null }}", + "lineNumber": 274 + }, + { + "text": "if (-not (Test-Path $commandPath)) {{ New-Item -Path $commandPath -ItemType File -Force | Out-Null }}", + "lineNumber": 275 + } + ] + }, + "score": 0.3079460859298706 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\Scripts\\remote_worker_controller.ps1", + "range": { + "startPosition": {}, + "endPosition": { + "line": 57 + } + }, + "contents": "param(\n [Parameter(Mandatory = $true)]\n [string]$WorkerName,\n\n [Parameter(Mandatory = $true)]\n [string]$WorkerType,\n\n [string]$PayloadBase64,\n\n [string]$PayloadBase64Path,\n\n[string]$DataRoot = (Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'),\n\n [int]$MaxRestarts = 5,\n\n [int]$RestartDelaySeconds = 10\n)\n\n$ErrorActionPreference = 'Stop'\ntry {\n if ($Host -and $Host.Runspace) {\n [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace = $Host.Runspace\n }\n}\ncatch {\n # Ignore runspace assignment errors - not critical for non-interactive execution\n}\n\n# region Path setup\n$workerRoot = Join-Path -Path $DataRoot -ChildPath $WorkerType\n$instanceRoot = Join-Path -Path $workerRoot -ChildPath $WorkerName\nNew-Item -ItemType Directory -Path $instanceRoot -Force | Out-Null\n\n$logsRoot = Join-Path -Path $instanceRoot -ChildPath 'logs'\n$stateRoot = Join-Path -Path $instanceRoot -ChildPath 'state'\nNew-Item -ItemType Directory -Path $logsRoot -Force | Out-Null\nNew-Item -ItemType Directory -Path $stateRoot -Force | Out-Null\n\n$logPath = Join-Path -Path $logsRoot -ChildPath 'worker.log'\n$metaPath = Join-Path -Path $stateRoot -ChildPath 'worker-info.json'\n$commandPath = Join-Path -Path $stateRoot -ChildPath 'commands.txt'\n$payloadPath = Join-Path -Path $stateRoot -ChildPath \"payload.ps1\"\n# endregion\n\n# region Logging\ntry {\n $logStream = [System.IO.FileStream]::new(\n $logPath,\n [System.IO.FileMode]::Append,\n [System.IO.FileAccess]::Write,\n [System.IO.FileShare]::ReadWrite\n )\n $logWriter = [System.IO.StreamWriter]::new($logStream, [System.Text.Encoding]::UTF8)\n $logWriter.AutoFlush = $true\n}\ncatch {\n # If we can't open the log file, write error to metadata and exit", + "signatures": {}, + "detailedLines": [ + { + "text": "param(", + "lineNumber": 1 + }, + { + "text": " [Parameter(Mandatory = $true)]", + "lineNumber": 2 + }, + { + "text": " [string]$WorkerName,", + "lineNumber": 3 + }, + { + "lineNumber": 4 + }, + { + "text": " [Parameter(Mandatory = $true)]", + "lineNumber": 5 + }, + { + "text": " [string]$WorkerType,", + "lineNumber": 6 + }, + { + "lineNumber": 7 + }, + { + "text": " [string]$PayloadBase64,", + "lineNumber": 8 + }, + { + "lineNumber": 9 + }, + { + "text": " [string]$PayloadBase64Path,", + "lineNumber": 10 + }, + { + "lineNumber": 11 + }, + { + "text": "[string]$DataRoot = (Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'),", + "lineNumber": 12 + }, + { + "lineNumber": 13 + }, + { + "text": " [int]$MaxRestarts = 5,", + "lineNumber": 14 + }, + { + "lineNumber": 15 + }, + { + "text": " [int]$RestartDelaySeconds = 10", + "lineNumber": 16 + }, + { + "text": ")", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 19 + }, + { + "text": "try {", + "lineNumber": 20 + }, + { + "text": " if ($Host -and $Host.Runspace) {", + "lineNumber": 21 + }, + { + "text": " [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace = $Host.Runspace", + "lineNumber": 22 + }, + { + "text": " }", + "lineNumber": 23 + }, + { + "text": "}", + "lineNumber": 24 + }, + { + "text": "catch {", + "lineNumber": 25 + }, + { + "text": " # Ignore runspace assignment errors - not critical for non-interactive execution", + "lineNumber": 26 + }, + { + "text": "}", + "lineNumber": 27 + }, + { + "lineNumber": 28 + }, + { + "text": "# region Path setup", + "lineNumber": 29 + }, + { + "text": "$workerRoot = Join-Path -Path $DataRoot -ChildPath $WorkerType", + "lineNumber": 30 + }, + { + "text": "$instanceRoot = Join-Path -Path $workerRoot -ChildPath $WorkerName", + "lineNumber": 31 + }, + { + "text": "New-Item -ItemType Directory -Path $instanceRoot -Force | Out-Null", + "lineNumber": 32 + }, + { + "lineNumber": 33 + }, + { + "text": "$logsRoot = Join-Path -Path $instanceRoot -ChildPath 'logs'", + "lineNumber": 34 + }, + { + "text": "$stateRoot = Join-Path -Path $instanceRoot -ChildPath 'state'", + "lineNumber": 35 + }, + { + "text": "New-Item -ItemType Directory -Path $logsRoot -Force | Out-Null", + "lineNumber": 36 + }, + { + "text": "New-Item -ItemType Directory -Path $stateRoot -Force | Out-Null", + "lineNumber": 37 + }, + { + "lineNumber": 38 + }, + { + "text": "$logPath = Join-Path -Path $logsRoot -ChildPath 'worker.log'", + "lineNumber": 39 + }, + { + "text": "$metaPath = Join-Path -Path $stateRoot -ChildPath 'worker-info.json'", + "lineNumber": 40 + }, + { + "text": "$commandPath = Join-Path -Path $stateRoot -ChildPath 'commands.txt'", + "lineNumber": 41 + }, + { + "text": "$payloadPath = Join-Path -Path $stateRoot -ChildPath \"payload.ps1\"", + "lineNumber": 42 + }, + { + "text": "# endregion", + "lineNumber": 43 + }, + { + "lineNumber": 44 + }, + { + "text": "# region Logging", + "lineNumber": 45 + }, + { + "text": "try {", + "lineNumber": 46 + }, + { + "text": " $logStream = [System.IO.FileStream]::new(", + "lineNumber": 47 + }, + { + "text": " $logPath,", + "lineNumber": 48 + }, + { + "text": " [System.IO.FileMode]::Append,", + "lineNumber": 49 + }, + { + "text": " [System.IO.FileAccess]::Write,", + "lineNumber": 50 + }, + { + "text": " [System.IO.FileShare]::ReadWrite", + "lineNumber": 51 + }, + { + "text": " )", + "lineNumber": 52 + }, + { + "text": " $logWriter = [System.IO.StreamWriter]::new($logStream, [System.Text.Encoding]::UTF8)", + "lineNumber": 53 + }, + { + "text": " $logWriter.AutoFlush = $true", + "lineNumber": 54 + }, + { + "text": "}", + "lineNumber": 55 + }, + { + "text": "catch {", + "lineNumber": 56 + }, + { + "text": " # If we can't open the log file, write error to metadata and exit", + "lineNumber": 57 + } + ] + }, + "score": 0.30259451270103455 + } + ] +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **ripgrep_raw_search** + + +Parameters: + +```json +{ + "pattern": "Failed to start worker", + "caseInsensitive": false +} +``` +Result: + +```json +{ + "success": { + "pattern": "Failed to start worker", + "outputMode": "content", + "workspaceResults": { + "c:\\Users\\Nathan\\Desktop\\UFL": { + "content": { + "matches": [ + { + "file": "Views/MainWindow.axaml.cs", + "matches": [ + { + "lineNumber": 132, + "content": " $\"Failed to start worker: {ex.Message}\"," + } + ] + }, + { + "file": "Scripts/remote_worker_controller.ps1", + "matches": [ + { + "lineNumber": 279, + "content": " throw \"Failed to start worker process.\"" + } + ] + }, + { + "file": "Views\\MainWindow.axaml.cs", + "matches": [ + { + "lineNumber": 132, + "content": " $\"Failed to start worker: {ex.Message}\"," + } + ] + } + ], + "totalLines": 3, + "totalMatchedLines": 3 + } + } + } + } +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **ripgrep_raw_search** + + +Parameters: + +```json +{ + "pattern": "InvalidOperationException.*Worker did not start", + "caseInsensitive": false +} +``` +Result: + +```json +{ + "success": { + "pattern": "InvalidOperationException.*Worker did not start", + "outputMode": "content", + "workspaceResults": { + "c:\\Users\\Nathan\\Desktop\\UFL": { + "content": { + "matches": [ + { + "file": "Services\\WorkerControllerService.cs", + "matches": [ + { + "lineNumber": 441, + "content": " throw new InvalidOperationException($\"Worker did not start successfully. Status: {statusText}. {errorMessage}\\n\\n{diagnostics}\");" + } + ] + } + ], + "totalLines": 1, + "totalMatchedLines": 1 + } + } + } + } +} +``` +Status: **completed** + + +
+
+ +--- + + +The error is shown via `MessageBoxManager.GetMessageBoxStandard`, which doesn't support scrolling. Creating a custom scrollable error dialog for long diagnostics and log entries: + + +--- + + +
Tool use: **read_file_v2** • Read file: Views/MainWindow.axaml.cs + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Views/MainWindow.axaml.cs + +
+
+ +--- + + +
+Tool use: **glob_file_search** • Searched codebase "undefined" • **6** results in **1** directory + +Directory: **undefined** (6 files) + +| File | +|------| +| `Views/WorkerEditWindow.axaml` | +| `Views\MainWindow.axaml` | +| `Views/MainWindow.axaml` | +| `Views\WorkerEditWindow.axaml` | +| `App.axaml` | +| `Views\GlobalSettingsWindow.axaml` | + +
+
+ +--- + + +Creating a custom scrollable error dialog window: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Views/ErrorDialog.axaml" +} +``` +Additional data: + +```json +{ + "codeblockId": "00301362-1924-4392-a4b4-fc1c5d4261d2" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Views/ErrorDialog.axaml.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "545a7a37-f390-4817-90c1-aed1c47915d2" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Views/MainWindow.axaml.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "e56b1e8c-9b29-4c7c-8319-17b914ad7704" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Views/MainWindow.axaml.cs + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Views/MainWindow.axaml.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "1a497e38-fdda-47a0-bfbb-41c05a0bad2a" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **ripgrep_raw_search** + + +Parameters: + +```json +{ + "pattern": "MessageBoxManager.GetMessageBoxStandard.*Error", + "caseInsensitive": false +} +``` +Result: + +```json +{ + "success": { + "pattern": "MessageBoxManager.GetMessageBoxStandard.*Error", + "outputMode": "content", + "workspaceResults": { + "c:\\Users\\Nathan\\Desktop\\UFL": { + "content": { + "matches": [ + { + "file": "Views/WorkerEditWindow.axaml.cs", + "matches": [ + { + "lineNumber": 52, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 67, + "content": " var errorBox = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + } + ] + }, + { + "file": "Views/MainWindow.axaml.cs", + "matches": [ + { + "lineNumber": 107, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 115, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 149, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 157, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 190, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 198, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 209, + "content": " var errorBox = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + } + ] + }, + { + "file": "Views\\WorkerEditWindow.axaml.cs", + "matches": [ + { + "lineNumber": 52, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 67, + "content": " var errorBox = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + } + ] + }, + { + "file": "Views\\MainWindow.axaml.cs", + "matches": [ + { + "lineNumber": 107, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 115, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 149, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 157, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 190, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 198, + "content": " var box = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + }, + { + "lineNumber": 209, + "content": " var errorBox = MessageBoxManager.GetMessageBoxStandard(\"Error\"," + } + ] + } + ], + "totalLines": 18, + "totalMatchedLines": 18 + } + } + } + } +} +``` +Status: **completed** + + +
+
+ +--- + + +Updating the attach error handler as well: + + +--- + + +
Tool use: **read_file_v2** • Read file: Views/MainWindow.axaml.cs + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Views/MainWindow.axaml.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "369728ca-7c53-4998-a737-0d3e17816714" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` +C:\Users\Nathan\Desktop\UFL\Views/ErrorDialog.axaml(19,14,19,14): Avalonia error AVLN2000: Unable to resolve suitable regular or attached property IsTextSelectionEnabled on type Avalonia.Controls:Avalonia.Controls.TextBlock Line 19, position 14. [C:\Users\Nathan\Desktop\UFL\UnifiedFarmLauncher.csproj] + +Build FAILED. + +C:\Users\Nathan\Desktop\UFL\Views/ErrorDialog.axaml(19,14,19,14): Avalonia error AVLN2000: Unable to resolve suitable regular or attached property IsTextSelectionEnabled on type Avalonia.Controls:Avalonia.Controls.TextBlock Line 19, position 14. [C:\Users\Nathan\Desktop\UFL\UnifiedFarmLauncher.csproj] + 0 Warning(s) + 1 Error(s) + +Time Elapsed 00:00:00.77 + +``` +
+
+ +--- + + +Fixing the XAML error by removing the unsupported property: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Views/ErrorDialog.axaml" +} +``` +Additional data: + +```json +{ + "codeblockId": "8b960b86-f6f6-4b5d-8947-4cbe3683d1d1" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:00.91 + +``` +
+
+ +--- + + +Created a custom scrollable error dialog. It includes: + +1. **Scrollable content area**: A `ScrollViewer` that shows all error text, including long diagnostics and log entries +2. **Monospace font**: Uses Consolas/Courier New for better readability of diagnostics and logs +3. **Text wrapping**: Wraps long lines +4. **Resizable window**: 700x500 default, with minimum 500x300 +5. **Text selection**: Users can select and copy error text + +When a worker fails to start, the error dialog will: +- Show the full error message with all diagnostics +- Display all log entries in a scrollable area +- Allow scrolling to view everything +- Enable copying text for debugging + +The dialog replaces the standard message box for worker start/stop/attach errors, so you can scroll through all diagnostic information and log entries. + +--- + +_**User**_ + +not terribly useful I'm afraid + +--- + +_**Agent (model default, mode Agent)**_ + + +The dialog is working, but the logs are empty because the controller isn't starting. Improving diagnostics to capture why `Start-Process` fails and show any PowerShell errors: + + +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +The `ensureScript` output isn't being captured. Capturing its output (including stderr) and testing the controller script execution directly: + + +--- + + +
Tool use: **read_file_v2** • Read file: Services/SshService.cs + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "c709e572-785d-4a2d-a28c-2396d987b391" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "207b0db0-11c9-491a-bb20-6260070901ed" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "d6ea7b41-b1d5-4a99-b158-8a05300b5d6b" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:00.84 + +``` +
+
+ +--- + + +Including stderr in diagnostics. Updating `SshService` to capture stderr and include it in diagnostics: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/SshService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "e67b9b42-d7cc-40f6-bf46-fd45a0400b0a" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:00.81 + +``` +
+
+ +--- + + +When you try starting the worker again, the error dialog should show: + +1. **EnsureScript output**: Any output from the ensureScript execution, including the verbose logging we added (attempting to start controller, paths, etc.) +2. **Stderr output**: Any errors written to stderr (like the `[Console]::Error.WriteLine` calls) +3. **Controller test results**: Whether the controller script can be invoked manually +4. **Enhanced diagnostics**: More detailed information about what's happening + +This should help identify why the controller PID isn't being set. The most likely causes are: +- `Start-Process` is failing silently (we'll see the error now) +- The controller script is crashing immediately (we'll see test results) +- There's a permissions or path issue (we'll see it in the verbose output) + +Try starting the worker again and share what the diagnostics show. + +--- + +_**User**_ + + + +--- + +_**Agent (model default, mode Agent)**_ + + +The test shows the controller can start (PID: 13284), but the metadata PID isn't being saved. The controller script may be overwriting the metadata on startup. Checking the controller script's initialization: + + +--- + + +
Tool use: **read_file_v2** • Read file: Scripts/remote_worker_controller.ps1 + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +The controller script overwrites the metadata on startup (line 228) with `Write-Metadata -ControllerPid $PID`. There's a race: the ensureScript sets the PID, then the controller overwrites it. Checking the `Write-Metadata` function to see if it preserves the existing ControllerPid: + + +--- + + +
Tool use: **read_file_v2** • Read file: Scripts/remote_worker_controller.ps1 + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Scripts/remote_worker_controller.ps1 + +
+
+ +--- + + +The controller's `Write-Metadata` defaults `ControllerPid` to `$PID`, which should be correct. The metadata shows an empty Controller PID, suggesting the controller may be starting before the ensureScript's write completes, or the PID isn't being preserved. Adding a small delay and ensuring the controller preserves the existing ControllerPid: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Scripts/remote_worker_controller.ps1" +} +``` +Additional data: + +```json +{ + "codeblockId": "c52992dd-3b17-439c-9edc-3e94e7695920" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "b1cfeb48-0f7d-4b31-81ec-709a36cb5900" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:00.86 + +``` +
+
+ +--- + + +Adding a small delay after the ensureScript writes metadata to ensure it's flushed to disk: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "4c785601-14ea-4f5a-a082-563ce9fd0b17" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 40 + +```bash +dotnet build --no-restore 2>&1 | Select-Object -First 40 +``` + +``` + UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:00.91 + +``` +
+
+ +--- + + +Fixed a race condition where the controller script was overwriting the metadata before the ensureScript's PID write completed. + +1. **Controller preserves existing PID**: On startup, it checks for an existing ControllerPid in metadata (set by ensureScript) and preserves it instead of overwriting. +2. **Added flush delay**: Added a 200ms delay after ensureScript writes metadata to ensure it's flushed to disk. +3. **Increased status check delay**: Increased from 2 to 3 seconds to allow more time for initialization. + +When you start the worker again, the Controller PID should appear in the diagnostics. The controller script will preserve the PID set by ensureScript, so it should be visible when checking status. + +Try starting the worker again and check if the Controller PID is now populated in the diagnostics. + +--- + +_**User**_ + +make it scroll vertically instead of horizontally. the main deal is that it's not printing anything when it says last 30 lines. + +--- + +_**Agent (model default, mode Agent)**_ + + +Fixing both issues: making the dialog scroll vertically and ensuring log entries are displayed. Checking why the log reading isn't working: + + +--- + + +
Tool use: **read_file_v2** • Read file: Views/ErrorDialog.axaml + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +Fixing both issues: preventing horizontal scrolling and ensuring log entries are displayed: + + +--- + + +
Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Views/ErrorDialog.axaml" +} +``` +Additional data: + +```json +{ + "codeblockId": "5ecadce2-3e99-42dc-a0aa-e33255b34f03" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Services/WorkerControllerService.cs" +} +``` +Additional data: + +```json +{ + "codeblockId": "7e8cbc30-2dff-4a64-9bdf-55df42a71e8c" +} +``` +Status: **loading** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** + +
+
+ +--- + + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "Views/ErrorDialog.axaml" +} +``` +Status: **loading** + + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** + +
+
+ +--- + + --- diff --git a/Scripts/remote_worker_controller.ps1 b/Scripts/remote_worker_controller.ps1 index df86fce..27f666f 100644 --- a/Scripts/remote_worker_controller.ps1 +++ b/Scripts/remote_worker_controller.ps1 @@ -224,8 +224,24 @@ function Get-PendingCommands { # endregion try { + # Check if metadata already exists with a ControllerPid (set by ensureScript) + $existingControllerPid = $null + if (Test-Path $metaPath) { + try { + $existing = Get-Content $metaPath -Raw | ConvertFrom-Json + if ($existing.ControllerPid) { + $existingControllerPid = $existing.ControllerPid + } + } catch { + # Ignore parse errors + } + } + + # Use existing ControllerPid if available, otherwise use current PID + $controllerPidToUse = if ($existingControllerPid) { $existingControllerPid } else { $PID } + # record initial state before launching worker - Write-Metadata -Status 'initializing' -WorkerPid $null -ControllerPid $PID -Restarts 0 + Write-Metadata -Status 'initializing' -WorkerPid $null -ControllerPid $controllerPidToUse -Restarts 0 $resolvedPayloadBase64 = Resolve-PayloadBase64 $PayloadBase64 = $resolvedPayloadBase64 diff --git a/Services/SshService.cs b/Services/SshService.cs index be72b56..f813112 100644 --- a/Services/SshService.cs +++ b/Services/SshService.cs @@ -275,10 +275,28 @@ namespace UnifiedFarmLauncher.Services if (process.ExitCode != 0 && !interactive) { - throw new InvalidOperationException($"SSH script execution failed with exit code {process.ExitCode}: {error}"); + var errorText = error.ToString(); + var combinedOutput = output.ToString(); + if (!string.IsNullOrWhiteSpace(errorText)) + { + combinedOutput = string.IsNullOrWhiteSpace(combinedOutput) + ? errorText + : $"{combinedOutput}\n\n=== STDERR ===\n{errorText}"; + } + throw new InvalidOperationException($"SSH script execution failed with exit code {process.ExitCode}: {combinedOutput}"); } - return output.ToString(); + // Include stderr in output if present (for diagnostics) + var result = output.ToString(); + var stderrText = error.ToString(); + if (!string.IsNullOrWhiteSpace(stderrText) && !interactive) + { + result = string.IsNullOrWhiteSpace(result) + ? stderrText + : $"{result}\n\n=== STDERR ===\n{stderrText}"; + } + + return result; } public async Task GetWorkerBasePathAsync(WorkerConfig worker) diff --git a/Services/WorkerControllerService.cs b/Services/WorkerControllerService.cs index c4049f2..de82f37 100644 --- a/Services/WorkerControllerService.cs +++ b/Services/WorkerControllerService.cs @@ -1,7 +1,10 @@ using System; using System.IO; +using System.Linq; using System.Reflection; using System.Text; +using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using UnifiedFarmLauncher.Models; @@ -325,13 +328,356 @@ if ($shouldStart) {{ '-PayloadBase64Path',""$payloadBase64Path"" ) - Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden | Out-Null - Write-Host ""Worker $workerName started under controller."" -ForegroundColor Green + Write-Host ""Attempting to start controller with: $psExe"" -ForegroundColor Cyan + Write-Host ""Controller path: $controllerPath"" -ForegroundColor Cyan + Write-Host ""Arguments: $($controllerArgs -join ' ')"" -ForegroundColor Cyan + + try {{ + $controllerProcess = Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden -PassThru -ErrorAction Stop + if ($controllerProcess) {{ + $controllerPid = $controllerProcess.Id + Write-Host ""Controller process started with PID: $controllerPid"" -ForegroundColor Green + + # Give it a moment to initialize + Start-Sleep -Milliseconds 500 + + # Verify process is still running + $stillRunning = Get-Process -Id $controllerPid -ErrorAction SilentlyContinue + if (-not $stillRunning) {{ + throw ""Controller process (PID: $controllerPid) exited immediately after start"" + }} + + # Update metadata with controller PID + $updatedMeta = [pscustomobject]@{{ + WorkerName = $workerName + WorkerType = $workerType + Status = 'launching' + ControllerPid = $controllerPid + WorkerPid = $null + Restarts = 0 + LastExitCode = $null + LogPath = $logPath + CommandPath = $commandPath + PayloadPath = $payloadPath + UpdatedAtUtc = (Get-Date).ToUniversalTime() + }} | ConvertTo-Json -Depth 5 + $updatedMeta | Set-Content -Path $metaPath -Encoding UTF8 + + # Ensure file is flushed to disk before controller reads it + Start-Sleep -Milliseconds 200 + + Write-Host ""Worker $workerName started under controller (PID: $controllerPid)."" -ForegroundColor Green + }} else {{ + throw ""Start-Process returned null - controller failed to start"" + }} + }} catch {{ + $errorMsg = $_.Exception.Message + $errorDetails = $_.Exception.ToString() + Write-Host ""Failed to start controller: $errorMsg"" -ForegroundColor Red + Write-Host ""Error details: $errorDetails"" -ForegroundColor Red + [Console]::Error.WriteLine(""Controller startup error: $errorMsg"") + [Console]::Error.WriteLine(""Full error: $errorDetails"") + + # Update metadata with error + $errorMeta = [pscustomobject]@{{ + WorkerName = $workerName + WorkerType = $workerType + Status = 'error' + ControllerPid = $null + WorkerPid = $null + Restarts = 0 + LastExitCode = 1 + LogPath = $logPath + CommandPath = $commandPath + PayloadPath = $payloadPath + UpdatedAtUtc = (Get-Date).ToUniversalTime() + ErrorMessage = ""Controller startup failed: $errorMsg"" + }} | ConvertTo-Json -Depth 5 + $errorMeta | Set-Content -Path $metaPath -Encoding UTF8 + throw + }} }} "; // Pipe script through stdin to avoid command line length limits - await _sshService.ExecuteRemoteScriptAsync(worker, ensureScript); + string? ensureScriptOutput = null; + try + { + ensureScriptOutput = await _sshService.ExecuteRemoteScriptAsync(worker, ensureScript); + // Log any output from ensureScript for debugging + if (!string.IsNullOrWhiteSpace(ensureScriptOutput)) + { + System.Diagnostics.Debug.WriteLine($"EnsureScript output: {ensureScriptOutput}"); + } + } + catch (Exception ex) + { + // If ensureScript fails, include that in diagnostics + System.Diagnostics.Debug.WriteLine($"EnsureScript execution error: {ex.Message}"); + // Don't throw here - let the status check below handle it with full diagnostics + } + + // Wait a moment for the controller to start and update metadata + // Give extra time for the metadata write to complete + await Task.Delay(3000); + + // Verify the worker actually started by checking metadata + // Try up to 3 times with delays to account for slower startup + WorkerStatus? status = null; + for (int i = 0; i < 3; i++) + { + status = await GetWorkerStatusAsync(worker, workerType); + if (status != null && (status.Status == "running" || status.Status == "launching")) + { + break; + } + if (i < 2) // Don't delay on last attempt + { + await Task.Delay(1000); + } + } + + if (status == null || (status.Status != "running" && status.Status != "launching")) + { + // Gather comprehensive diagnostic information + var diagnostics = await GatherDiagnosticsAsync(worker, workerType); + var errorMessage = status?.ErrorMessage ?? "Worker failed to start. Check logs for details."; + var statusText = status?.Status ?? "unknown"; + + // Include ensureScript output if available + var ensureOutputInfo = string.IsNullOrWhiteSpace(ensureScriptOutput) + ? "" + : $"\n\n=== EnsureScript Output ===\n{ensureScriptOutput}"; + + throw new InvalidOperationException($"Worker did not start successfully. Status: {statusText}. {errorMessage}\n\n{diagnostics}{ensureOutputInfo}"); + } + } + + private async Task GetWorkerStatusAsync(WorkerConfig worker, string workerType) + { + var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker); + var instanceRoot = Path.Combine(remoteBasePath, workerType, worker.Name); + var metaPath = Path.Combine(instanceRoot, "state", "worker-info.json"); + + var script = $@" +$ProgressPreference = 'SilentlyContinue' +$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers' +$instanceRoot = Join-Path (Join-Path $dataRoot '{workerType}') '{worker.Name}' +$metaPath = Join-Path $instanceRoot 'state\worker-info.json' +if (Test-Path $metaPath) {{ + try {{ + $meta = Get-Content $metaPath -Raw | ConvertFrom-Json + $meta | ConvertTo-Json -Depth 5 -Compress + }} catch {{ + Write-Error ""Error reading metadata: $($_.Exception.Message)"" + }} +}} else {{ + Write-Error ""Metadata file not found at $metaPath"" +}} +"; + + try + { + var output = await _sshService.ExecuteRemoteScriptAsync(worker, script); + if (string.IsNullOrWhiteSpace(output)) + return null; + + var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + // Look for JSON line - it might be on a single line or multiple lines + var jsonLines = lines.Where(line => line.Trim().StartsWith("{")).ToList(); + + string? jsonText = null; + if (jsonLines.Count > 0) + { + // Use the first JSON line found + jsonText = jsonLines[0].Trim(); + } + else + { + // Try to find JSON that might span multiple lines + var fullText = string.Join("", lines); + var startIdx = fullText.IndexOf('{'); + if (startIdx >= 0) + { + var endIdx = fullText.LastIndexOf('}'); + if (endIdx > startIdx) + { + jsonText = fullText.Substring(startIdx, endIdx - startIdx + 1); + } + } + } + + if (string.IsNullOrEmpty(jsonText)) + return null; + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + return JsonSerializer.Deserialize(jsonText, options); + } + catch (Exception ex) + { + // Log the exception for debugging but return null + System.Diagnostics.Debug.WriteLine($"Error parsing worker status: {ex.Message}"); + return null; + } + } + + private async Task GatherDiagnosticsAsync(WorkerConfig worker, string workerType) + { + var diagnostics = new StringBuilder(); + + var script = $@" +$ProgressPreference = 'SilentlyContinue' +$ErrorActionPreference = 'Continue' +$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers' +$instanceRoot = Join-Path (Join-Path $dataRoot '{workerType}') '{worker.Name}' +$metaPath = Join-Path $instanceRoot 'state\worker-info.json' +$logPath = Join-Path $instanceRoot 'logs\worker.log' +$controllerPath = Join-Path $dataRoot 'controller.ps1' + +Write-Host ""=== Diagnostics ==="" -ForegroundColor Cyan +Write-Host ""Metadata file exists: $((Test-Path $metaPath))"" +Write-Host ""Log file exists: $((Test-Path $logPath))"" +Write-Host ""Controller script exists: $((Test-Path $controllerPath))"" + +# Check controller script content +if (Test-Path $controllerPath) {{ + $controllerSize = (Get-Item $controllerPath).Length + Write-Host ""Controller script size: $controllerSize bytes"" + $firstLine = Get-Content $controllerPath -First 1 -ErrorAction SilentlyContinue + if ($firstLine) {{ + Write-Host ""Controller script first line: $firstLine"" + }} + + # Try to test if controller script can be parsed/executed + Write-Host ""`n=== Testing Controller Script ==="" -ForegroundColor Cyan + try {{ + $testResult = powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command ""& {{ Get-Command -Name '$controllerPath' -ErrorAction Stop }}"" 2>&1 + Write-Host ""Controller script syntax check: Passed"" + }} catch {{ + Write-Host ""Controller script syntax check failed: $($_.Exception.Message)"" -ForegroundColor Red + }} + + # Check if we can manually invoke the controller with test parameters + $testParams = @('-WorkerName', 'TEST', '-WorkerType', 'sheepit', '-PayloadBase64Path', 'C:\temp\test.b64') + Write-Host ""Attempting to test controller invocation..."" + try {{ + $testProcess = Start-Process -FilePath powershell -ArgumentList @('-NoLogo', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ""$controllerPath"", '-WorkerName', 'TEST') -PassThru -WindowStyle Hidden -ErrorAction Stop + Start-Sleep -Milliseconds 200 + if ($testProcess.HasExited) {{ + Write-Host ""Test controller process exited with code: $($testProcess.ExitCode)"" -ForegroundColor Yellow + }} else {{ + Write-Host ""Test controller process started successfully (PID: $($testProcess.Id))"" -ForegroundColor Green + Stop-Process -Id $testProcess.Id -Force -ErrorAction SilentlyContinue + }} + }} catch {{ + Write-Host ""Test controller invocation failed: $($_.Exception.Message)"" -ForegroundColor Red + Write-Host ""Full error: $($_.Exception.ToString())"" -ForegroundColor Red + }} +}} + +if (Test-Path $metaPath) {{ + try {{ + $meta = Get-Content $metaPath -Raw | ConvertFrom-Json + Write-Host ""Metadata Status: $($meta.Status)"" + Write-Host ""Controller PID: $($meta.ControllerPid)"" + Write-Host ""Worker PID: $($meta.WorkerPid)"" + if ($meta.ErrorMessage) {{ + Write-Host ""Error Message: $($meta.ErrorMessage)"" -ForegroundColor Red + }} + }} catch {{ + Write-Host ""Error reading metadata: $($_.Exception.Message)"" -ForegroundColor Red + Write-Host ""Metadata file content (first 500 chars):"" -ForegroundColor Yellow + $metaContent = Get-Content $metaPath -Raw -ErrorAction SilentlyContinue + if ($metaContent) {{ + Write-Host $metaContent.Substring(0, [Math]::Min(500, $metaContent.Length)) + }} + }} +}} else {{ + Write-Host ""Metadata file not found - controller may not have started"" -ForegroundColor Yellow +}} + +# Check if controller process is running +$controllerRunning = $false +$controllerProcessInfo = ""N/A"" +if (Test-Path $metaPath) {{ + try {{ + $meta = Get-Content $metaPath -Raw | ConvertFrom-Json + if ($meta.ControllerPid) {{ + $proc = Get-Process -Id $meta.ControllerPid -ErrorAction SilentlyContinue + $controllerRunning = ($null -ne $proc) + if ($controllerRunning) {{ + $controllerProcessInfo = ""Running (PID: $($meta.ControllerPid))"" + }} else {{ + $controllerProcessInfo = ""Not running (PID: $($meta.ControllerPid) - process not found)"" + }} + }} else {{ + $controllerProcessInfo = ""No controller PID in metadata"" + }} + }} catch {{ + $controllerProcessInfo = ""Error checking: $($_.Exception.Message)"" + }} +}} else {{ + $controllerProcessInfo = ""Metadata file not found"" +}} +Write-Host ""Controller process: $controllerProcessInfo"" + +Write-Host ""`n=== Recent Log Entries (last 30 lines) ==="" -ForegroundColor Cyan +if (Test-Path $logPath) {{ + try {{ + $logFileInfo = Get-Item $logPath -ErrorAction SilentlyContinue + if ($logFileInfo) {{ + Write-Host ""Log file size: $($logFileInfo.Length) bytes"" -ForegroundColor Gray + }} + + $logContent = Get-Content $logPath -ErrorAction SilentlyContinue + if ($logContent) {{ + $logLines = if ($logContent.Count -gt 30) {{ $logContent[-30..-1] }} else {{ $logContent }} + Write-Host ""Found $($logLines.Count) log lines (showing last $([Math]::Min(30, $logLines.Count)))"" + $logLines | ForEach-Object {{ Write-Host $_ }} + }} else {{ + Write-Host ""Log file exists but appears to be empty or unreadable"" -ForegroundColor Yellow + Write-Host ""Attempting to read raw bytes..."" -ForegroundColor Gray + try {{ + $rawBytes = [IO.File]::ReadAllBytes($logPath) + Write-Host ""Log file contains $($rawBytes.Length) bytes"" + if ($rawBytes.Length -gt 0) {{ + $text = [System.Text.Encoding]::UTF8.GetString($rawBytes) + Write-Host ""Log content (first 500 chars):"" -ForegroundColor Cyan + Write-Host $text.Substring(0, [Math]::Min(500, $text.Length)) + }} + }} catch {{ + Write-Host ""Could not read log file bytes: $($_.Exception.Message)"" -ForegroundColor Red + }} + }} + }} catch {{ + Write-Host ""Error reading log: $($_.Exception.Message)"" -ForegroundColor Red + Write-Host ""Stack trace: $($_.ScriptStackTrace)"" -ForegroundColor DarkRed + }} +}} else {{ + Write-Host ""Log file does not exist yet at: $logPath"" -ForegroundColor Yellow +}} +"; + + try + { + var output = await _sshService.ExecuteRemoteScriptAsync(worker, script); + return output.Trim(); + } + catch (Exception ex) + { + return $"Unable to gather diagnostics: {ex.Message}"; + } + } + + private class WorkerStatus + { + public string? Status { get; set; } + public int? WorkerPid { get; set; } + public string? ErrorMessage { get; set; } } public async Task StopWorkerAsync(WorkerConfig worker, string workerType) diff --git a/Views/ErrorDialog.axaml b/Views/ErrorDialog.axaml new file mode 100644 index 0000000..e32dbed --- /dev/null +++ b/Views/ErrorDialog.axaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + +