mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-26 15:07:39 +00:00
154 lines
5.8 KiB
Swift
154 lines
5.8 KiB
Swift
import Foundation
|
|
import OSLog
|
|
|
|
/// Handles terminal control messages via the unified control socket
|
|
@MainActor
|
|
final class TerminalControlHandler {
|
|
private let logger = Logger(subsystem: BundleIdentifiers.loggerSubsystem, category: "TerminalControl")
|
|
|
|
// MARK: - Singleton
|
|
|
|
static let shared = TerminalControlHandler()
|
|
|
|
// MARK: - Initialization
|
|
|
|
private init() {
|
|
// Register handler with the shared socket manager
|
|
// NOTE: System handlers (like SystemControlHandler) need to be registered separately
|
|
// since they may have different lifecycle requirements
|
|
SharedUnixSocketManager.shared.registerControlHandler(for: .terminal) { [weak self] data in
|
|
await self?.handleMessage(data)
|
|
}
|
|
|
|
logger.info("🚀 Terminal control handler initialized")
|
|
}
|
|
|
|
// MARK: - Message Handling
|
|
|
|
private func handleMessage(_ data: Data) async -> Data? {
|
|
do {
|
|
// First check what action this is
|
|
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
let action = json["action"] as? String
|
|
{
|
|
switch action {
|
|
case "spawn":
|
|
// Try to decode as terminal spawn request
|
|
if let spawnRequest = try? ControlProtocol.decodeTerminalSpawnRequest(data) {
|
|
logger
|
|
.info(
|
|
"📥 Terminal spawn request for session: \(spawnRequest.payload?.sessionId ?? "unknown")"
|
|
)
|
|
let response = await handleSpawnRequest(spawnRequest)
|
|
return try ControlProtocol.encode(response)
|
|
} else {
|
|
logger.error("Failed to decode terminal spawn request")
|
|
return createErrorResponse(for: data, error: "Invalid spawn request format")
|
|
}
|
|
|
|
default:
|
|
logger.error("Unknown terminal action: \(action)")
|
|
return createErrorResponse(for: data, error: "Unknown terminal action: \(action)")
|
|
}
|
|
} else {
|
|
logger.error("Invalid terminal message format")
|
|
return createErrorResponse(for: data, error: "Invalid message format")
|
|
}
|
|
} catch {
|
|
logger.error("Failed to process terminal message: \(error)")
|
|
return createErrorResponse(for: data, error: "Failed to process message: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
private func handleSpawnRequest(_ message: ControlProtocol.TerminalSpawnRequestMessage) async -> ControlProtocol
|
|
.TerminalSpawnResponseMessage
|
|
{
|
|
guard let payload = message.payload else {
|
|
return ControlProtocol.terminalSpawnResponse(
|
|
to: message,
|
|
success: false,
|
|
error: "Missing payload"
|
|
)
|
|
}
|
|
|
|
logger.info("Spawning terminal session \(payload.sessionId)")
|
|
|
|
do {
|
|
// If a specific terminal is requested, temporarily set it
|
|
var originalTerminal: String?
|
|
if let requestedTerminal = payload.terminalPreference {
|
|
originalTerminal = UserDefaults.standard.string(forKey: "preferredTerminal")
|
|
UserDefaults.standard.set(requestedTerminal, forKey: "preferredTerminal")
|
|
}
|
|
|
|
defer {
|
|
// Restore original terminal preference if we changed it
|
|
if let original = originalTerminal {
|
|
UserDefaults.standard.set(original, forKey: "preferredTerminal")
|
|
}
|
|
}
|
|
|
|
// Launch the terminal
|
|
try TerminalLauncher.shared.launchOptimizedTerminalSession(
|
|
workingDirectory: payload.workingDirectory ?? "",
|
|
command: payload.command ?? "",
|
|
sessionId: payload.sessionId,
|
|
vibetunnelPath: nil // Use bundled path
|
|
)
|
|
|
|
// Success response with compile-time guarantees
|
|
return ControlProtocol.terminalSpawnResponse(
|
|
to: message,
|
|
success: true
|
|
)
|
|
} catch {
|
|
logger.error("Failed to spawn terminal: \(error)")
|
|
return ControlProtocol.terminalSpawnResponse(
|
|
to: message,
|
|
success: false,
|
|
error: error.localizedDescription
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Public Methods
|
|
|
|
/// Start the terminal control handler
|
|
func start() {
|
|
// Handler is registered in init, just log that we're ready
|
|
logger.info("✅ Terminal control handler started")
|
|
}
|
|
|
|
/// Stop the terminal control handler
|
|
func stop() {
|
|
SharedUnixSocketManager.shared.unregisterControlHandler(for: .terminal)
|
|
logger.info("🛑 Terminal control handler stopped")
|
|
}
|
|
|
|
// MARK: - Error Handling
|
|
|
|
private func createErrorResponse(for data: Data, error: String) -> Data? {
|
|
do {
|
|
// Try to get request ID for proper error response
|
|
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
let id = json["id"] as? String,
|
|
let action = json["action"] as? String
|
|
{
|
|
// Create error response matching request
|
|
let errorResponse: [String: Any] = [
|
|
"id": id,
|
|
"type": "response",
|
|
"category": "terminal",
|
|
"action": action,
|
|
"error": error
|
|
]
|
|
|
|
return try JSONSerialization.data(withJSONObject: errorResponse)
|
|
}
|
|
} catch {
|
|
logger.error("Failed to create error response: \(error)")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|