local wez = require 'wezterm' local act = wez.action local instances = require 'bugabinga.instances' local names = { aider = 'Aider', claude = 'Claude', codex = 'Codex', copilot = 'Copilot', gemini = 'Gemini', opencode = 'OpenCode', pi = 'Pi', } local function clean( value ) if not value or value == '' then return nil end value = value:gsub( '^%s+', '' ):gsub( '%s+$', '' ) return value ~= '' and value or nil end local function basename( value ) value = clean( value ) if not value then return nil end value = value:gsub( '\\', '/' ) return value:match '([^/]+)$' or value end local function program( user_vars ) local command = clean( user_vars and user_vars.WEZTERM_PROG ) if not command then return nil end command = command:gsub( '^%^', '' ) local executable = basename( command:match '^([^%s]+)' ) return executable and executable:lower():gsub( '%.exe$', '' ):gsub( '%.cmd$', '' ):gsub( '%.bat$', '' ) or nil end local function identity( user_vars ) local explicit = clean( user_vars and user_vars.agent ) if explicit and names[explicit:lower()] then return names[explicit:lower()] end return names[program( user_vars )] end local function detect( title, process, user_vars ) local explicit = identity( user_vars ) if explicit then return explicit end local executable = basename( process ) if executable then executable = executable:lower():gsub( '%.exe$', '' ) if names[executable] then return names[executable] end end if clean( user_vars and user_vars.WEZTERM_STATE ) == 'idle' then return nil end title = clean( title ) if not title then return nil end if title:sub( 1, #'π' ) == 'π' then return 'Pi' end local lower = title:lower() for executable_name, display_name in pairs( names ) do if lower:match( '^' .. executable_name .. '%f[%W]' ) then return display_name end end return nil end local function state( progress ) if progress == 'Indeterminate' then return 'working', 1 end if type( progress ) == 'table' and progress.Error ~= nil then return 'error', 0 end if type( progress ) == 'table' and progress.Percentage ~= nil then return 'working', 1 end return 'idle', 2 end local function badge( progress ) local progress_state = state( progress ) if progress_state == 'error' then return '✗', progress_state end if progress_state == 'working' then return '◉', progress_state end return nil, progress_state end local function call( object, method ) local fn = object and object[method] if type( fn ) ~= 'function' then return nil end local ok, value = pcall( fn, object ) return ok and value or nil end local function sort_entries( entries ) table.sort( entries, function ( left, right ) if left.priority ~= right.priority then return left.priority < right.priority end if left.workspace ~= right.workspace then return left.workspace:lower() < right.workspace:lower() end if left.agent ~= right.agent then return left.agent < right.agent end return left.id < right.id end ) return entries end local function scan( windows ) local panes = {} for _, mux_window in ipairs( windows or {} ) do local workspace = call( mux_window, 'get_workspace' ) or 'default' for _, tab in ipairs( call( mux_window, 'tabs' ) or {} ) do for _, pane_info in ipairs( call( tab, 'panes_with_info' ) or {} ) do local pane = pane_info.pane local pane_id = call( pane, 'pane_id' ) or pane_info.pane_id local title = call( pane, 'get_title' ) or '' local process = call( pane, 'get_foreground_process_name' ) local agent = detect( title, process, call( pane, 'get_user_vars' ) ) local progress_state, priority if agent then progress_state, priority = state( call( pane, 'get_progress' ) ) end table.insert( panes, { id = tostring( pane_id ), pane_id = pane_id, pane = pane, workspace = workspace, title = title, process = process, agent = agent, state = progress_state, priority = priority, } ) end end end return panes end local function collect( windows ) local entries = {} for _, pane in ipairs( scan( windows ) ) do if pane.agent then table.insert( entries, pane ) end end return sort_entries( entries ) end local function from_instances( rows ) local entries = {} for _, row in ipairs( rows or {} ) do local agent = row.agent if agent then local state = row.state or 'unknown' local priority = tonumber( row.priority ) or ({ error = 0, working = 1, idle = 2, unknown = 3, })[state] or 3 table.insert( entries, { id = instances.target_id( row.gui_pid, row.pane_id ), gui_pid = row.gui_pid, pane_id = row.pane_id, workspace = row.workspace or 'default', title = row.title or '', agent = agent, state = state, priority = priority, } ) end end return sort_entries( entries ) end local last_snapshot = nil local function write_snapshot() if not instances.enabled() then return end local ok, windows = pcall( wez.mux.all_windows ) if not ok then return end local panes = {} for _, pane in ipairs( scan( windows ) ) do table.insert( panes, { pane_id = pane.pane_id, workspace = pane.workspace, title = pane.title, process = pane.process, agent = pane.agent, state = pane.state, priority = pane.priority, } ) end local payload = wez.json_encode { panes = panes, } if payload == last_snapshot then return end local pid = instances.current_pid() local runtime = os.getenv 'XDG_RUNTIME_DIR' if not pid or not runtime then return end local path = string.format( '%s/wezterm/instances-%d.json', runtime, pid ) local temporary = path .. '.tmp' local file, open_error = io.open( temporary, 'w' ) if not file then wez.log_error( 'failed to open agent snapshot: ' .. tostring( open_error ) ) return end local wrote, write_error = file:write( payload ) file:close() if not wrote then wez.log_error( 'failed to write agent snapshot: ' .. tostring( write_error ) ) return end local renamed, rename_error = os.rename( temporary, path ) if not renamed then wez.log_error( 'failed to publish agent snapshot: ' .. tostring( rename_error ) ) return end last_snapshot = payload end local function all_entries() if instances.enabled() then write_snapshot() local rows = instances.list() if rows then return from_instances( rows ) end end local ok, windows = pcall( wez.mux.all_windows ) return collect( ok and windows or {} ) end local function state_marker( state ) if state == 'error' then return '✗' end if state == 'working' then return '●' end if state == 'idle' then return '○' end return '?' end local function switcher() return wez.action_callback( function ( window, pane ) local entries = all_entries() if #entries == 0 then window:toast_notification( 'WezTerm', 'No agent panes found', nil, 3000 ) return end local targets = {} local choices = {} for _, entry in ipairs( entries ) do targets[entry.id] = entry local title = clean( entry.title ) or entry.agent table.insert( choices, { id = entry.id, label = string.format( '%s %s %-7s %s %s', state_marker( entry.state ), entry.agent, entry.state, entry.workspace, wez.truncate_right( title, 48 ) ), } ) end window:perform_action( act.InputSelector { title = 'Agent panes', description = 'Failed and working agents first', fuzzy_description = 'Agent: ', fuzzy = true, choices = choices, action = wez.action_callback( function ( inner_window, inner_pane, id ) local target = id and targets[id] or nil if not target then return end if target.gui_pid then if not instances.activate( target.gui_pid, target.pane_id ) then inner_window:toast_notification( 'WezTerm', 'Could not activate agent pane', nil, 3000 ) end return end inner_window:perform_action( act.SwitchToWorkspace { name = target.workspace, }, inner_pane ) target.pane:activate() end ), }, pane ) end ) end return { badge = badge, identity = identity, program = program, write_snapshot = write_snapshot, switcher = switcher, }