feat(web): detect agent-spawned local servers and surface in sidebar#2241
feat(web): detect agent-spawned local servers and surface in sidebar#2241Marve10s wants to merge 11 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review This PR introduces a new feature with significant scope: local server detection, process control capabilities (including process termination), new RPC endpoints, UI components, and polling infrastructure. New features of this scale with process-killing capabilities warrant human review. You can customize Macroscope's approvability policy. Learn more. |
2026-04-20.20.27.24.mov |
@juliusmarminge There are a lot of use cases when I can't understand if Agent run the server simply, without checking all commands and tool calls. I often ask them to do since it's easier to run it like this than run multiple different worktrees and codebases manually |
|
interesting solution. I've played with this long time ago (#43) but never found a reliable way to identify the ports 😭 |
Oh, I never saw that PR. I'm resolving the conflicts and comments from AI reviewers. Can take a look at PR 43, might be useful or close that, idk. Do you think you'd have time to check my other PRs ? There are a lot of UX improvements and Gemini CLI that was merged to DPcode. Seems like working fine |
da93f3e to
63a32c6
Compare
63a32c6 to
7875a07
Compare
Adds end-to-end visibility into localhost servers that coding agents start through any tool (terminal pty, Bash background tasks, Monitor tail, etc). The sidebar now shows a pulsing terminal icon when a process is live, and clicking it opens a redesigned dialog listing detected URLs with per-port Open/Stop controls. Server - New `localProcesses.probePorts` RPC and `LocalProcessProbePorts*` contracts that reuse the existing `lsof` / `Get-NetTCPConnection` infrastructure to report listener state without killing anything. - ProviderRuntimeIngestion now buffers `command_output` deltas per thread/turn/item and attaches a `commandActivity` payload (with parsed localhost URLs) to `tool.updated` and `tool.completed` activities. Shared `toolActivity` helpers extract the URLs. Web - `deriveSidebarAgentCommandStatus` scans every `tool.completed` activity on the latest turn so URLs surfaced via Monitor/tail/log tools are captured (not just `command_execution`). - New `useListeningPortProbe` hook ref-counts probe requests per environment and polls `localProcesses.probePorts` every 5s, so rows only render the live icon when a port is actually listening. - `AgentCommandStatusIcon` (sidebar) emerald + pulse when live; click opens the redesigned dialog. - Dialog redesign: subtle backdrop matching the command palette, single URL list with per-row Open + Stop, "Also listening" strip for orphan ports. Stops are scoped to a single port so an agent running multiple servers can have one killed at a time. - `DialogPopup` gains `forceBackdrop` to bypass Base UI's nested- dialog backdrop suppression when opened from the sidebar stack. - Work-log URL chip restyled to neutral pill with sky pulse + arrow. - Dismiss state persisted per thread/status; suppression is bypassed while a process is still observed live.
Address three Cursor reviewer comments on 7875a07: - ChatView: pass `activeLatestTurn?.turnId` into `deriveWorkLogEntries` again. The `undefined` slipped in during rebase resolution and broadened the chat work log across every turn instead of the active one. - session-logic: run `extractToolCommandActivity` for non-command tools so the timeline picks up localhost URLs and `outputPreview` from Monitor/tail tools, matching what `deriveSidebarAgentCommandStatus` already does. `commandPreview` stays narrow (via `extractToolCommand`) for non-command tools so details like `/tmp/app.ts` aren't inferred as commands. - toolActivity: normalize `127.0.0.1` and `[::1]` (along with `0.0.0.0`) to `localhost` for href-based dedup so the same server printed under multiple loopback hosts collapses to a single chip. The visible `url` preserves the original spelling.
`deriveSidebarAgentCommandStatus` only emits a status when at least one localhost URL is detected, so the only label it ever returns is "Agent local URL detected" — the "Agent ran command" union member and the matching runtime fallbacks were unreachable. - types: narrow `SidebarAgentCommandStatus.label` to the literal that's actually produced. - Sidebar dialog title: collapse the dead `?? "Agent ran command"` fallback; pick the title from `isRunning` only. - ThreadStatusIndicators tooltip: drop the unreachable `!isRunning && !hasLocalUrl` branch.
…tick `writeShellStreamThread` compared the stored sidebar summary (which has a non-null computed `agentCommandStatus`) against `nextThread.summary` straight off the shell stream — but the server always sets `agentCommandStatus: null`. For any thread with an active URL detection the equality check could never succeed, so the block ran on every shell tick: it rebuilt the summary, recomputed the same status, and stored a new object reference even though nothing changed. That cascaded into unnecessary Zustand notifications and sidebar re-renders. Compute the desired summary first (via `withSidebarAgentCommandStatus`) and compare that to the stored one. When activities haven't changed the new check returns true and the no-op write is skipped, breaking the loop without changing the contract that activities own `agentCommandStatus` and `withSidebarAgentCommandStatus` derives it.
70e4d57 to
4869630
Compare
| } | ||
| return /(?:^|\s)(?:(?:bun|npm|pnpm|yarn|npx|node|deno|python|python3|ruby|go|cargo|make|cmake|docker|git|bash|sh|zsh|uv|tsx|ts-node|turbo|vite|next|astro|remix)\b|\.\/)[\s\w./:=@-]*/iu.test( | ||
| trimmed, | ||
| ); |
There was a problem hiding this comment.
Regex matches common English words as executable names
Low Severity
The isSafeCommandFallback regex includes short executable names like go, make, next, and sh that are also common English words. Because the pattern only requires a word boundary (\b) after the match, natural-language tool details such as "The next step is to refactor" or "Just go ahead and deploy" would pass the check and be incorrectly extracted as commands. This is used as a last-resort fallback when no structured command data exists, so the blast radius is limited, but it can produce misleading command labels in the work log timeline.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 872a84e. Configure here.
| const primaryUrl = urls[0]; | ||
| if (!newestLocalUrlCommand || !primaryUrl) { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Redundant guard after filtering guarantees non-empty result
Low Severity
The null check if (!newestLocalUrlCommand || !primaryUrl) on the results of completedCommands.at(-1) and urls[0] is redundant — the early return at line 546 already guarantees completedCommands is non-empty and every entry has at least one URL with a truthy href (per the filter at line 555). The guard silently masks future regressions where the dedup filter might accidentally discard all URLs, returning null instead of surfacing the underlying inconsistency.
Reviewed by Cursor Bugbot for commit 872a84e. Configure here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e022a8e. Configure here.
| } | ||
| } | ||
| return listening; | ||
| }, [environmentId, portsKey, statusMap]); |
There was a problem hiding this comment.
Port probe creates new Set on every tick causing rerenders
Medium Severity
The useListeningPortProbe hook's final useMemo produces a new Set reference every time statusMap changes, which happens on every 5-second probe tick for the environment — even when the specific ports haven't changed state. Because this hook runs inside the memoized SidebarThreadRow, every thread row with candidate ports re-renders every 5 seconds. The applyProbe store action replaces the entire environment's PortStatusMap object reference (via { ...previous }) whenever any port in that environment changes, so all subscribers re-render.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit e022a8e. Configure here.


Closes #1298
Summary
End-to-end visibility into localhost servers that coding agents start through any tool — terminal pty, Bash background tasks, Monitor tail, etc. The sidebar shows a pulsing terminal icon when a process is live, and clicking it opens a redesigned dialog with per-port Open and Stop controls.
Why
t3code's sidebar previously only tracked subprocesses spawned through its own pty. When an agent backgrounds
pnpm devviaBash run_in_background: trueand tails it withMonitor, the dev server is real and listening — but the sidebar was blind to it. This PR closes that gap.What changed
Server
localProcesses.probePortsRPC +LocalProcessProbePorts*contracts. Reuses existinglsof/Get-NetTCPConnectionto report listener state without killing.ProviderRuntimeIngestionbufferscommand_outputdeltas per(thread, turn, item)and attaches acommandActivitypayload (with parsed localhost URLs) totool.updated/tool.completed. SharedtoolActivityhelpers extract the URLs.Web
deriveSidebarAgentCommandStatusscans everytool.completedactivity on the latest turn so URLs surfaced via Monitor/tail/log tools are captured (not justcommand_execution).useListeningPortProbe(environmentId, ports)hook: ref-counts probe requests per environment and polls every 5s. The sidebar only renders the live icon when a port is actually listening.AgentCommandStatusIcon(sidebar): emerald + pulsing when live; clicking opens the dialog.bg-background/60).DialogPopupgainsforceBackdropso the backdrop renders even when Base UI considers this dialog nested under the sidebar's menu stack.Note on diff size
The branch is currently based on
feat/checkout-dirty-worktree-error-handling(open as #1785) for local context, so this PR's diff includes those commits until #1785 lands. The 30 files specific to this work are listed above; happy to rebase ontomainonce #1785 merges, or sooner if preferred.Note
Medium Risk
Adds new WebSocket RPCs that execute
lsof/PowerShell and send SIGTERM to listening PIDs, plus client-side polling every 5s; mistakes could impact performance or terminate unintended local processes (mitigated by PID/port validation and refusing to kill the current server PID).Overview
Detects and surfaces agent-started localhost servers end-to-end, including those started outside the built-in terminal, by extracting localhost URLs from tool/command output and showing a live status indicator in the sidebar.
On the server, introduces a
localProcessesmodule (probe + stop) backed bylsof/PowerShell with newlocalProcess.probePorts/localProcess.stopPortsWS RPCs and contracts, and enhances runtime ingestion to buffercommand_outputdeltas (capped) and attach a normalizedcommandActivity(command, output preview, extracted localhost URLs) to relevant tool lifecycle activities.On the web app, adds
agentCommandStatustoSidebarThreadSummary, derives it from latest-turn activities, polls candidate ports via a shareduseListeningPortProbestore, and updates the sidebar/timeline UI with an interactive status icon + dialog to open URLs and stop listeners; dismissal of stale badges is persisted in UI state and bypassed while a process is still observed live.Reviewed by Cursor Bugbot for commit e022a8e. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Detect agent-spawned local servers and surface them in the sidebar with controls to probe and stop ports
SidebarAgentCommandStatusderived from tool activity in the latest turn, capturing detected localhost URLs and command context. Sidebar thread rows show anAgentCommandStatusIconand open anAgentCommandStatusDialogfor inspecting and stopping detected processes.useListeningPortProbe, a React hook that periodically pollslocalProcesses.probePortsto check whether candidate ports are actively listening, suppressing stale dismissed statuses when no live process is found.probeLocalPortsandstopLocalPortsserver utilities (lsof on Unix, PowerShell on Windows) exposed as WebSocket RPC methods and wired through toEnvironmentApi.localProcesses.normalizeCommandActivityPayloadin the sharedtoolActivitypackage to extract localhost URLs, output previews, and unwrapped shell commands from a broader set of tool completions.command_outputdeltas per item in the provider ingestion layer and attaches them totool.completedactivities for URL extraction.uiStateStoreso dismissed banners are not re-shown unless a live subprocess is detected.Macroscope summarized e022a8e.
Refs #216