Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
de48830
implement the remote side of the termlisten protocol
sawka Apr 27, 2026
1e14a37
updates for wshcmdreader to support multiple osc sequences
sawka Apr 27, 2026
868470f
md formatting
sawka Apr 27, 2026
3d442dc
termlistensrv + working http integration test
sawka Apr 27, 2026
507a988
implement termproxy support in tsunami command
sawka Apr 27, 2026
bb8ba32
default raw tsunami binaries to termproxy, all wave paths disable it …
sawka Apr 27, 2026
50ee4e3
commit generated file update
sawka Apr 27, 2026
6fb3ffc
integrate server side via ptybuffer
sawka Apr 27, 2026
003d073
checkpoint, got tsunami sub-block and client<->server signaling working
sawka Apr 27, 2026
6d7790c
fix some bugs (more remaining)
sawka Apr 27, 2026
b335043
checkpoint, fixing bugs
sawka Apr 28, 2026
4db2737
fix bugs, term mode switcher, preload changes, etc.
sawka Apr 28, 2026
7945bd1
fix manifest for pre-build binaries
sawka Apr 28, 2026
802f928
meta sync + header
sawka May 1, 2026
f61eb14
better global keybindngs for builder window
sawka May 1, 2026
c7480fb
small change to publish dialog
sawka May 1, 2026
e96a78b
update to gpt-5.5 for builder
sawka May 1, 2026
a947688
fix header icons for tsunami sub-blocks
sawka May 1, 2026
5aea889
fix tsunami sub-block menu items
sawka May 1, 2026
17e636d
show app name in header in tsunami blocks
sawka May 1, 2026
db626d0
simplify, use tsunamidirect
sawka May 2, 2026
5925b66
update copyright years
sawka May 2, 2026
cd36896
more simplifications to tsunami now that we have tsunamidirect
sawka May 2, 2026
14bd2d0
move allowtermlisten to a global config setting
sawka May 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
implement termproxy support in tsunami command
  • Loading branch information
sawka committed Apr 27, 2026
commit 507a988a88be9f95423320bcd74cbdb5a9f840e1
13 changes: 12 additions & 1 deletion tsunami/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ type BuildOpts struct {
Verbose bool
Open bool
KeepTemp bool
TermProxy bool
OutputFile string
ScaffoldPath string
SdkReplacePath string
Expand Down Expand Up @@ -979,7 +980,17 @@ func TsunamiRun(opts BuildOpts) error {

runCmd.Stdin = os.Stdin

if opts.Open {
if opts.TermProxy {
runCmd.Stdout = os.Stdout
runCmd.Stderr = os.Stderr
runCmd.Env = append(os.Environ(), "TSUNAMI_TERMPROXY=1")
if err := runCmd.Start(); err != nil {
return fmt.Errorf("failed to start application: %w", err)
}
if err := runCmd.Wait(); err != nil {
return fmt.Errorf("application exited with error: %w", err)
}
} else if opts.Open {
// If --open flag is set, we need to capture stderr to parse the listening message
stderr, err := runCmd.StderrPipe()
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions tsunami/cmd/main-tsunami.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,13 @@ var runCmd = &cobra.Command{
verbose, _ := cmd.Flags().GetBool("verbose")
open, _ := cmd.Flags().GetBool("open")
keepTemp, _ := cmd.Flags().GetBool("keeptemp")
termProxy, _ := cmd.Flags().GetBool("termproxy")
opts := build.BuildOpts{
AppPath: args[0],
Verbose: verbose,
Open: open,
KeepTemp: keepTemp,
TermProxy: termProxy,
MoveFileBack: true,
SdkVersion: TsunamiSdkVersion,
}
Expand Down Expand Up @@ -163,6 +165,7 @@ func init() {
runCmd.Flags().BoolP("verbose", "v", false, "Enable verbose output")
runCmd.Flags().Bool("open", false, "Open the application in the browser after starting")
runCmd.Flags().Bool("keeptemp", false, "Keep temporary build directory")
runCmd.Flags().Bool("termproxy", false, "Proxy HTTP through the terminal via OSC 9010")
rootCmd.AddCommand(runCmd)

packageCmd.Flags().BoolP("verbose", "v", false, "Enable verbose output")
Expand Down
50 changes: 28 additions & 22 deletions tsunami/engine/clientimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/fs"
"log"
"net"
Expand All @@ -21,11 +22,13 @@ import (

"github.com/google/uuid"
"github.com/wavetermdev/waveterm/tsunami/rpctypes"
"github.com/wavetermdev/waveterm/tsunami/termlisten"
"github.com/wavetermdev/waveterm/tsunami/util"
"github.com/wavetermdev/waveterm/tsunami/vdom"
)

const TsunamiListenAddrEnvVar = "TSUNAMI_LISTENADDR"
const TsunamiTermProxyEnvVar = "TSUNAMI_TERMPROXY"
const DefaultListenAddr = "localhost:0"
const DefaultComponentName = "App"

Expand Down Expand Up @@ -208,50 +211,53 @@ func (c *ClientImpl) RunMain() {
}

func (c *ClientImpl) listenAndServe(ctx context.Context) error {
// Create HTTP handlers
handlers := newHTTPHandlers(c)

// Create a new ServeMux and register handlers
mux := http.NewServeMux()
handlers.registerHandlers(mux, handlerOpts{
AssetsFS: c.AssetsFS,
StaticFS: c.StaticFS,
ManifestFile: c.ManifestFileBytes,
})

// Determine listen address from environment variable or use default
listenAddr := os.Getenv(TsunamiListenAddrEnvVar)
if listenAddr == "" {
listenAddr = DefaultListenAddr
}
server := &http.Server{Handler: mux}

// Create server and listen on specified address
server := &http.Server{
Addr: listenAddr,
Handler: mux,
}
var listener net.Listener
var port int

// Start listening
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
return fmt.Errorf("failed to listen: %v", err)
if os.Getenv(TsunamiTermProxyEnvVar) != "" {
tl, passthrough, err := termlisten.MakeListener(os.Stdin)
if err != nil {
return fmt.Errorf("termproxy: %w", err)
}
go io.Copy(io.Discard, passthrough)
termlisten.SetupSignals(tl, nil, nil)
listener = tl
port = tl.Port()
} else {
Comment on lines +237 to +247
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Re-emit OSC 9009 after SIGCONT rebinds the listener.

SetupSignals can call l.Reenter() with a new port on resume, but onResume is nil here, so the frontend keeps pointing at the stale port after suspend/resume.

Proposed fix
-        termlisten.SetupSignals(tl, nil, nil)
+        termlisten.SetupSignals(tl, nil, sendOsc9009Tsunami)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if shouldRunTsunamiTermProxy() {
tl, passthrough, err := termlisten.MakeListener(os.Stdin)
if err != nil {
return fmt.Errorf("terminal proxy unavailable: %v\n[tsunami] To bind a regular TCP port instead, run with --tcp", err)
}
go io.Copy(io.Discard, passthrough)
termlisten.SetupSignals(tl, nil, nil)
listener = tl
port = tl.Port()
sendOsc9009Tsunami(port)
} else {
if shouldRunTsunamiTermProxy() {
tl, passthrough, err := termlisten.MakeListener(os.Stdin)
if err != nil {
return fmt.Errorf("terminal proxy unavailable: %v\n[tsunami] To bind a regular TCP port instead, run with --tcp", err)
}
go io.Copy(io.Discard, passthrough)
termlisten.SetupSignals(tl, nil, sendOsc9009Tsunami)
listener = tl
port = tl.Port()
sendOsc9009Tsunami(port)
} else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tsunami/engine/clientimpl.go` around lines 237 - 247, The terminal proxy
block doesn't re-emit OSC 9009 when termlisten.SetupSignals rebinds the listener
on SIGCONT; modify the call to termlisten.SetupSignals in the
shouldRunTsunamiTermProxy branch to supply a non-nil onResume callback that
calls sendOsc9009Tsunami with the new listener port (use tl.Reenter or the
listener provided to the callback to obtain the updated Port()), so the frontend
is updated after resume; keep the existing passthrough goroutine and error
handling around termlisten.MakeListener/tl unchanged.

listenAddr := os.Getenv(TsunamiListenAddrEnvVar)
if listenAddr == "" {
listenAddr = DefaultListenAddr
}
server.Addr = listenAddr
var err error
listener, err = net.Listen("tcp", listenAddr)
if err != nil {
return fmt.Errorf("failed to listen: %v", err)
}
port = listener.Addr().(*net.TCPAddr).Port
}

// Log the address we're listening on
port := listener.Addr().(*net.TCPAddr).Port
log.Printf("[tsunami] listening at http://localhost:%d", port)

// Serve in a goroutine so we don't block
go func() {
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
log.Printf("HTTP server error: %v", err)
c.doShutdown("http server closed")
}
}()

// Wait for context cancellation and shutdown server gracefully
go func() {
<-ctx.Done()
log.Printf("Context canceled, shutting down server...")
if err := server.Shutdown(context.Background()); err != nil {
log.Printf("Server shutdown error: %v", err)
}
Expand Down