Merge desktop-tmux-default: app-started sessions survive quitting the app
This commit is contained in:
@@ -7,7 +7,8 @@
|
|||||||
* BIND_HOST (private 127.0.0.1 by default; 0.0.0.0 only when LAN sharing is on),
|
* BIND_HOST (private 127.0.0.1 by default; 0.0.0.0 only when LAN sharing is on),
|
||||||
* SHELL_PATH (platform-aware default so Windows gets PowerShell), and USE_TMUX
|
* SHELL_PATH (platform-aware default so Windows gets PowerShell), and USE_TMUX
|
||||||
* (forced off on Windows — tmux is a *nix keepalive; elsewhere respect the
|
* (forced off on Windows — tmux is a *nix keepalive; elsewhere respect the
|
||||||
* ambient env, defaulting off). Every other ambient var passes through untouched.
|
* ambient env, defaulting to auto-detect). Every other ambient var passes through
|
||||||
|
* untouched.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { DesktopPrefs } from './types.js'
|
import type { DesktopPrefs } from './types.js'
|
||||||
@@ -17,8 +18,23 @@ import { defaultShellForPlatform } from './shell.js'
|
|||||||
const PRIVATE_BIND_HOST = '127.0.0.1'
|
const PRIVATE_BIND_HOST = '127.0.0.1'
|
||||||
/** Wildcard bind host — LAN sharing (phones/tablets reconnect); opt-in, no auth. */
|
/** Wildcard bind host — LAN sharing (phones/tablets reconnect); opt-in, no auth. */
|
||||||
const LAN_BIND_HOST = '0.0.0.0'
|
const LAN_BIND_HOST = '0.0.0.0'
|
||||||
/** tmux keepalive is a *nix feature; the desktop shell defaults it off (and forces off on Windows). */
|
/** tmux is a *nix keepalive, so Windows forces it off outright. */
|
||||||
const TMUX_OFF = '0'
|
const TMUX_OFF = '0'
|
||||||
|
/**
|
||||||
|
* Everywhere else the desktop shell auto-detects (loadConfig's 'auto' → is the tmux
|
||||||
|
* binary present?), which is what the standalone server already did.
|
||||||
|
*
|
||||||
|
* It used to default OFF, and that quietly broke the app's whole premise. Without
|
||||||
|
* tmux the shell is a direct child of the embedded server, so quitting the app —
|
||||||
|
* including the tray "Quit", including a logout — kills every session with it. The
|
||||||
|
* point of this app is to hand Claude Code a task, walk away, and reconnect from
|
||||||
|
* another device; a session that cannot survive the window closing does not do that.
|
||||||
|
* With tmux the node-pty process is only a tmux CLIENT, so shutdown detaches and the
|
||||||
|
* shell keeps running (see SessionManager.shutdown).
|
||||||
|
*
|
||||||
|
* An explicit ambient USE_TMUX still wins, so USE_TMUX=0 remains the way to opt out.
|
||||||
|
*/
|
||||||
|
const TMUX_AUTO = 'auto'
|
||||||
|
|
||||||
export interface ServerEnvInput {
|
export interface ServerEnvInput {
|
||||||
readonly prefs: DesktopPrefs
|
readonly prefs: DesktopPrefs
|
||||||
@@ -38,6 +54,6 @@ export function buildServerEnv(input: ServerEnvInput): Record<string, string | u
|
|||||||
PORT: String(port),
|
PORT: String(port),
|
||||||
BIND_HOST: prefs.lanSharing ? LAN_BIND_HOST : PRIVATE_BIND_HOST,
|
BIND_HOST: prefs.lanSharing ? LAN_BIND_HOST : PRIVATE_BIND_HOST,
|
||||||
SHELL_PATH: prefs.shellPath ?? defaultShellForPlatform(platform, env),
|
SHELL_PATH: prefs.shellPath ?? defaultShellForPlatform(platform, env),
|
||||||
USE_TMUX: platform === 'win32' ? TMUX_OFF : (env.USE_TMUX ?? TMUX_OFF),
|
USE_TMUX: platform === 'win32' ? TMUX_OFF : (env.USE_TMUX ?? TMUX_AUTO),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,22 @@
|
|||||||
* guarantee (input.env untouched), and pass-through of unrelated env vars.
|
* guarantee (input.env untouched), and pass-through of unrelated env vars.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { execFileSync } from 'node:child_process'
|
||||||
|
|
||||||
import { describe, test, expect } from 'vitest'
|
import { describe, test, expect } from 'vitest'
|
||||||
import { buildServerEnv } from '../../desktop/src/server-config.js'
|
import { buildServerEnv } from '../../desktop/src/server-config.js'
|
||||||
|
import { loadConfig } from '../../src/config.js'
|
||||||
import type { DesktopPrefs } from '../../desktop/src/types.js'
|
import type { DesktopPrefs } from '../../desktop/src/types.js'
|
||||||
|
|
||||||
|
const tmuxInstalled = (() => {
|
||||||
|
try {
|
||||||
|
execFileSync('tmux', ['-V'], { stdio: 'ignore' })
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
const BASE_PREFS: DesktopPrefs = {
|
const BASE_PREFS: DesktopPrefs = {
|
||||||
port: null,
|
port: null,
|
||||||
lanSharing: false,
|
lanSharing: false,
|
||||||
@@ -69,11 +81,33 @@ describe('buildServerEnv', () => {
|
|||||||
expect(result.USE_TMUX).toBe('1')
|
expect(result.USE_TMUX).toBe('1')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('defaults USE_TMUX to off on non-Windows when the env is unset', () => {
|
test('defaults USE_TMUX to auto-detect on non-Windows when the env is unset', () => {
|
||||||
|
// Not '0'. Without tmux the shell is a direct child of the embedded server, so
|
||||||
|
// quitting the app kills every session — which defeats the point of walking away
|
||||||
|
// from a task and reconnecting later. 'auto' means "use tmux if it is installed".
|
||||||
const result = buildServerEnv({ prefs: BASE_PREFS, port: 3000, platform: 'linux', env: {} })
|
const result = buildServerEnv({ prefs: BASE_PREFS, port: 3000, platform: 'linux', env: {} })
|
||||||
|
expect(result.USE_TMUX).toBe('auto')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('an explicit ambient USE_TMUX=0 still wins — opting out stays possible', () => {
|
||||||
|
const result = buildServerEnv({ prefs: BASE_PREFS, port: 3000, platform: 'darwin', env: { USE_TMUX: '0' } })
|
||||||
expect(result.USE_TMUX).toBe('0')
|
expect(result.USE_TMUX).toBe('0')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// The bug this guards against was the two layers disagreeing: buildServerEnv emitted
|
||||||
|
// a value that loadConfig then resolved to something else than intended. Assert the
|
||||||
|
// end-to-end result, not just the string.
|
||||||
|
test('the default resolves to tmux ON once loadConfig sees it', () => {
|
||||||
|
if (!tmuxInstalled) return // 'auto' correctly resolves to false without the binary
|
||||||
|
const env = buildServerEnv({ prefs: BASE_PREFS, port: 3000, platform: 'darwin', env: {} })
|
||||||
|
expect(loadConfig({ ...env, BIND_HOST: '127.0.0.1' }).useTmux).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('an explicit opt-out resolves to tmux OFF through loadConfig', () => {
|
||||||
|
const env = buildServerEnv({ prefs: BASE_PREFS, port: 3000, platform: 'darwin', env: { USE_TMUX: '0' } })
|
||||||
|
expect(loadConfig({ ...env, BIND_HOST: '127.0.0.1' }).useTmux).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
test('does not mutate the input env', () => {
|
test('does not mutate the input env', () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const env = { PATH: '/usr/bin', USE_TMUX: '1' }
|
const env = { PATH: '/usr/bin', USE_TMUX: '1' }
|
||||||
|
|||||||
Reference in New Issue
Block a user