native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Remove

khoi 7384483e 495c381b

-249
-249
docs/ssh-notifications-over-ssh.md
··· 1 - # SSH Notifications Over SSH 2 - 3 - ## Goal 4 - 5 - Show Supacode notifications when a coding agent is running on a remote machine over `ssh`. 6 - 7 - ## Status 8 - 9 - This is still a design doc, not shipped behavior. 10 - 11 - The current codebase remains socket-only for agent hooks: 12 - 13 - - `AgentHookSettingsCommand` only generates Unix-socket commands 14 - - `ClaudeHookSettings` and `CodexHookSettings` only install those socket commands 15 - - there is no `SUPACODE_REMOTE_NOTIFICATIONS`, `SSH_CONNECTION`, or `SSH_TTY` transport switch in the app 16 - 17 - ## Decision 18 - 19 - This is feasible. 20 - 21 - The simplest path is to support remote notifications by emitting terminal notification escape sequences from the remote host and letting the existing local Ghostty -> Supacode notification path handle them. 22 - 23 - Structured remote hook parity with the current local socket-based integration is also feasible, but it is a larger feature and should be treated as a second phase. 24 - 25 - ## Current Supacode Behavior 26 - 27 - ### Local terminal notifications already work 28 - 29 - Supacode already receives terminal desktop notifications from Ghostty: 30 - 31 - - `ThirdParty/ghostty/src/terminal/osc.zig` 32 - - `ThirdParty/ghostty/src/terminal/osc/parsers/osc9.zig` 33 - - `ThirdParty/ghostty/src/terminal/osc/parsers/rxvt_extension.zig` 34 - - `ThirdParty/ghostty/src/Surface.zig` 35 - - `supacode/Infrastructure/Ghostty/GhosttySurfaceBridge.swift` 36 - - `supacode/Features/Terminal/Models/WorktreeTerminalState.swift` 37 - - `supacode/Features/Terminal/BusinessLogic/WorktreeTerminalManager.swift` 38 - - `supacode/Features/App/Reducer/AppFeature.swift` 39 - - `supacode/Clients/Notifications/SystemNotificationClient.swift` 40 - 41 - The flow is: 42 - 43 - 1. A process inside the terminal emits `OSC 9` or `OSC 777`. 44 - 2. Ghostty parses the escape sequence into a desktop notification action. 45 - 3. `GhosttySurfaceBridge` forwards the title/body into `WorktreeTerminalState`. 46 - 4. `WorktreeTerminalState` stores the in-app notification and emits a terminal event. 47 - 5. `AppFeature` decides whether to show a macOS system notification and/or sound. 48 - 49 - This path is transport-agnostic. If the bytes arrive through a local shell or through a remote `ssh` PTY, the local Ghostty surface sees the same terminal stream. 50 - 51 - ### Local structured coding-agent hooks do not work remotely 52 - 53 - The current Claude/Codex integrations are local-only. 54 - 55 - Each terminal surface injects: 56 - 57 - - `SUPACODE_WORKTREE_ID` 58 - - `SUPACODE_TAB_ID` 59 - - `SUPACODE_SURFACE_ID` 60 - - `SUPACODE_SOCKET_PATH` 61 - 62 - Relevant files: 63 - 64 - - `supacode/Features/Terminal/Models/WorktreeTerminalState.swift` 65 - - `supacode/Features/Settings/BusinessLogic/AgentHookSettingsCommand.swift` 66 - - `supacode/Features/Settings/BusinessLogic/ClaudeHookSettings.swift` 67 - - `supacode/Features/Settings/BusinessLogic/CodexHookSettings.swift` 68 - - `supacode/Infrastructure/AgentHookSocketServer.swift` 69 - 70 - The installed hook commands send either busy-state updates or raw JSON payloads to a local Unix domain socket under `/tmp/supacode-<uid>/pid-<pid>`. 71 - 72 - That cannot work from a remote host: 73 - 74 - - the remote process cannot access the local Unix socket 75 - - the remote shell does not automatically inherit the local `SUPACODE_*` environment 76 - - the current hook command shape assumes local socket connectivity 77 - 78 - ## Existing Signals In The Repo 79 - 80 - There are already local helpers for the terminal-notification path: 81 - 82 - - `bins/osc9-notify.sh` 83 - - `bins/osc777-notify.sh` 84 - 85 - There are already tests that verify command generation, desktop notification delivery, and deduplication behavior: 86 - 87 - - `supacodeTests/GhosttySurfaceBridgeTests.swift` 88 - - `supacodeTests/AgentBusyStateTests.swift` 89 - - `supacodeTests/AgentHookCommandTests.swift` 90 - 91 - This means the local app side is already prepared to accept terminal-originated notifications and coalesce them against richer hook notifications. 92 - 93 - ## Recommended Implementation 94 - 95 - ## Phase 1 96 - 97 - Add an SSH-safe remote notification mode that uses terminal escape sequences instead of the local Unix socket. 98 - 99 - ### Why this is the right first step 100 - 101 - - It reuses the existing app-side pipeline. 102 - - It avoids new transport infrastructure. 103 - - It solves the user-visible problem in `SUP-39`. 104 - - It keeps the change narrow and forward-only. 105 - 106 - ### Notification protocol choice 107 - 108 - Use `OSC 777` for agent hooks. 109 - 110 - Why: 111 - 112 - - `OSC 9` only gives a body in the current Ghostty path. 113 - - `OSC 777` supports both title and body. 114 - - Supacode already receives both title and body from Ghostty for desktop notifications. 115 - 116 - `OSC 9` can remain useful for smoke testing and manual scripts, but `OSC 777` is the better hook transport. 117 - 118 - ### Implementation shape 119 - 120 - Add a second hook command builder that emits terminal notifications instead of writing to the local socket. 121 - 122 - The current command-generation choke point is `AgentHookSettingsCommand`. `ClaudeHookSettings` and `CodexHookSettings` only embed those generated commands into their respective settings files, so the remote path should be added there rather than duplicated per agent. 123 - 124 - The hook command should: 125 - 126 - 1. Read the raw JSON payload from `stdin`. 127 - 2. Extract: 128 - - `title` 129 - - `message` 130 - - `last_assistant_message` 131 - - `hook_event_name` 132 - 3. Choose a title/body: 133 - - title = payload title if present, otherwise agent name 134 - - body = `message` or `last_assistant_message` 135 - 4. Sanitize terminal control characters and delimiters. 136 - 5. Emit `OSC 777`. 137 - 138 - The easiest portable implementation is a small helper script that uses `python3` to parse JSON and print the escape sequence. 139 - 140 - ### Command selection 141 - 142 - The hook installer should generate commands using this rule: 143 - 144 - - if `SUPACODE_SOCKET_PATH` exists, use the current local socket command 145 - - otherwise, if the process is running in an SSH session, use the remote OSC command 146 - 147 - Remote detection should prefer explicit signal over inference: 148 - 149 - - first: `SUPACODE_REMOTE_NOTIFICATIONS=1` 150 - - second: `SSH_CONNECTION` or `SSH_TTY` 151 - 152 - This avoids changing local behavior and keeps the new path opt-in or clearly scoped to remote sessions. 153 - 154 - ### Scope 155 - 156 - Phase 1 should only cover notifications. 157 - 158 - It should not try to preserve: 159 - 160 - - local hook busy-state updates 161 - - tab/surface targeting parity beyond the active remote terminal stream 162 - - remote install automation 163 - 164 - ### Acceptance criteria 165 - 166 - - a remote Claude/Codex hook can emit a Supacode notification over `ssh` 167 - - the local app records the notification in the worktree list 168 - - the local app can show a macOS system notification 169 - - the existing hook-vs-OSC deduplication still works 170 - - local non-SSH hook behavior is unchanged 171 - 172 - ### Tests 173 - 174 - Add tests for: 175 - 176 - - remote helper payload parsing 177 - - `OSC 777` title/body delivery through the existing bridge callback 178 - - command selection logic between local socket and remote OSC modes 179 - - sanitization of control characters 180 - 181 - ## Phase 2 182 - 183 - Add a real remote relay for structured hook parity. 184 - 185 - This is the path if Supacode needs: 186 - 187 - - busy-state rings for remote agents 188 - - richer remote targeting semantics 189 - - remote commands that land in the local socket transport instead of going through terminal escape sequences 190 - - remote installation/bootstrap owned by Supacode 191 - 192 - ### Architecture 193 - 194 - The relay shape should be: 195 - 196 - 1. Supacode starts a local authenticated relay server. 197 - 2. Supacode opens an SSH reverse tunnel back to that local relay. 198 - 3. Supacode installs remote metadata or helper wrappers on the remote host. 199 - 4. Remote hook commands talk to the tunneled relay endpoint. 200 - 5. The local relay forwards the request into the existing local Unix socket or directly into app state. 201 - 202 - ### What this unlocks 203 - 204 - - remote busy-state updates with the existing structured model 205 - - remote notifications without shell JSON parsing in the app-specific hook command 206 - - future remote agent commands beyond notifications 207 - 208 - ### Why this is not phase 1 209 - 210 - - it introduces authentication, bootstrap, lifecycle, and reconnect behavior 211 - - it is a larger surface area than the problem requires 212 - - it should be justified by remote busy-state or broader remote orchestration goals 213 - 214 - ## Risks And Open Questions 215 - 216 - ### tmux and nested terminal layers 217 - 218 - Plain `ssh` PTY delivery should work. Nested layers such as `tmux` may require validation because escape-sequence passthrough behavior depends on terminal configuration. 219 - 220 - This should be treated as a compatibility matrix item for phase 1 validation, not as a blocker to the first implementation. 221 - 222 - ### Remote helper availability 223 - 224 - The simplest JSON parser is `python3`. If remote environments without Python need to be supported, Supacode should ship a tiny helper binary or install a standalone script during remote bootstrap. 225 - 226 - ### Automatic installation on the remote host 227 - 228 - Phase 1 does not require Supacode to edit remote config files automatically. If product requirements demand a one-click remote install, that belongs with the relay/bootstrap work in phase 2. 229 - 230 - ## Proposed Work Split 231 - 232 - ### Issue 1 233 - 234 - Implement SSH-safe remote notifications via `OSC 777`. 235 - 236 - ### Issue 2 237 - 238 - Add a remote relay for structured hook parity and busy-state updates. 239 - 240 - ## Summary 241 - 242 - Supacode can show notifications over `ssh` today if the remote process emits terminal notification escape sequences. 243 - 244 - The missing piece is not the app-side notification pipeline. The missing piece is the remote integration path. 245 - 246 - The recommended order is: 247 - 248 - 1. ship SSH-safe hook notifications over `OSC 777` 249 - 2. add a real remote relay only if remote structured parity is needed