agent-float-term 0.2.2

Harness Floating Terminal: a persistent F7 shell for unwrapped AI CLIs
Documentation
#!/usr/bin/env expect
# Independent entry point: expect tests/startup_smoke.exp /path/to/agent-float-term
# Uses only ordinary shells, never starts a harness, and kills only its private server.
set timeout 15
log_user 0
if {$argc != 1} {
    puts stderr "usage: expect tests/startup_smoke.exp /path/to/agent-float-term"
    exit 1
}
set source_binary [file normalize [lindex $argv 0]]
foreach tool {tmux bash} {
    if {[auto_execok $tool] eq ""} {
        puts stderr "startup smoke requires $tool"
        exit 1
    }
}
set tmux [lindex [auto_execok tmux] 0]
set shell [lindex [auto_execok bash] 0]
set root ""
set socket ""
set viewers {}
set original_cwd [pwd]
proc check {condition message} {
    if {![uplevel 1 [list expr $condition]]} { error $message }
}
proc tm {args} {
    global tmux socket
    return [exec $tmux -S $socket {*}$args]
}
proc eventually {script message} {
    global viewers
    set deadline [expr {[clock milliseconds] + 15000}]
    while {[clock milliseconds] < $deadline} {
        if {[uplevel 1 $script]} { return }
        foreach viewer $viewers {
            expect -i $viewer -timeout 0 -re {.+} {} timeout {} eof {error "start exited unexpectedly"}
        }
        after 50
    }
    error "timed out: $message"
}
proc attached {count} {
    if {[catch {tm list-clients -F {#{client_pid}}} listing]} { return 0 }
    return [expr {[llength [split [string trim $listing]]] == $count}]
}
proc contains {pane text} {
    return [expr {[string first $text [tm capture-pane -p -t $pane]] >= 0}]
}
proc owned_generation {} {
    global dedicated
    set marker [file join $dedicated tmux.owner]
    check {[file type $marker] eq "file"} "dedicated ownership marker is not a regular file"
    check {([file attributes $marker -permissions] & 0077) == 0} "dedicated ownership marker is not private"
    set input [open $marker r]
    set generation [read $input]
    close $input
    check {[regexp {^[0-9a-fA-F]{32}$} $generation]} "invalid dedicated ownership generation"
    return $generation
}
proc finish {viewer expected} {
    global viewers
    expect {
        -i $viewer -timeout 15
        eof {}
        timeout {error "start did not exit within 15 seconds"}
    }
    set status [wait -i $viewer]
    set viewers [lsearch -all -inline -not -exact $viewers $viewer]
    check {[lindex $status 2] == 0 && [lindex $status 3] == $expected} "unexpected start exit status: $status"
}
proc teardown {} {
    global root socket tmux viewers original_cwd
    if {$socket ne "" && [file exists $socket]} { catch {exec $tmux -S $socket kill-server} }
    foreach viewer $viewers {
        catch {close -i $viewer}
        catch {wait -i $viewer}
    }
    cd $original_cwd
    if {$root ne ""} { file delete -force -- $root }
}
trap {teardown; exit 1} {SIGINT SIGTERM SIGHUP}

set failed [catch {
    set root [file normalize [exec mktemp -d /tmp/aft-start.XXXXXXXX]]
    file attributes $root -permissions 0700
    # Keep this run stable if another agent rebuilds the shared target artifact.
    set binary [file join $root agent-float-term]
    file copy $source_binary $binary
    file attributes $binary -permissions 0700
    set inherited_path $env(PATH)
    # Preserve Tcl's trace so changes really reach exec/spawn children.
    foreach name [array names env] { unset env($name) }
    set env(PATH) $inherited_path
    set env(HOME) [file join $root home]
    set env(XDG_CONFIG_HOME) [file join $root config]
    set env(XDG_DATA_HOME) [file join $root data]
    set env(XDG_STATE_HOME) [file join $root state]
    set env(XDG_RUNTIME_DIR) [file join $root runtime]
    set env(TMUX_TMPDIR) [file join $root sockets]
    set env(SHELL) $shell
    set env(TERM) xterm-256color
    set work [file join $root {cwd #{pane_id} $value}]
    foreach directory [list $env(HOME) $env(XDG_CONFIG_HOME) $env(XDG_DATA_HOME) $env(XDG_STATE_HOME) $env(XDG_RUNTIME_DIR) $env(TMUX_TMPDIR) $work] {
        file mkdir $directory
        file attributes $directory -permissions 0700
    }
    set config_dir [file join $env(XDG_CONFIG_HOME) agent-float-term]
    file mkdir $config_dir
    file attributes $config_dir -permissions 0700
    set out [open [file join $config_dir config.json] {WRONLY CREAT EXCL} 0600]
    puts $out [format {{"shell":"%s"}} [string map {\\ \\\\ \" \\\" \n \\n \r \\r \t \\t} $shell]]
    close $out
    set dedicated [file join $env(XDG_RUNTIME_DIR) agent-float-term-[exec id -u]]
    set socket [file join $dedicated tmux.sock]
    cd $work

    set rejected [catch {exec $binary start < /dev/null 2>@1} output options]
    check {$rejected && [lindex [dict get $options -errorcode] 2] == 1} "noninteractive start must fail with status 1"
    check {[string first "requires an interactive terminal" $output] >= 0} "noninteractive refusal was not explicit"
    check {![file exists $dedicated]} "noninteractive start created a dedicated server directory"

    # Even at the expected socket path, an unowned server must not be adopted.
    file mkdir $dedicated
    file attributes $dedicated -permissions 0700
    tm -f /dev/null new-session -d -s foreign -c $env(HOME) $shell --noprofile --norc -i
    set foreign_before [tm list-panes -a -F {#{session_id}|#{pane_id}|#{pane_pid}}]
    spawn -noecho $binary start
    set refused $spawn_id
    lappend viewers $refused
    stty rows 40 columns 120 < $spawn_out(slave,name)
    expect {
        -i $refused -timeout 15
        -re {socket is not an owned dedicated server} {}
        timeout {error "unowned dedicated socket was not refused"}
        eof {error "start exited without an ownership refusal"}
    }
    finish $refused 1
    check {[tm list-panes -a -F {#{session_id}|#{pane_id}|#{pane_pid}}] eq $foreign_before} "start modified the unowned server"
    check {[tm list-clients -F {#{client_pid}}] eq ""} "start attached to an unowned session"
    tm kill-server
    eventually {expr {[catch {tm list-sessions}]}} "private foreign server stopped"
    if {[file exists $socket]} {
        file stat $socket stale_before
        spawn -noecho $binary start
        set refused $spawn_id
        lappend viewers $refused
        stty rows 40 columns 120 < $spawn_out(slave,name)
        expect {
            -i $refused -timeout 15
            -re {dead socket has no ownership evidence} {}
            timeout {error "unowned dead socket was not refused"}
            eof {error "start exited without a stale-socket ownership refusal"}
        }
        finish $refused 1
        file stat $socket stale_after
        check {$stale_after(ino) == $stale_before(ino) && $stale_after(dev) == $stale_before(dev)} "start replaced an unowned dead socket"
        check {![file exists [file join $dedicated tmux.owner]]} "start created ownership evidence for an unowned socket"
        check {[catch {tm list-sessions}]} "start created a server from an unowned dead socket"
    }
    # Remove only this unowned control socket to begin the clean-install scenario.
    # The owned recovery scenarios below never remove or replace the dead socket.
    file delete -- $socket

    # No command after 'start': it opens a shell, not a wrapped or auto-started CLI.
    spawn -noecho $binary start
    set first $spawn_id
    lappend viewers $first
    stty rows 40 columns 120 < $spawn_out(slave,name)
    eventually {attached 1} "first dedicated shell attached"
    set first_pane [tm list-panes -a -F {#{pane_id}}]
    check {[llength [split $first_pane]] == 1} "start created more than one shell pane"
    set first_pid [tm display-message -p -t $first_pane {#{pane_pid}}]
    set first_session [tm display-message -p -t $first_pane {#{session_id}}]
    check {[tm display-message -p -t $first_pane {#{pane_current_command}}] eq [file tail $shell]} "start did not open an ordinary shell"
    set first_client [tm list-clients -F {#{client_name}}]
    send -i $first -- {printf 'AFT_START_%s:%s\n' "$PWD" "$AFT_DISABLE"}
    send -i $first -- "\r"
    eventually {contains $first_pane "AFT_START_$work:1"} "literal hash/dollar cwd and recursion guard"
    # tmux 3.4 escapes '$' in printed metadata. Render the expected path the same
    # way without expanding formats; the shell output above checks the raw cwd.
    check {[tm display-message -p -t $first_pane {#{pane_current_path}}] eq [tm display-message -p -l $work]} "start expanded literal cwd characters"

    # Running start inside that shell must not create a nested outer session.
    send -i $first -- "'$binary' start\r"
    eventually {contains $first_pane "Already inside tmux; no nested outer session was created."} "in-tmux start binds without nesting"
    check {[tm list-panes -a -F {#{pane_id}}] eq $first_pane} "in-tmux start created another pane"

    spawn -noecho $binary start
    set second $spawn_id
    lappend viewers $second
    stty rows 40 columns 120 < $spawn_out(slave,name)
    eventually {attached 2} "second start attached to reused owned server"
    set second_pane ""
    foreach pane [split [tm list-panes -a -F {#{pane_id}}] \n] {
        if {$pane ne $first_pane} {
            check {$second_pane eq ""} "second start created extra panes"
            set second_pane $pane
        }
    }
    check {$second_pane ne ""} "second start hijacked the first session"
    set second_session [tm display-message -p -t $second_pane {#{session_id}}]
    check {$second_session ne $first_session} "second start reused the first work session"
    check {[lsearch -exact [split [tm list-clients -F {#{client_name}|#{session_id}}] \n] "$first_client|$first_session"] >= 0} "second start moved the original client"
    check {[tm display-message -p -t $first_pane {#{pane_pid}}] eq $first_pid} "second start replaced the first shell"
    send -i $second -- {printf 'AFT_SECOND_%s\n' "$PWD"}
    send -i $second -- "\r"
    eventually {contains $second_pane "AFT_SECOND_$work"} "second shell retains literal cwd"
    send -i $first -- "printf 'AFT_FIRST_%s\\n' STILL_HERE\r"
    eventually {contains $first_pane AFT_FIRST_STILL_HERE} "first client remains on its original shell"
    check {![contains $second_pane AFT_FIRST_STILL_HERE]} "first client input went to second session"
    send -i $second -- "\002d"
    finish $second 0
    send -i $first -- "\002d"
    finish $first 0
    tm has-session -t $first_session
    tm has-session -t $second_session
    check {[tm display-message -p -t $first_pane {#{pane_pid}}] eq $first_pid} "detach terminated dedicated shell"

    set previous_generation [owned_generation]
    check {[tm show-options -gqv @aft_generation] eq $previous_generation} "live generation differs from the private ownership marker"
    set previous_pid $first_pid
    # Failure injection is confined to our dedicated server. Its two detached
    # work sessions are intentionally destroyed; neither socket nor marker is deleted.
    tm kill-server
    foreach recovery {killed-server last-shell-exit} {
        eventually {expr {[catch {tm list-sessions}]}} "server stopped before $recovery recovery"
        check {[owned_generation] eq $previous_generation} "server exit discarded the ownership marker"
        if {[file exists $socket]} {
            check {[file type $socket] eq "socket"} "dead owned socket is not a socket"
        }
        spawn -noecho $binary start
        set recovered $spawn_id
        lappend viewers $recovered
        stty rows 40 columns 120 < $spawn_out(slave,name)
        eventually {attached 1} "start recovers after $recovery without manual socket deletion"
        set generation [owned_generation]
        check {$generation ne $previous_generation} "$recovery reused the dead server generation"
        check {[tm show-options -gqv @aft_generation] eq $generation} "recovered generation differs from the ownership marker"
        check {[tm show-options -gqv @aft_dedicated] eq "1"} "recovered server is not marked dedicated"
        set pane [tm list-panes -a -F {#{pane_id}}]
        check {[llength [split $pane]] == 1} "recovery created extra shell panes"
        set shell_pid [tm display-message -p -t $pane {#{pane_pid}}]
        check {$shell_pid ne $previous_pid} "recovery did not create a fresh shell"
        check {[tm display-message -p -t $pane {#{pane_current_command}}] eq [file tail $shell]} "recovery did not open a normal shell"
        send -i $recovered -- {printf 'AFT_RECOVERED_%s:%s\n' "$PWD" "$AFT_DISABLE"}
        send -i $recovered -- "\r"
        eventually {contains $pane "AFT_RECOVERED_$work:1"} "recovered shell preserves literal cwd and recursion guard"
        # With only this shell left, a normal exit must also permit a later start.
        send -i $recovered -- "exit\r"
        finish $recovered 0
        set previous_generation $generation
        set previous_pid $shell_pid
    }
    eventually {expr {[catch {tm list-sessions}]}} "final dedicated shell exited"
} message options]
if {$failed} {
    puts stderr "Startup smoke FAILED: $message"
    if {[dict exists $options -errorinfo]} { puts stderr [dict get $options -errorinfo] }
    if {$socket ne "" && [file exists $socket]} {
        catch {puts stderr "clients: [tm list-clients]"}
        catch {
            foreach pane [split [tm list-panes -a -F {#{pane_id}}] \n] {
                puts stderr "pane $pane:\n[tm capture-pane -p -t $pane]"
            }
        }
    }
}
teardown
if {$failed} { exit 1 }
puts "Startup smoke passed: terminal guard, literal cwd, ordinary shells, owned reuse without takeover, unowned stale refusal, owned restart after kill/exit"