Skip to content

Commit ece76a4

Browse files
committed
fix(tmux): correct external session attach instructions
1 parent ddd69e1 commit ece76a4

File tree

6 files changed

+73
-7
lines changed

6 files changed

+73
-7
lines changed

cmd/gestalt-agent/run.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,16 @@ func printTmuxAttachHint(out io.Writer, sessionID string) {
4747
if out == nil {
4848
return
4949
}
50-
id := strings.TrimSpace(sessionID)
51-
if id == "" {
52-
id = "gestalt-agent"
53-
}
50+
target, err := tmuxTargetForSession(sessionID)
5451
fmt.Fprintln(out, "Session is running in tmux.")
55-
fmt.Fprintf(out, "Attach with: tmux attach -t %q\n", id)
52+
if err != nil {
53+
fmt.Fprintln(out, "Attach with: tmux attach")
54+
return
55+
}
56+
if target.sessionName == "" {
57+
fmt.Fprintf(out, "Switch with: tmux select-window -t %q\n", target.windowName)
58+
return
59+
}
60+
fmt.Fprintf(out, "Attach with: tmux attach -t %q\n", target.sessionName)
61+
fmt.Fprintf(out, "Then switch with: tmux select-window -t %q\n", target.windowName)
5662
}

cmd/gestalt-agent/run_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"errors"
77
"net/http"
88
"net/http/httptest"
9+
"os"
10+
"path/filepath"
911
"strings"
1012
"testing"
1113

@@ -120,3 +122,38 @@ func newTestSessionServer(t *testing.T, handler func(w http.ResponseWriter)) *ht
120122
handler(w)
121123
}))
122124
}
125+
126+
func TestPrintTmuxAttachHintOutsideTmux(t *testing.T) {
127+
t.Setenv("TMUX", "")
128+
var out bytes.Buffer
129+
printTmuxAttachHint(&out, "Fixer 1")
130+
text := out.String()
131+
if !strings.Contains(text, "Session is running in tmux.") {
132+
t.Fatalf("missing session message: %q", text)
133+
}
134+
if !strings.Contains(text, `Attach with: tmux attach -t "Gestalt `+filepath.Base(mustGetwd(t))+`"`) {
135+
t.Fatalf("expected attach target for workdir session, got %q", text)
136+
}
137+
if !strings.Contains(text, `Then switch with: tmux select-window -t "Fixer 1"`) {
138+
t.Fatalf("expected window switch hint, got %q", text)
139+
}
140+
}
141+
142+
func TestPrintTmuxAttachHintInsideTmux(t *testing.T) {
143+
t.Setenv("TMUX", "1")
144+
var out bytes.Buffer
145+
printTmuxAttachHint(&out, "Fixer 1")
146+
text := out.String()
147+
if !strings.Contains(text, `Switch with: tmux select-window -t "Fixer 1"`) {
148+
t.Fatalf("expected select-window hint, got %q", text)
149+
}
150+
}
151+
152+
func mustGetwd(t *testing.T) string {
153+
t.Helper()
154+
wd, err := os.Getwd()
155+
if err != nil {
156+
t.Fatalf("getwd: %v", err)
157+
}
158+
return wd
159+
}

frontend/src/App.svelte

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
$: crashState = $appHealthStore
6868
$: clipboardAvailable = canUseClipboard()
6969
$: activeTerminal = terminals.find((terminal) => terminal.id === activeId) || null
70+
$: tmuxSessionName =
71+
status?.working_dir && String(status.working_dir).trim()
72+
? `Gestalt ${buildTitle(status.working_dir)}`
73+
: ''
7074
$: if (status) {
7175
setSessionUiConfigFromStatus(status)
7276
}
@@ -373,6 +377,7 @@
373377
visible={true}
374378
sessionInterface={activeTerminal.interface || ''}
375379
sessionRunner={activeTerminal.runner || ''}
380+
tmuxSessionName={tmuxSessionName}
376381
guiModules={resolveGuiModules(activeTerminal.gui_modules, activeTerminal.runner)}
377382
onDelete={deleteTerminal}
378383
/>

frontend/src/components/Terminal.svelte

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
export let temporalUrl = ''
1515
export let sessionInterface = ''
1616
export let sessionRunner = ''
17+
export let tmuxSessionName = ''
1718
export let guiModules = []
1819
export let planSidebarOpen = false
1920
export let onTogglePlan = () => {}
@@ -44,6 +45,7 @@
4445
let promptFilesLabel = ''
4546
let interfaceValue = ''
4647
let runnerValue = ''
48+
let tmuxSessionValue = ''
4749
let isCLI = false
4850
let isExternal = false
4951
let hasPlanModule = false
@@ -172,6 +174,7 @@
172174
$: interfaceValue =
173175
typeof sessionInterface === 'string' ? sessionInterface.trim().toLowerCase() : ''
174176
$: runnerValue = typeof sessionRunner === 'string' ? sessionRunner.trim().toLowerCase() : ''
177+
$: tmuxSessionValue = typeof tmuxSessionName === 'string' ? tmuxSessionName.trim() : ''
175178
$: isCLI = interfaceValue === 'cli'
176179
$: isExternal = runnerValue === 'external'
177180
$: hasPlanModule =
@@ -262,7 +265,13 @@
262265
{#if isExternal}
263266
<div class="terminal-external">
264267
<p>This session is managed in tmux.</p>
265-
<p>Attach with: <code>tmux attach -t "{sessionId}"</code></p>
268+
{#if tmuxSessionValue}
269+
<p>Attach with: <code>tmux attach -t "{tmuxSessionValue}"</code></p>
270+
<p>Then switch with: <code>tmux select-window -t "{sessionId}"</code></p>
271+
{:else}
272+
<p>Attach with: <code>tmux attach</code></p>
273+
<p>If needed, list sessions first: <code>tmux ls</code></p>
274+
{/if}
266275
</div>
267276
{:else if isCLI}
268277
<TerminalCanvas

frontend/src/views/TerminalView.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
export let visible = true
1212
export let sessionInterface = ''
1313
export let sessionRunner = ''
14+
export let tmuxSessionName = ''
1415
export let guiModules = []
1516
export let onDelete = () => {}
1617
@@ -117,6 +118,7 @@
117118
{temporalUrl}
118119
{sessionInterface}
119120
{sessionRunner}
121+
{tmuxSessionName}
120122
{guiModules}
121123
{planSidebarOpen}
122124
onTogglePlan={togglePlanSidebar}

frontend/tests/terminalComponent.test.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,17 @@ describe('Terminal', () => {
250250
getTerminalState.mockReturnValue(buildState())
251251

252252
const { getByText } = render(Terminal, {
253-
props: { sessionId: 't1', sessionInterface: 'cli', sessionRunner: 'external' },
253+
props: {
254+
sessionId: 't1',
255+
sessionInterface: 'cli',
256+
sessionRunner: 'external',
257+
tmuxSessionName: 'Gestalt workspace',
258+
},
254259
})
255260

256261
await tick()
257262
expect(getByText('This session is managed in tmux.')).toBeTruthy()
263+
expect(getByText('Attach with:')).toBeTruthy()
264+
expect(getByText('Then switch with:')).toBeTruthy()
258265
})
259266
})

0 commit comments

Comments
 (0)