agent-float-term 0.3.4

Harness Floating Terminal: a persistent F7 shell for unwrapped AI CLIs
Documentation
#!/usr/bin/env expect
# EXPERIMENTAL, not a backend or a shipping gate pass. No application build needed.
# Run from any directory: expect tests/native_pane_probe.exp
# All mouse/prefix/shell input traverses the attached outer client's PTY.
set timeout 5
log_user 0
if {$argc != 0} {
    puts stderr "usage: expect tests/native_pane_probe.exp"
    exit 1
}
foreach tool {tmux bash mktemp} {
    if {[auto_execok $tool] eq ""} {
        puts stderr "native pane probe requires $tool"
        exit 1
    }
}
set tmux [lindex [auto_execok tmux] 0]
set shell [lindex [auto_execok bash] 0]
set root ""
set socket ""
set client ""
set deadline [expr {[clock milliseconds] + 45000}]

proc check {condition message} {
    if {![uplevel 1 [list expr $condition]]} { error $message }
}
proc tm {args} {
    global tmux socket
    return [exec $tmux -S $socket -f /dev/null {*}$args]
}
proc drain {} {
    global client
    if {$client ne ""} {
        expect -i $client -timeout 0 -re {.+} {} timeout {} eof {error "outer client exited"}
    }
}
proc eventually {script message} {
    global deadline
    set until [expr {min($deadline, [clock milliseconds] + 5000)}]
    while {[clock milliseconds] < $until} {
        drain
        if {[uplevel 1 $script]} { return }
        after 25
    }
    error "timed out: $message"
}
proc type {text} {
    global client
    send -i $client -- $text
}
proc contains {pane text} {
    return [expr {[string first $text [tm capture-pane -p -t $pane]] >= 0}]
}
proc status_binding {} {
    foreach line [split [tm list-keys -T root] \n] {
        if {[regexp {^bind-key +(?:-r +)?-T +root +MouseDown1Status +} $line]} { return $line }
    }
    error "default MouseDown1Status binding missing"
}
proc state_is {path expected} {
    if {![file exists $path]} { return 0 }
    set input [open $path r]
    set actual [read $input]
    close $input
    return [expr {$actual eq $expected}]
}
proc client_session {name} {
    foreach line [split [tm list-clients -F {#{client_name}|#{session_name}}] \n] {
        lassign [split $line |] actual session
        if {$actual eq $name} { return $session }
    }
    return ""
}
proc float_viewer {main} {
    global tmux socket retained env
    # new-pane creates the float; do not try to move the retained shell into it.
    # Multiple argv avoid shell quoting; env -u permits this deliberate nesting.
    set pane [tm new-pane -t $main -x 60 -y 16 -X 20 -Y 5 -P -F {#{pane_id}} \
        -c $env(HOME) env -u TMUX $tmux -S $socket -f /dev/null attach-session -t $retained]
    check {[tm display-message -p -t $pane {#{pane_floating_flag}|#{pane_active}|#{pane_width}|#{pane_height}|#{pane_left}|#{pane_top}}] eq "1|1|60|16|20|5"} \
        "new-pane did not create an active native 60x16 float at 20,5"
    check {[tm display-message -p -t $main {#{pane_width}|#{pane_height}}] eq "120|39"} "float split the main pane"
    eventually {expr {[tm display-message -p -t $retained {#{session_attached}}] == 1}} "nested viewer attached"
    puts "PASS native float $pane: [tm display-message -p -t $pane {floating=#{pane_floating_flag} active=#{pane_active} size=#{pane_width}x#{pane_height} origin=#{pane_left},#{pane_top}}]"
    return $pane
}
proc click {column expected count} {
    global outer
    # SGR left-button press AND release, terminal coordinates are one-based.
    type "\033\[<0;${column};1M\033\[<0;${column};1m"
    eventually {expr {[client_session $outer] eq $expected && [tm show-options -gqv @session_changes] == $count}} \
        "one main-client switch to $expected"
    # Catch duplicate release-triggered transitions, draining redraws.
    set until [expr {[clock milliseconds] + 350}]
    while {[clock milliseconds] < $until} { drain; after 25 }
    check {[client_session $outer] eq $expected} "main client switched again"
    check {[tm show-options -gqv @session_changes] == $count} "duplicate session transition"
    puts "PASS SGR click ($column,1): outer=$outer session=$expected transitions=$count (cumulative)"
}
proc teardown {} {
    global root socket client tmux
    if {$socket ne "" && [file exists $socket]} {
        catch {exec $tmux -S $socket -f /dev/null kill-server}
    }
    if {$client ne ""} {
        catch {close -i $client}
        catch {wait -i $client}
    }
    if {$root ne ""} { file delete -force -- $root }
}
trap {teardown; exit 1} {SIGINT SIGTERM SIGHUP}

set failed [catch {
    # Short, private socket paths fit macOS sockaddr_un. Never use a live socket.
    set root [file normalize [exec mktemp -d /tmp/aft-native.XXXXXXXX]]
    file attributes $root -permissions 0700
    set inherited_path $env(PATH)
    # Preserve Tcl's env-array trace while removing TMUX, shell startup variables,
    # and all other inherited configuration from every child process.
    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(TMPDIR) [file join $root tmp]
    set env(SHELL) $shell
    set env(TERM) xterm-256color
    set env(LC_ALL) C
    set env(PS1) {PROBE> }
    foreach directory [list $env(HOME) $env(XDG_CONFIG_HOME) $env(XDG_DATA_HOME) $env(XDG_STATE_HOME) $env(XDG_RUNTIME_DIR) $env(TMUX_TMPDIR) $env(TMPDIR) [file join $env(HOME) retained]] {
        file mkdir $directory
        file attributes $directory -permissions 0700
    }
    set socket [file join $env(TMUX_TMPDIR) server]
    set version [exec $tmux -V]
    check {$version eq "tmux 3.7c"} "requires real tmux 3.7c on PATH, found: $version"
    puts "EXPERIMENTAL native viewer probe: $version ($tmux)"
    puts "Private fixture: $root"
    foreach session {alpha beta retained} {
        tm new-session -d -s $session -x 120 -y 40 -c $env(HOME) $shell --noprofile --norc -i
    }
    tm set-option -g default-shell $shell
    tm set-option -g mouse on
    tm set-option -g status-position top
    tm set-option -g status-interval 1
    tm set-option -g escape-time 100
    tm set-option -t retained status off
    tm set-option -t retained destroy-unattached off
    set retained [tm display-message -p -t retained: {#{session_id}}]
    set shell_pane [tm display-message -p -t retained: {#{pane_id}}]
    set shell_pid [tm display-message -p -t $shell_pane {#{pane_pid}}]
    set alpha [tm display-message -p -t alpha: {#{pane_id}}]
    set beta [tm display-message -p -t beta: {#{pane_id}}]
    set beta_session [tm display-message -p -t beta: {#{session_id}}]
    set main_pids [list [tm display-message -p -t $alpha {#{pane_pid}}] [tm display-message -p -t $beta {#{pane_pid}}]]
    tm set-option -g {status-format[0]} "#\[range=session|$beta_session\] BETA #\[norange\] #\[range=left\] BACK #\[norange\]"
    set default_mouse [status_binding]
    check {[string first {switch-client -t =} $default_mouse] >= 0} "unexpected default session-range binding: $default_mouse"
    puts "Fixture default status binding: $default_mouse"
    # Instrument ONLY this empty-config fixture. No forwarding or key wrappers.
    tm set-option -g @custom_clicks 0
    tm set-option -g @prefix_count 0
    tm bind-key -T root MouseDown1StatusLeft {set-option -gF @custom_clicks '#{e|+:#{@custom_clicks},1}' ; set-option -gF @mouse_target '#{client_name}|#{mouse_status_range}|#{mouse_status_line}' ; switch-client -t alpha}
    tm bind-key -T prefix F12 {set-option -gF @prefix_target '#{pane_id}|#{session_name}|#{client_name}' ; set-option -gF @prefix_count '#{e|+:#{@prefix_count},1}'}
    set fixture_bindings [tm list-keys]

    spawn -noecho $tmux -S $socket -f /dev/null attach-session -t alpha
    set client $spawn_id
    stty rows 40 columns 120 < $spawn_out(slave,name)
    eventually {expr {[tm list-clients -F {#{session_name}}] eq "alpha"}} "outer attached PTY"
    set outer [tm list-clients -F {#{client_name}}]
    eventually {expr {[tm display-message -p -t $alpha {#{pane_width}|#{pane_height}}] eq "120|39"}} "outer PTY size"
    # Count actual transitions, filtered to the outer client;
    # nested attach/detach must not be attributed to status clicks.
    tm set-option -g @session_changes 0
    tm set-hook -g client-session-changed "set-option -gF @last_hook '#{hook_client}|#{client_name}|#{hook_session_name}|#{session_name}'; if-shell -F '#{==:#{hook_client},$outer}' { set-option -gF @session_changes '#{e|+:#{@session_changes},1}' }"

    set first [float_viewer $alpha]
    type {probe_state=kept_unexported; cd "$HOME/retained"; printf '%s|%s|%s' "$$" "$probe_state" "$PWD" > "$HOME/state-1"; printf 'PROBE_READY_%s\n' 1}
    type "\r"
    set expected "$shell_pid|kept_unexported|$env(HOME)/retained"
    eventually {state_is [file join $env(HOME) state-1] $expected} "initial shell PID, unexported state and cwd"
    eventually {contains $first PROBE_READY_1} "retained shell rendered through first viewer"
    check {[tm display-message -p -t alpha: {#{pane_id}}] eq $first} "float not active before status click"
    click 3 beta 1
    check {[tm display-message -p -t $retained {#{session_attached}}] == 1} "status click switched the nested client"

    tm kill-pane -t $first
    eventually {expr {[tm display-message -p -t $retained {#{session_attached}}] == 0}} "hide destroyed only viewer"
    check {[lsearch -exact [split [tm list-panes -a -F {#{pane_id}}] \n] $first] < 0} "first viewer survived hide"
    check {[tm display-message -p -t $shell_pane {#{pane_pid}|#{pane_dead}}] eq "$shell_pid|0"} "hide killed retained shell"
    puts "PASS hide: viewer=$first destroyed; retained=$retained pane=$shell_pane pid=$shell_pid alive and detached"

    set second [float_viewer $beta]
    check {$second ne $first} "viewer was not recreated"
    type {printf '%s|%s|%s' "$$" "$probe_state" "$PWD" > "$HOME/state-2"; printf 'PROBE_READY_%s\n' 2}
    type "\r"
    eventually {state_is [file join $env(HOME) state-2] $expected} "same shell PID, unexported state and cwd after recreate"
    eventually {contains $second PROBE_READY_2} "retained shell rendered through recreated viewer"
    puts "PASS recreate: viewer=$second retained=$retained pane=$shell_pane pid=$shell_pid; unexported state and cwd unchanged"

    # Actual default prefix C-b then F12, not a CLI command with a chosen target.
    check {[tm display-message -p -t beta: {#{pane_id}}] eq $second} "float not active before prefix"
    type "\002\033\[24~"
    eventually {expr {[tm show-options -gqv @prefix_count] eq "1"}} "fixture prefix binding executed"
    set prefix_target [tm show-options -gqv @prefix_target]
    check {$prefix_target eq "$second|beta|$outer"} "prefix attribution differs from native outer float targeting: $prefix_target"
    check {$second ne $beta && $second ne $shell_pane} "prefix target is not distinct from main and retained shell"
    puts "EXPECTED PRODUCTION GATE FAILURE: C-b F12 target=$prefix_target; main=$beta; retained-shell=$shell_pane. Outer tmux intercepts prefix and targets its active float, NOT the main pane."

    click 10 alpha 2
    check {[tm show-options -gqv @custom_clicks] eq "1"} "custom mouse binding did not execute exactly once"
    check {[tm show-options -gqv @mouse_target] eq "$outer|left|0"} "custom click not attributed to outer top status: [tm show-options -gqv @mouse_target]"
    puts "PASS custom mouse attribution: [tm show-options -gqv @mouse_target]; invocations=1"
    check {[tm show-options -gqv @prefix_count] eq "1"} "prefix executed more than once"
    check {[tm display-message -p -t $retained {#{session_attached}}] == 1} "custom click moved nested client"
    # Return to the same live viewer to ask the shell, rather than trusting a
    # stale state file. This CLI session switch is outside the two click counts.
    tm switch-client -c $outer -t beta
    eventually {expr {[client_session $outer] eq "beta"}} "return for final shell check"
    type {printf '%s|%s|%s' "$$" "$probe_state" "$PWD" > "$HOME/state-3"}
    type "\r"
    eventually {state_is [file join $env(HOME) state-3] $expected} "shell unchanged after custom click and prefix"
    tm kill-pane -t $second
    eventually {expr {[tm display-message -p -t $retained {#{session_attached}}] == 0}} "second viewer destroyed"
    check {[lsearch -exact [split [tm list-panes -a -F {#{pane_id}}] \n] $second] < 0} "second viewer survived hide"
    check {[tm display-message -p -t $shell_pane {#{pane_pid}|#{pane_dead}}] eq "$shell_pid|0"} "second hide killed shell"
    check {[list [tm display-message -p -t $alpha {#{pane_pid}}] [tm display-message -p -t $beta {#{pane_pid}}]] eq $main_pids} "main shells replaced"
    check {[status_binding] eq $default_mouse} "default session-range binding changed"
    check {[tm list-keys] eq $fixture_bindings} "bindings changed during probe"
    puts "PASS final shell state/PID/cwd and main PIDs unchanged; fixture bindings unchanged"
} message options]
if {$failed && $socket ne "" && [file exists $socket]} {
    catch {puts stderr "Clients: [tm list-clients -F {#{client_name}|#{session_name}|#{pane_id}|#{client_width}x#{client_height}}]"}
    catch {puts stderr "Changes: [tm show-options -gqv @session_changes]; last hook: [tm show-options -gqv @last_hook]"}
    catch {puts stderr "Status: [tm show-options -gv status-format]"}
}
teardown
check {$root eq "" || ![file exists $root]} "private directory survived cleanup"
if {$failed} {
    puts stderr "FAIL experimental native pane probe: $message"
    puts stderr [dict get $options -errorinfo]
    exit 1
}
puts "PASS experimental observations ONLY; production gate FAIL (prefix target); multi-client visibility UNSUPPORTED; NOT SHIPPING"