Move port scanning from shell to app-side with batching (#100)
* Move port scanning from shell to app-side with batching Replace per-shell `ps -axo + lsof` scanning with a centralized PortScanner singleton in the app. Each shell now sends lightweight `report_tty` (once per session) and `ports_kick` (on preexec/precmd) socket messages. The app coalesces kicks across all panels and runs a single `ps -t <ttys> + lsof -p <pids>` covering every active panel. Also fixes a macOS 26 Tahoe regression where `getsockopt(LOCAL_PEERPID)` returns ENOTCONN on accepted sockets when the peer disconnects before the handler thread starts. This was silently breaking ALL socket commands sent via ncat --send-only. The fix captures the peer PID in the accept loop immediately after accept(), and falls back to LOCAL_PEERCRED (uid check) when the PID lookup fails. * Fix PR review feedback: burst timing and auth comment clarity - P2: burstDelays were accumulating (0.5+1.5+3+... = ~22.5s) instead of firing at absolute offsets from burst start. Now uses burstStart anchor so scans fire at 0.5s, 1.5s, 3s, 5s, 7.5s, 10s as intended. - P1: Clarify LOCAL_PEERCRED fallback rationale — same security boundary as socket file permissions (0600), does not widen attack surface. Long-lived connections still get full descendant check via LOCAL_PEERPID.
This commit is contained in:
parent
3193e602d4
commit
9642bb59fc
7 changed files with 516 additions and 158 deletions
|
|
@ -89,6 +89,7 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
@Published var gitBranch: SidebarGitBranchState?
|
||||
@Published var surfaceListeningPorts: [UUID: [Int]] = [:]
|
||||
@Published var listeningPorts: [Int] = []
|
||||
var surfaceTTYNames: [UUID: String] = [:]
|
||||
|
||||
var focusedSurfaceId: UUID? { focusedPanelId }
|
||||
var surfaceDirectories: [UUID: String] {
|
||||
|
|
@ -330,6 +331,7 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
panelDirectories = panelDirectories.filter { validSurfaceIds.contains($0.key) }
|
||||
panelTitles = panelTitles.filter { validSurfaceIds.contains($0.key) }
|
||||
surfaceListeningPorts = surfaceListeningPorts.filter { validSurfaceIds.contains($0.key) }
|
||||
surfaceTTYNames = surfaceTTYNames.filter { validSurfaceIds.contains($0.key) }
|
||||
recomputeListeningPorts()
|
||||
}
|
||||
|
||||
|
|
@ -1292,6 +1294,8 @@ extension Workspace: BonsplitDelegate {
|
|||
panelDirectories.removeValue(forKey: panelId)
|
||||
panelTitles.removeValue(forKey: panelId)
|
||||
panelSubscriptions.removeValue(forKey: panelId)
|
||||
surfaceTTYNames.removeValue(forKey: panelId)
|
||||
PortScanner.shared.unregisterPanel(workspaceId: id, panelId: panelId)
|
||||
|
||||
// Keep the workspace invariant: always retain at least one real panel.
|
||||
// This prevents runtime close callbacks from ever collapsing into a tabless workspace.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue