native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
11 kB · 286 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287import AppKitimport Foundationimport GhosttyKitimport Networkimport Observation
@MainActor@Observablefinal class MirrorReplica { private(set) var usesStyledScrollback = false private(set) var view: GhosttySurfaceView? private(set) var displaySize = CGSize(width: 800, height: 600) var onMessage: ((MirrorMessage) -> Void)? var onFailure: ((String) -> Void)? @ObservationIgnored private var listener: NWListener? @ObservationIgnored private var peer: MirrorRelayConnection? @ObservationIgnored private var candidate: MirrorRelayConnection? @ObservationIgnored private let runtime: GhosttyRuntime @ObservationIgnored private let clock: any Clock<Duration> @ObservationIgnored private var parseTimeout: Task<Void, Never>? @ObservationIgnored private let token = UUID().uuidString + UUID().uuidString @ObservationIgnored private var pending: MirrorMessage? @ObservationIgnored private var pendingScrollback: MirrorStyledScrollback? @ObservationIgnored private var pendingViewportText: String? @ObservationIgnored private var displayedScrollback: MirrorStyledScrollback? @ObservationIgnored private var expectedViewportText: String? @ObservationIgnored private var presentationPaused = false @ObservationIgnored private var presentationTask: Task<Void, Never>? @ObservationIgnored private var presentationMarker: String? @ObservationIgnored private var displayedMessage: MirrorMessage? @ObservationIgnored private var stopped = false @ObservationIgnored private var needsRestart = false
init(runtime: GhosttyRuntime, clock: any Clock<Duration> = ContinuousClock()) { self.runtime = runtime self.clock = clock }
func start() throws { if needsRestart { stop() } guard listener == nil, peer == nil, view == nil else { return } stopped = false let parameters = NWParameters.tcp parameters.requiredLocalEndpoint = .hostPort(host: .ipv4(.loopback), port: .any) let listener = try NWListener(using: parameters) self.listener = listener listener.stateUpdateHandler = { [weak self, weak listener] state in Task { @MainActor in guard let self, !self.stopped, self.listener === listener else { return } switch state { case .ready: guard let port = listener?.port, let executable = ProwlPaths.bundledMirrorRelayURL, FileManager.default.isExecutableFile(atPath: executable.path) else { self.fail(String(localized: "Cannot start display replica.")) return } let command = "'\(executable.path.replacing("'", with: "'\\''"))' \(port.rawValue) \(self.token)" self.view = GhosttySurfaceView( runtime: self.runtime, workingDirectory: nil, context: GHOSTTY_SURFACE_CONTEXT_WINDOW, command: command) self.view?.bridge.consumeWorkingDirectory = { [weak self] path in guard let self, path == self.presentationMarker else { return false } self.didParseFrame() return true } // Ghostty can also derive a title from OSC 7. Do not expose private markers. self.view?.bridge.consumeTitle = { [weak self] title in guard let self else { return false } return title.hasPrefix("/prowl-replica-\(self.token)-") } case .failed(let error): self.fail(error.localizedDescription) default: break } } } listener.newConnectionHandler = { [weak self] connection in Task { @MainActor in self?.accept(connection) } } listener.start(queue: .main) }
func display( _ message: MirrorMessage, styledScrollback: MirrorStyledScrollback? = nil, viewportText: String? = nil ) { guard let frame = message.frame, frame.columns >= 1, frame.columns <= 1000, frame.rows >= 1, frame.rows <= 1000 else { onFailure?(String(localized: "Invalid Host terminal dimensions.")) return } view?.mirrorGrid = (frame.columns, frame.rows) view?.updateSurfaceSize() if let surface = view?.surface { let size = ghostty_surface_size(surface) let scale = view?.window?.backingScaleFactor ?? NSScreen.main?.backingScaleFactor ?? 2 displaySize = CGSize( width: CGFloat(size.width_px) / scale, height: CGFloat(size.height_px) / scale) } if let peer, let sequence = message.sequence { guard styledScrollback?.isValid != false else { fail(String(localized: "Invalid Host viewport frame.")) return } if styledScrollback != nil || usesStyledScrollback { presentationPaused = true view?.setOcclusion(false) } displayedMessage = message displayedScrollback = styledScrollback expectedViewportText = viewportText let marker = "/prowl-replica-\(token)-\(sequence)" presentationMarker = marker parseTimeout?.cancel() let clock = clock parseTimeout = Task { @MainActor [weak self] in do { try await clock.sleep(for: .seconds(30)) } catch { return } guard let self, self.presentationMarker == marker else { return } self.fail(String(localized: "Display replica timed out while parsing a Host frame. Reconnect to try again.")) } var payload = MirrorRelayPacket.sequenceBytes(sequence) payload.append(frame.bytes) if let styledScrollback { // Establish the live input modes first, then replace the display with // the archive. The exporter omits trailing empty rows, so pad one // viewport and position from the retained buffer's start, not its end. payload.append(Data("\u{1b}[H\u{1b}[2J\u{1b}[3J\u{1b}[0m".utf8)) payload.append(styledScrollback.bytes) payload.append(Data(String(repeating: "\r\n", count: Int(frame.rows)).utf8)) payload.append(Data("\u{1b}[?25l".utf8)) } // Unlike title callbacks, OSC 7 remains available with a static title and after config reloads. payload.append(Data("\u{1b}]7;file://localhost\(marker)\u{7}".utf8)) peer.send(MirrorRelayPacket(kind: .frame, payload: payload)) } else { pending = message pendingScrollback = styledScrollback pendingViewportText = viewportText } }
private func didParseFrame() { guard let message = displayedMessage, let sequence = message.sequence, let lease = message.subscriptionID, let terminal = view?.surface else { return } presentationMarker = nil parseTimeout?.cancel() parseTimeout = nil guard let scrollback = displayedScrollback else { usesStyledScrollback = false resumeRendering() onMessage?(.acknowledge(.init(sequence: sequence, subscriptionID: lease))) return } let top = "scroll_to_top" let scroll = "scroll_page_lines:\(scrollback.rowOffset)" guard ghostty_surface_binding_action(terminal, top, UInt(top.utf8.count)), ghostty_surface_binding_action(terminal, scroll, UInt(scroll.utf8.count)) else { fail(String(localized: "Invalid Host viewport frame.")) return } // Scroll bindings enqueue IO work too. Verify after that queue advances, // rather than acknowledging the still-visible bottom of the archive. presentationTask = Task { @MainActor [weak self] in var verified = false for _ in 0..<50 { do { try await Task.sleep(for: .milliseconds(10)) } catch { return } guard let self, !self.stopped, self.displayedMessage?.sequence == sequence, let terminal = self.view?.surface, let frame = message.frame else { return } if let text = try? GhosttyMirrorPaneSource.viewportText( terminal, columns: frame.columns, rows: frame.rows, preserveRows: true), text == self.expectedViewportText { verified = true break } } guard let self, !Task.isCancelled, !self.stopped else { return } self.usesStyledScrollback = verified self.resumeRendering() self.presentationTask = nil self.onMessage?(.acknowledge(.init(sequence: sequence, subscriptionID: lease))) } }
private func resumeRendering() { guard presentationPaused else { return } presentationPaused = false view?.setOcclusion(view?.window?.occlusionState.contains(.visible) == true) }
func stop() { stopped = true parseTimeout?.cancel() parseTimeout = nil presentationTask?.cancel() presentationTask = nil needsRestart = false listener?.cancel() listener = nil peer?.close() peer = nil candidate?.close() candidate = nil view?.closeSurface() view = nil pending = nil displayedMessage = nil pendingScrollback = nil pendingViewportText = nil displayedScrollback = nil expectedViewportText = nil presentationMarker = nil presentationPaused = false usesStyledScrollback = false }
private func fail(_ reason: String) { needsRestart = true onFailure?(reason) }
private func accept(_ connection: NWConnection) { guard !stopped, candidate == nil, peer == nil else { connection.cancel() return } let candidate = MirrorRelayConnection(connection) self.candidate = candidate candidate.onPacket = { [weak self, weak candidate] packet in guard let self, let candidate, self.peer === candidate || self.candidate === candidate else { return } if self.peer == nil { // Code security: only the helper holding this replica token can forward input. guard packet.kind == .authenticate, packet.payload == Data(self.token.utf8) else { candidate.close(String(localized: "Invalid display relay.")) return } self.peer = candidate self.candidate = nil self.listener?.cancel() self.listener = nil if let pending = self.pending { self.pending = nil self.display( pending, styledScrollback: self.pendingScrollback, viewportText: self.pendingViewportText) self.pendingScrollback = nil self.pendingViewportText = nil } } else { guard let displayed = self.displayedMessage, let lease = displayed.subscriptionID else { return } switch packet.kind { case .input: self.onMessage?( .input(.init(bytes: packet.payload, subscriptionID: lease))) case .acknowledge: guard let sequence = try? MirrorRelayPacket.sequence(packet.payload), sequence <= (displayed.sequence ?? 0) else { candidate.close(String(localized: "Invalid display acknowledgement.")) return } // PTY write completion precedes parsing. The private OSC marker // above is the presentation barrier, including history positioning. default: candidate.close(String(localized: "Invalid display relay message.")) } } } candidate.onClose = { [weak self, weak candidate] error in guard let self, let candidate, !self.stopped, self.peer === candidate || self.candidate === candidate else { return } self.fail(error ?? String(localized: "Display replica disconnected.")) } candidate.start() }}