sketch/loop: add PortMonitor for TCP port monitoring with Agent integration
Add PortMonitor struct that uses Tailscale portlist library to monitor
open/listening TCP ports and send AgentMessage notifications to Agent
when ports are opened or closed, with cached port list access method.
When I asked Sketch to do this with the old implementation, it did
ok parsing /proc, but then it tried to conver it to ss format...
using a library seems to work ok!
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s8fc57de4b5583d34k
diff --git a/loop/agent.go b/loop/agent.go
index 8f96924..82d823e 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -30,6 +30,7 @@
"sketch.dev/llm/conversation"
"sketch.dev/mcp"
"sketch.dev/skabandclient"
+ "tailscale.com/portlist"
)
const (
@@ -155,6 +156,9 @@
// SkabandAddr returns the skaband address if configured
SkabandAddr() string
+
+ // GetPorts returns the cached list of open TCP ports
+ GetPorts() []portlist.Port
}
type CodingAgentMessageType string
@@ -168,6 +172,7 @@
CommitMessageType CodingAgentMessageType = "commit" // for displaying git commits
AutoMessageType CodingAgentMessageType = "auto" // for automated notifications like autoformatting
CompactMessageType CodingAgentMessageType = "compact" // for conversation compaction notifications
+ PortMessageType CodingAgentMessageType = "port" // for port monitoring events
cancelToolUseMessage = "Stop responding to my previous message. Wait for me to ask you something else before attempting to use any more tools."
)
@@ -430,6 +435,8 @@
gitOrigin string
// MCP manager for handling MCP server connections
mcpManager *mcp.MCPManager
+ // Port monitor for tracking TCP ports
+ portMonitor *PortMonitor
// Time when the current turn started (reset at the beginning of InnerLoop)
startOfTurn time.Time
@@ -654,6 +661,14 @@
func (a *Agent) URL() string { return a.url }
+// GetPorts returns the cached list of open TCP ports.
+func (a *Agent) GetPorts() []portlist.Port {
+ if a.portMonitor == nil {
+ return nil
+ }
+ return a.portMonitor.GetPorts()
+}
+
// BranchName returns the git branch name for the conversation.
func (a *Agent) BranchName() string {
return a.gitState.BranchName(a.config.BranchPrefix)
@@ -1070,6 +1085,10 @@
mcpManager: mcp.NewMCPManager(),
}
+
+ // Initialize port monitor with 5-second interval
+ agent.portMonitor = NewPortMonitor(agent, 5*time.Second)
+
return agent
}
@@ -1522,11 +1541,23 @@
}
func (a *Agent) Loop(ctxOuter context.Context) {
+ // Start port monitoring
+ if a.portMonitor != nil && a.IsInContainer() {
+ if err := a.portMonitor.Start(ctxOuter); err != nil {
+ slog.WarnContext(ctxOuter, "Failed to start port monitor", "error", err)
+ } else {
+ slog.InfoContext(ctxOuter, "Port monitor started")
+ }
+ }
+
// Set up cleanup when context is done
defer func() {
if a.mcpManager != nil {
a.mcpManager.Close()
}
+ if a.portMonitor != nil && a.IsInContainer() {
+ a.portMonitor.Stop()
+ }
}()
for {