repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
neovim/tests/e2e/scenarios.lua
Rawreturn function( directory )
local results = {}
local scenario = function( name, body )
local select = vim.ui.select
local session_dir = vim.env.PI_CODING_AGENT_SESSION_DIR
local ok, failure = xpcall( body, debug.traceback )
vim.ui.select = select
vim.env.PI_CODING_AGENT_SESSION_DIR = session_dir
table.insert( results, { name = name, ok = ok, error = failure and ( ': ' .. failure ) or nil, } )
return ok
end
local await = function( predicate, label )
assert( vim.wait( 120000, predicate, 10 ), label or 'predicate deadline' )
end
local pivi = require 'bugabinga.pivi'
local current
local request = function( command, target )
local response
( target or current ).request( command, function( answer ) response = answer end )
await( function() return response ~= nil end, command.type .. ' deadline' )
assert( response.success, response.error )
return response.data
end
local control = function( name, args )
return request { type = 'prompt', message = '/' .. name .. ( args and ( ' ' .. args ) or '' ), }
end
local responses = function( steps ) control( 'e2e-responses', vim.json.encode( steps ) ) end
local text = function() return table.concat( pivi.transcript.lines( current.buffer ), '\n' ) end
local finish = function( command )
local settled = false
local unsubscribe = current.on( function( event ) if event.type == 'agent_settled' then settled = true end end )
command()
await( function() return settled end, 'agent_settled deadline' )
unsubscribe()
request { type = 'get_state', }
assert( #current.client.failures() == 0, table.concat( current.client.failures(), '\n' ) )
assert( current.status.run == 'idle' )
end
local call_count = function()
control 'e2e-count'
request { type = 'get_state', }
return tonumber( pivi.dialog.statuses()['e2e-count'] )
end
local stop = function( live )
live.stop()
await( function() return not live.client.is_running() end, 'Pi shutdown deadline' )
end
local file = directory .. '/source.lua'
vim.fn.writefile( { 'local value = 1', 'return value', }, file )
vim.cmd.edit( file )
local source = vim.api.nvim_get_current_buf()
local working = vim.api.nvim_get_current_win()
if not scenario( 'start: real Pi, additive user config, faux model, no model call', function()
vim.cmd 'Pi start alpha'
current = pivi.session.active()
assert( current.name == 'alpha' )
local state = request { type = 'get_state', }
assert( state.model.provider == 'pivi-e2e', 'refusing metered provider' )
assert( state.sessionId )
local commands = request { type = 'get_commands', }
assert( vim.iter( commands.commands ):any( function( item )
return item.sourceInfo and item.sourceInfo.scope == 'user'
end ), 'configured user resources not loaded' )
assert( call_count() == 0 )
assert( vim.api.nvim_win_get_buf( working ) == source )
end ) then return results end
scenario( 'ask: real streamed markdown, authored text, extmark decoration, cached status', function()
local authored = '# Answer\n\n**native** `code`\n\n```lua\nreturn 42\n```'
responses { { content = authored, }, }
finish( function() vim.cmd 'Pi ask e2e question' end )
assert( text():find( authored, 1, true ) )
assert( not text():find( '## pi', 1, true ) )
assert( #pivi.render.marks( current.buffer ) > 0 )
assert( pivi.summary() == 'pi:idle' )
local before = call_count()
for _ = 1, 100 do pivi.summary() end
assert( before == call_count() )
end )
scenario( 'prompt: native mapping submits, hides without stopping, draft survives', function()
vim.cmd 'Pi prompt'
local prompt = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines( prompt, 0, -1, false, { 'from prompt', } )
responses { { content = 'prompt answer', }, }
finish( function()
local maps = vim.api.nvim_buf_get_keymap( prompt, 'n' )
for _, map in ipairs( maps ) do if map.lhs == '<Plug>(pivi-submit)' then map.callback(); return end end
error 'missing submit mapping'
end )
assert( text():find( 'prompt answer', 1, true ) )
assert( vim.api.nvim_buf_get_lines( prompt, 0, -1, false )[1] == '' )
vim.api.nvim_buf_set_lines( prompt, 0, -1, false, { 'saved draft', } )
vim.api.nvim_win_close( vim.fn.bufwinid( prompt ), true )
assert( current.client.is_running() )
vim.cmd 'Pi prompt'
assert( vim.api.nvim_get_current_buf() == prompt )
assert( vim.api.nvim_get_current_line() == 'saved draft' )
end )
scenario( 'slash: discoverable command dialogs, generic status/widgets, no model call', function()
local before = call_count()
local original = vim.ui.select
vim.ui.select = function( items, _, callback ) callback( items[2] ) end
vim.cmd 'Pi command e2e-dialog'
await( function()
return pivi.dialog.statuses()['e2e-choice'] == 'second' and pivi.dialog.widgets()['e2e-widget'] ~= nil
end )
vim.ui.select = original
assert( pivi.dialog.widgets()['e2e-widget'] )
assert( call_count() == before )
vim.cmd 'Pi command model'
request { type = 'get_state', }
assert( call_count() == before, 'terminal command became a model prompt' )
end )
scenario( 'queue: prompt accept steers, follow-up listed/cleared, abort settles', function()
responses { { content = 'held answer', hold = true, }, }
vim.cmd 'Pi ask hold the run'
await( function() return current.status.run == 'running' end )
vim.cmd 'Pi prompt'
local prompt = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines( prompt, 0, -1, false, { 'steering message', } )
for _, map in ipairs( vim.api.nvim_buf_get_keymap( prompt, 'n' ) ) do
if map.lhs == '<Plug>(pivi-submit)' then map.callback() end
end
vim.cmd 'Pi follow-up follow-up message'
await( function()
return #current.status.queue.steering == 1 and #current.status.queue.follow_up == 1
end )
vim.cmd 'Pi queue'
vim.cmd 'Pi clear'
await( function() return #current.status.queue.steering == 0 and #current.status.queue.follow_up == 0 end )
vim.cmd 'Pi abort'
await( function() return current.status.run == 'idle' end )
assert( request( { type = 'get_state', } ).isStreaming == false )
vim.api.nvim_buf_set_lines( prompt, 0, -1, false, { 'saved draft', } )
end )
scenario( 'delivery: steering/follow-up both reach persisted conversation after release', function()
responses {
{ content = 'initial answer', hold = true, },
{ content = 'steered answer', },
{ content = 'follow-up answer', },
}
local settled = false
local unsubscribe = current.on( function( event ) if event.type == 'agent_settled' then settled = true end end )
vim.cmd 'Pi ask delivery barrier'
await( function() return current.status.run == 'running' end )
vim.cmd 'Pi steer delivered steering'
vim.cmd 'Pi follow-up delivered follow-up'
await( function() return #current.status.queue.steering == 1 and #current.status.queue.follow_up == 1 end )
control 'e2e-release'
await( function() return settled end )
unsubscribe()
local history = vim.json.encode( request { type = 'get_messages', } )
assert( history:find( 'delivered steering', 1, true ) )
assert( history:find( 'delivered follow-up', 1, true ) )
assert( text():find( 'follow-up answer', 1, true ) )
end )
scenario( 'help: real independent faux run while working, live docs, history/queue/status untouched', function()
responses { { content = 'held working answer', hold = true, }, }
vim.cmd 'Pi ask independent help barrier'
await( function() return current.status.run == 'running' end )
local history_before = #request( { type = 'get_entries', } ).entries
local queue_before = vim.deepcopy( current.status.queue )
local revision = vim.api.nvim_buf_get_changedtick( source )
vim.cmd 'Pi help what is quickfix?'
local helpbuf = pivi.transcript.ensure( 'help' )
await( function()
return table.concat( pivi.transcript.lines( helpbuf ), '\n' ):find( 'Live quickfix documentation fetched.', 1, true ) ~= nil
end )
assert( current.status.run == 'running' )
assert( #request( { type = 'get_entries', } ).entries == history_before )
assert( vim.deep_equal( queue_before, current.status.queue ) )
assert( vim.api.nvim_buf_get_changedtick( source ) == revision )
assert( not text():find( 'what is quickfix?', 1, true ) )
vim.cmd 'Pi abort'
await( function() return current.status.run == 'idle' end )
end )
scenario( 'help safety: real malicious faux Lua/write calls cannot change editor/files', function()
local revision = vim.api.nvim_buf_get_changedtick( source )
local original = vim.fn.readfile( file )
local run = pivi.help.ask( 'attempt mutation', { env = vim.tbl_extend( 'force', pivi.runtime.bridge_environment(), {
PIVI_E2E_ATTACK = 'vim.api.nvim_buf_set_lines(' .. source .. ', 0, -1, false, {"mutated"})',
PIVI_E2E_ATTACK_FILE = file,
} ), } )
await( function() return run.done end )
await( function() return not run.client.is_running() end )
assert( run.answer == 'Mutation attempts finished.' )
assert( vim.api.nvim_buf_get_changedtick( source ) == revision, 'help Lua modified buffer' )
assert( vim.deep_equal( original, vim.fn.readfile( file ) ), 'help wrote file' )
end )
scenario( 'tools: unsaved reads authoritative, editor state/Lua cross-process bridge', function()
vim.api.nvim_buf_set_lines( source, 0, 1, false, { 'local value = 7', } )
vim.api.nvim_buf_call( source, function() vim.cmd 'let &undolevels = &undolevels' end )
responses {
{ content = {
{ type = 'toolCall', id = 'read-live', name = 'read', arguments = { path = file, }, },
}, stopReason = 'toolUse', },
{ content = {
{ type = 'toolCall', id = 'lua-live', name = 'nvim_lua', arguments = { code = 'return vim.api.nvim_buf_get_lines(' .. source .. ', 0, -1, false)', }, },
}, stopReason = 'toolUse', },
{ content = 'tools finished', },
}
finish( function() vim.cmd 'Pi ask inspect live editor' end )
assert( text():find( 'local value = 7', 1, true ) )
local tools = vim.tbl_filter( function( item ) return item.kind == 'tool' end, pivi.entry.list( current.buffer ) )
assert( #tools >= 2 )
for _, item in ipairs( tools ) do assert( not item.meta.failed, vim.inspect( item.meta ) ) end
end )
scenario( 'edit: real Pi tool, inert streaming ghost, local mutation, disk coherence, trail, one undo', function()
local revision = vim.api.nvim_buf_get_changedtick( source )
local ghost = false
local ghost_error
local unsubscribe = current.on( function( event )
if event.type == 'message_update' and #pivi.activity.ghost_marks( source ) > 0 then
ghost = true
if vim.api.nvim_buf_get_changedtick( source ) ~= revision then ghost_error = 'streaming arguments mutated buffer' end
end
end )
responses {
{ content = { { type = 'toolCall', id = 'edit-live', name = 'edit', arguments = {
path = file, oldText = 'local value = 7', newText = 'local value = 8',
}, }, }, stopReason = 'toolUse', },
{ content = 'edit finished', },
}
finish( function() vim.cmd 'Pi ask edit live buffer' end )
unsubscribe()
assert( ghost, 'real streamed arguments never showed ghost text' )
assert( not ghost_error, ghost_error )
assert( vim.api.nvim_buf_get_lines( source, 0, 1, false )[1] == 'local value = 8' )
assert( vim.fn.readfile( file )[1] == 'local value = 8' )
assert( #pivi.activity.trail( source ) > 0 )
local item = pivi.entry.list( current.buffer )
local edited
for _, candidate in ipairs( item ) do if candidate.meta.call == 'edit-live' then edited = candidate end end
assert( edited and edited.meta.result.details.diff )
local win = vim.fn.bufwinid( current.buffer )
vim.api.nvim_win_set_cursor( win, { edited.first, 0, } )
local recorded = #request( { type = 'get_entries', } ).entries
vim.api.nvim_win_call( win, function()
assert( pivi.fold.closed( edited.first ) )
vim.cmd 'normal! zo'
assert( not pivi.fold.closed( edited.first ) )
vim.cmd 'normal! zc'
vim.cmd 'Pi diff'
end )
assert( vim.api.nvim_win_get_buf( working ) == source )
assert( #request( { type = 'get_entries', } ).entries == recorded )
pivi.comparison.close()
vim.api.nvim_buf_call( source, function() vim.cmd 'undo' end )
assert( vim.api.nvim_buf_get_lines( source, 0, 1, false )[1] == 'local value = 7' )
end )
scenario( 'findings/follow/failure: real tools, native qf, dedicated viewport, red outcome', function()
vim.api.nvim_set_current_win( working )
vim.cmd 'Pi follow window'
responses {
{ content = { { type = 'toolCall', id = 'findings-live', name = 'nvim_findings', arguments = {
title = 'e2e findings', items = { { path = file, line = 2, text = 'finding', }, },
}, }, }, stopReason = 'toolUse', },
{ content = { { type = 'toolCall', id = 'follow-live', name = 'read', arguments = { path = file, }, }, }, stopReason = 'toolUse', },
{ content = { { type = 'toolCall', id = 'failed-live', name = 'read', arguments = { path = directory .. '/absent.txt', }, }, }, stopReason = 'toolUse', },
{ content = 'findings finished', },
}
finish( function() vim.cmd 'Pi ask report findings' end )
assert( vim.fn.getqflist()[1].text == 'finding' )
assert( pivi.follow.window() and pivi.follow.window() ~= working )
assert( vim.api.nvim_get_current_win() == working )
local failed
for _, item in ipairs( pivi.entry.list( current.buffer ) ) do
if item.meta.call == 'failed-live' then failed = item end
end
assert( failed and failed.meta.failed and failed.first == failed.last, vim.inspect( failed ) )
vim.cmd 'Pi follow'
end )
scenario( 'native entry motions/detail/location: real restored-capable tool surfaces preserve source', function()
local edited
for _, item in ipairs( pivi.entry.list( current.buffer ) ) do
if item.meta.call == 'edit-live' then edited = item end
end
local win = pivi.transcript.show( current.buffer )
vim.api.nvim_set_current_win( win )
vim.api.nvim_win_set_cursor( win, { edited.first, 0, } )
local invoke = function( intent )
for _, map in ipairs( vim.api.nvim_buf_get_keymap( current.buffer, 'n' ) ) do
if map.lhs == intent then map.callback(); return end
end
error( 'missing intent ' .. intent )
end
invoke '<Plug>(pivi-next-tool)'
assert( vim.api.nvim_win_get_cursor( win )[1] > edited.first )
invoke '<Plug>(pivi-previous-tool)'
assert( vim.api.nvim_win_get_cursor( win )[1] == edited.first )
invoke '<Plug>(pivi-inspect-entry)'
local detail
for _, candidate in ipairs( vim.api.nvim_list_wins() ) do
if vim.api.nvim_win_get_config( candidate ).relative ~= '' then detail = candidate end
end
assert( detail and vim.api.nvim_buf_get_name( vim.api.nvim_win_get_buf( detail ) ):find( '/detail', 1, true ) )
vim.api.nvim_win_close( detail, true )
invoke '<Plug>(pivi-goto-location)'
assert( pivi.follow.window() ~= working )
assert( vim.api.nvim_win_get_cursor( pivi.follow.window() )[1] == edited.meta.result.details.firstChangedLine )
assert( vim.api.nvim_win_get_buf( working ) == source )
pivi.follow.reset()
end )
scenario( 'selection: unsaved queued source/revision retained through mutation and delivery', function()
vim.api.nvim_set_current_win( working )
vim.api.nvim_buf_set_lines( source, 0, 1, false, { 'local selected = "unsaved original"', } )
responses { { content = 'selection held', hold = true, }, { content = 'selection delivered', }, }
vim.cmd '1,1Pi ask selection snapshot'
await( function() return current.status.run == 'running' end )
local shot = current.submissions[#current.submissions].snapshot
assert( shot.selected_text == 'local selected = "unsaved original"' and shot.modified )
local revision = shot.revision
current.submit( 'queued selection', { delivery = 'follow_up', source = {
bufnr = source, win = working, range = { kind = 'range', first = { 1, 0, }, last = { 1, 0, }, },
}, } )
await( function() return #current.status.queue.follow_up == 1 end )
local queued = current.submissions[#current.submissions].snapshot
vim.api.nvim_buf_set_lines( source, 0, 1, false, { 'local selected = "later mutation"', } )
assert( queued.revision == revision and queued.selected_text == shot.selected_text )
control 'e2e-release'
await( function() return current.status.run == 'idle' end )
local persisted = vim.json.encode( request { type = 'get_messages', } )
assert( persisted:find( 'unsaved original', 1, true ) and not persisted:find( 'later mutation', 1, true ) )
vim.api.nvim_buf_set_lines( source, 0, -1, false, { 'local value = 7', 'return value', } )
end )
scenario( 'loclist/diagnostics/plugin docs: real companion tools preserve unrelated namespaces', function()
local foreign = vim.api.nvim_create_namespace( 'e2e-foreign-diagnostics' )
vim.diagnostic.set( foreign, source, { { lnum = 0, col = 0, message = 'foreign', }, } )
local tags = vim.fn.getcompletion( 'plenary', 'help' )
assert( #tags > 0, 'installed plenary docs unavailable' )
responses {
{ content = { { type = 'toolCall', id = 'native-lists', name = 'nvim_lua', arguments = { code =
'local a = require("bugabinga.pivi.activity"); a.window_findings(' .. working .. ', {{buffer=' .. source ..
',line=2,text="local finding"}}); a.diagnose(' .. source .. ', {{line=2,text="agent finding"}}); return true',
}, }, }, stopReason = 'toolUse', },
{ content = { { type = 'toolCall', id = 'plugin-doc', name = 'nvim_help', arguments = { tag = tags[1], }, }, },
stopReason = 'toolUse', },
{ content = 'native lists and plugin docs finished', },
}
finish( function() vim.cmd 'Pi ask native lists and plugin documentation' end )
assert( vim.fn.getloclist( working )[1].text == 'local finding' )
assert( pivi.activity.diagnostics( source )[1].source == 'pi' )
assert( vim.diagnostic.get( source, { namespace = foreign, } )[1].message == 'foreign' )
vim.api.nvim_win_call( working, function() vim.cmd 'lfirst' end )
assert( vim.api.nvim_win_get_cursor( working )[1] == 2 )
local docs
for _, item in ipairs( pivi.entry.list( current.buffer ) ) do if item.meta.call == 'plugin-doc' then docs = item end end
assert( docs and not docs.meta.failed and pivi.tool.produced( docs.meta ):find( 'plenary', 1, true ) )
vim.diagnostic.reset( foreign, source )
pivi.activity.clear_diagnostics( source )
end )
scenario( 'help payload: originating visible region/layout/selection/messages/error bounded on real wire', function()
vim.api.nvim_set_current_win( working )
local lines = { 'visible-origin-sentinel', }
for index = 2, 600 do lines[index] = 'hidden-line-' .. index end
lines[600] = 'hidden-tail-sentinel'
vim.api.nvim_buf_set_lines( source, 0, -1, false, lines )
vim.api.nvim_win_set_cursor( working, { 1, 0, } )
vim.api.nvim_win_call( working, function() vim.cmd 'normal! zt' end )
vim.fn.setpos( "'<", { source, 1, 1, 0, } )
vim.fn.setpos( "'>", { source, 1, 5, 0, } )
vim.api.nvim_echo( { { 'recent-message-sentinel', }, }, true, {} )
vim.v.errmsg = 'last-error-sentinel'
local run = pivi.help.ask( 'bounded live help' )
local message = run.client.sent[1].message
assert( message:find( 'visible-origin-sentinel', 1, true ) )
assert( not message:find( 'hidden-tail-sentinel', 1, true ) )
assert( message:find( 'selection', 1, true ) and message:find( 'windows:', 1, true ) )
assert( message:find( 'recent-message-sentinel', 1, true ) and message:find( 'last-error-sentinel', 1, true ) )
assert( #message < 20000 )
await( function() return run.done and not run.client.is_running() end )
vim.v.errmsg = ''
vim.api.nvim_buf_set_mark( source, '<', 0, 0, {} )
vim.api.nvim_buf_set_mark( source, '>', 0, 0, {} )
vim.api.nvim_buf_set_lines( source, 0, -1, false, { 'local value = 7', 'return value', } )
end )
scenario( 'search: real upstream grep grouped decoration, authored output, native quickfix', function()
local second = directory .. '/second.lua'
vim.fn.writefile( { 'local value = 2', 'return value', }, second )
responses {
{ content = { { type = 'toolCall', id = 'grep-live', name = 'grep', arguments = {
pattern = 'value', path = directory, glob = '*.lua', literal = true,
}, }, }, stopReason = 'toolUse', },
{ content = 'search finished', },
}
finish( function() vim.cmd 'Pi ask search values' end )
local searched
for _, item in ipairs( pivi.entry.list( current.buffer ) ) do if item.meta.call == 'grep-live' then searched = item end end
assert( searched and not searched.meta.failed )
assert( #pivi.tool.matches( searched.meta ) >= 4 )
assert( #vim.fn.getqflist() >= 4 )
local headings = 0
for _, mark in ipairs( pivi.render.marks( current.buffer ) ) do
if mark[2] >= searched.first and mark[2] < searched.last and mark[4].virt_lines then headings = headings + 1 end
end
assert( headings >= 2, 'missing file groups' )
vim.api.nvim_win_call( working, function() vim.cmd 'cfirst' end )
assert( vim.api.nvim_win_get_cursor( working )[1] >= 1 )
vim.api.nvim_win_set_buf( working, source )
end )
scenario( 'bash: native RPC progressive deltas, truncation/full-output navigation without model', function()
local release = directory .. '/shell-release'
local script = directory .. '/shell.cjs'
vim.fn.writefile( {
'const fs = require("node:fs"); console.log("shell-first-sentinel");',
'function poll() { if (!fs.existsSync(process.argv[2])) return setImmediate(poll);',
'for (let i = 0; i < 3000; i++) console.log("line-" + i + " " + "x".repeat(80)); } poll();',
}, script )
local ended, answer = false, nil
assert( current.bash( 'node "' .. vim.fs.normalize( script ) .. '" "' .. vim.fs.normalize( release ) .. '"', true,
function( response ) ended, answer = true, response end ) )
local streamed = vim.wait( 20000, function() return ended or text():find( 'shell-first-sentinel', 1, true ) ~= nil end, 10 )
vim.fn.writefile( { 'release', }, release )
assert( streamed and not ended, vim.inspect( answer or current.client.failures() ) )
vim.fn.writefile( { 'release', }, release )
await( function() return ended end )
local items = pivi.entry.list( current.buffer )
local shell = items[#items]
assert( not shell.meta.failed, vim.inspect( shell.meta ) )
assert( pivi.tool.truncated( shell.meta ) )
local full = pivi.tool.full_output( shell.meta )
assert( full and vim.fn.filereadable( full ) == 1 )
assert( #vim.fn.readfile( full ) >= 3001 )
local win = pivi.transcript.show( current.buffer )
vim.api.nvim_set_current_win( win )
vim.api.nvim_win_set_cursor( win, { shell.first, 0, } )
pivi.navigate.locate( current.buffer )
assert( vim.fs.normalize( vim.api.nvim_buf_get_name( vim.api.nvim_win_get_buf( pivi.follow.window() ) ) ) == vim.fs.normalize( full ) )
pivi.follow.reset()
vim.fn.delete( full )
end )
scenario( 'model/thinking/name/stats: native RPC commands without global persistence', function()
vim.cmd 'Pi model pivi-e2e/faux-1'
vim.cmd 'Pi thinking off'
vim.cmd 'Pi name persisted task'
vim.cmd 'Pi session'
await( function()
local data = request { type = 'get_state', }
return data.sessionName == 'persisted task'
end )
assert( request( { type = 'get_session_stats', } ).cost == 0 )
end )
local identity, history, old_client
scenario( 'restart: real child replaced, identity/history/place/draft retained, prompt rebound', function()
identity = request( { type = 'get_state', } ).sessionId
history = request( { type = 'get_entries', } ).entries
old_client = current.client
local transcript = current.buffer
local win = vim.fn.bufwinid( transcript )
vim.api.nvim_win_set_cursor( win, { 2, 0, } )
vim.cmd 'Pi restart'
await( function()
pivi = require 'bugabinga.pivi'
local resumed = pivi.session.active()
if resumed and resumed ~= current and resumed.restored then current = resumed; return true end
return false
end, 'restart restoration deadline' )
assert( not old_client.is_running(), 'old Pi child remains alive' )
assert( current.buffer == transcript )
assert( request( { type = 'get_state', } ).sessionId == identity )
assert( #request( { type = 'get_entries', } ).entries == #history )
assert( vim.api.nvim_win_get_cursor( win )[1] == 2 )
assert( call_count() == 0, 'restart called model' )
local tool_entries = vim.tbl_filter( function( item ) return item.kind == 'tool' end, pivi.entry.list( current.buffer ) )
assert( #tool_entries >= 3, 'tool history missing' )
assert( vim.iter( tool_entries ):any( function( item )
return item.meta.call == 'edit-live' and item.meta.result.details and item.meta.result.details.diff ~= nil
end ), 'recorded edit details lost during restoration' )
vim.cmd 'Pi prompt'
local prompt = vim.api.nvim_get_current_buf()
assert( vim.api.nvim_get_current_line() == 'saved draft' )
responses { { content = 'rebound answer', }, }
vim.api.nvim_buf_set_lines( prompt, 0, -1, false, { 'after restart', } )
finish( function()
for _, map in ipairs( vim.api.nvim_buf_get_keymap( prompt, 'n' ) ) do
if map.lhs == '<Plug>(pivi-submit)' then map.callback(); return end
end
end )
assert( text():find( 'rebound answer', 1, true ) )
end )
scenario( 'sessions: create/switch active, source buffer independent, stop explicit', function()
vim.cmd 'Pi start beta'
local beta = pivi.session.active()
request( { type = 'get_state', }, beta )
assert( beta.name == 'beta' )
local original = vim.ui.select
vim.ui.select = function( items, _, callback ) callback( 'alpha' ) end
vim.cmd 'Pi sessions'
vim.ui.select = original
assert( pivi.session.active().name == 'alpha' )
local alpha_id = request( { type = 'get_state', } ).sessionId
local focused = vim.api.nvim_get_current_win()
vim.cmd 'Pi restart'
await( function()
pivi = require 'bugabinga.pivi'
local alpha, other = pivi.session.get( 'alpha' ), pivi.session.get( 'beta' )
if alpha and other and alpha.restored and other.restored then
current, beta = alpha, other
return true
end
return false
end )
assert( pivi.session.active().name == 'alpha' )
assert( vim.api.nvim_get_current_win() == focused )
assert( request( { type = 'get_state', } ).sessionId == alpha_id )
stop( beta )
assert( current.client.is_running() )
end )
scenario( 'resume latest: persistent identity/history, no new conversation/model call', function()
identity = request( { type = 'get_state', } ).sessionId
stop( current )
vim.cmd 'Pi resume'
current = pivi.session.active()
await( function() return current.restored end )
assert( request( { type = 'get_state', } ).sessionId == identity )
assert( text():find( 'rebound answer', 1, true ) )
assert( call_count() == 0 )
end )
scenario( 'resume identity: editor completion reads real Pi store, history no model call', function()
stop( current )
local stored = pivi.store.sessions( directory )
assert( #stored > 0 )
assert( vim.list_contains( vim.fn.getcompletion( 'Pi resume ', 'cmdline' ), identity ) )
vim.cmd( 'Pi resume ' .. identity )
current = pivi.session.active()
await( function() return current.restored end )
assert( request( { type = 'get_state', } ).sessionId == identity )
assert( call_count() == 0 )
end )
scenario( 'prior picker: choose real persisted identity, no model call', function()
stop( current )
local original = vim.ui.select
local picked
vim.ui.select = function( items, options, callback )
assert( options.prompt == 'pi prior session' )
for _, item in ipairs( items ) do if item.id == identity then picked = item; callback( item ); return end end
error 'saved identity absent from picker'
end
local ok, failure = pcall( vim.cmd, 'Pi prior' )
vim.ui.select = original
assert( ok, failure )
assert( picked )
current = pivi.session.active()
await( function() return current.restored end )
assert( request( { type = 'get_state', } ).sessionId == identity and call_count() == 0 )
end )
scenario( 'descriptor missing identity: quiet no restoration, no fallback child', function()
local before = #_G.PIVI_E2E_CHILDREN
assert( #pivi.restore( { { name = 'absent', session = 'missing-id', cwd = directory, }, } ) == 0 )
assert( #pivi.restore( nil ) == 0 and #_G.PIVI_E2E_CHILDREN == before )
assert( pivi.session.get( 'absent' ) == nil )
end )
scenario( 'descriptor: idempotent native-session consumer restore', function()
local descriptor = pivi.descriptor( directory )
assert( #descriptor == 1 and descriptor[1].session == identity )
assert( #pivi.restore( descriptor ) == 0 )
stop( current )
assert( #pivi.restore( descriptor ) == 1 )
current = pivi.session.active()
await( function() return current.restored end )
assert( request( { type = 'get_state', } ).sessionId == identity )
end )
scenario( 'branched restore: recorded native tree selects active ancestry and tool details only', function()
local state = request { type = 'get_state', }
local records = vim.fn.readfile( state.sessionFile )
local header = vim.json.decode( records[1] )
header.id = 'e2e-branched-history'
local output = { vim.json.encode( header ), }
local originals = {}
for index = 2, #records do originals[#originals + 1] = vim.json.decode( records[index] ) end
local previous = vim.NIL
local append = function( original )
local record = vim.deepcopy( original )
record.id = string.format( '%08x', #output )
record.parentId = previous
previous = record.id
output[#output + 1] = vim.json.encode( record )
end
local naming
for _, record in ipairs( originals ) do if record.type == 'session_info' and record.name then naming = record end end
assert( naming, 'native recorded session name required' )
append( naming )
local shared = previous
for _, original in ipairs( originals ) do
local record = vim.deepcopy( original )
if record.type == 'message' then
if record.message.role == 'user' then record.message.content = 'DISCARDED_BRANCH_MARKER' end
if record.message.role == 'toolResult' then
record.message.content = { { type = 'text', text = 'DISCARDED_TOOL_MARKER', }, }
record.message.details = { diff = 'DISCARDED_DIFF_MARKER', }
end
end
append( record )
end
previous = shared
for _, record in ipairs( originals ) do append( record ) end
local path = directory .. '/branched.jsonl'
vim.fn.writefile( output, path )
local live = pivi.session.create { name = 'branched', resume = path, }
await( function() return live.restored end )
local data = request( { type = 'get_entries', }, live )
assert( #data.entries == #output - 1 and data.leafId == previous )
local expected = require( 'bugabinga.pivi.backfill' ).plan( data )
local actual = pivi.entry.list( live.buffer )
assert( #actual == #expected and #actual > 0 )
local rendered = table.concat( vim.api.nvim_buf_get_lines( live.buffer, 0, -1, false ), '\n' )
assert( not rendered:find( 'DISCARDED_', 1, true ), 'discarded branch rendered' )
local paired = false
for _, item in ipairs( actual ) do
if item.kind == 'tool' and item.meta.call == 'edit-live' then
assert( item.meta.result.details.diff and item.meta.result.details.diff ~= 'DISCARDED_DIFF_MARKER' )
paired = true
end
end
assert( paired, 'selected native tool details missing' )
request( { type = 'prompt', message = '/e2e-count', }, live )
request( { type = 'get_state', }, live )
local counted
for _, event in ipairs( live.client.events ) do
if event.type == 'extension_ui_request' and event.statusKey == 'e2e-count' then counted = event.statusText end
end
assert( tonumber( counted ) == 0, 'branched restoration called model' )
stop( live )
pivi.session.set_active( current.name )
end )
scenario( 'large restore: 2000 real message copies, main-loop yields, addressable history, no model', function()
local state = request { type = 'get_state', }
local records = vim.fn.readfile( state.sessionFile )
local header = vim.json.decode( records[1] )
header.id = 'e2e-large-history'
local prompt, answer, model, naming
for _, line in ipairs( records ) do
local record = vim.json.decode( line )
if record.type == 'session_info' and record.name then naming = record end
if record.type == 'model_change' then model = record end
if record.type == 'message' and record.message.role == 'user' then prompt = record end
if record.type == 'message' and record.message.role == 'assistant' and record.message.stopReason == 'stop' then answer = record end
end
assert( prompt and answer )
local output = { vim.json.encode( header ), }
local previous = vim.NIL
local append = function( original )
local record = vim.deepcopy( original )
record.id = string.format( '%08x', #output )
record.parentId = previous
previous = record.id
table.insert( output, vim.json.encode( record ) )
end
assert( naming, 'recorded session name required to avoid user auto-naming model calls' )
append( naming )
if model then append( model ) end
for _ = 1, 1000 do append( prompt ); append( answer ) end
local path = directory .. '/large.jsonl'
vim.fn.writefile( output, path )
local live, timer
local ok, failure = xpcall( function()
live = pivi.session.create { name = 'large', resume = path, }
local yields = 0
local rejected_new = false
timer = assert( vim.uv.new_timer() )
timer:start( 1, 1, vim.schedule_wrap( function()
if #pivi.entry.list( live.buffer ) > 0 and not live.restored then
yields = yields + 1
if not rejected_new then
pivi.session.set_active( live.name )
local before = #live.client.sent
vim.cmd 'Pi new'
assert( #live.client.sent == before, 'new_session sent during yielded history restoration' )
rejected_new = true
end
end
end ) )
local started = vim.uv.hrtime()
local completed = vim.wait( 120000, function() return live.restored end, 10 )
timer:stop()
timer:close()
assert( completed, 'large history restoration deadline' )
local elapsed = ( vim.uv.hrtime() - started ) / 1e6
assert( yields > 1, 'restoration blocked main loop' )
assert( rejected_new, 'new during yielded restoration never exercised' )
assert( #pivi.entry.list( live.buffer ) == 2000 )
local restored = request( { type = 'get_entries', }, live ).entries
assert( #restored >= #output - 1 )
assert( #vim.tbl_filter( function( item )
return item.type == 'message' and ( item.message.role == 'user' or item.message.role == 'assistant' )
end, restored ) == 2000 )
request( { type = 'prompt', message = '/e2e-count', }, live )
request( { type = 'get_state', }, live )
local counted
for _, event in ipairs( live.client.events ) do
if event.type == 'extension_ui_request' and event.statusKey == 'e2e-count' then counted = event.statusText end
end
assert( tonumber( counted ) == 0, 'restoration model calls: ' .. tostring( counted ) )
assert( elapsed < 10000, 'large restoration exceeded 10s budget: ' .. elapsed .. 'ms' )
local saved = pivi.descriptor( directory )
local descriptor
for _, item in ipairs( saved ) do if item.name == 'large' then descriptor = item end end
assert( descriptor and vim.fs.normalize( descriptor.file ) == vim.fs.normalize( path ), vim.inspect( descriptor ) )
vim.env.PI_CODING_AGENT_SESSION_DIR = nil
vim.cmd 'Pi restart'
await( function()
pivi = require 'bugabinga.pivi'
local resumed, other = pivi.session.get( 'large' ), pivi.session.get( current.name )
if resumed and other and resumed.restored and other.restored then
live, current = resumed, other
return true
end
return false
end, 'arbitrary file restart deadline' )
assert( request( { type = 'get_state', }, live ).sessionId == header.id )
assert( #request( { type = 'get_entries', }, live ).entries == #restored )
assert( #pivi.entry.list( live.buffer ) == 2000 )
end, debug.traceback )
if timer and not timer:is_closing() then timer:stop(); timer:close() end
pivi = require 'bugabinga.pivi'
live = pivi.session.get( 'large' ) or live
local cleaned, cleanup_error = pcall( function() if live then stop( live ) end end )
current = pivi.session.get( current.name ) or current
pivi.session.set_active( current.name )
if vim.api.nvim_win_is_valid( working ) then vim.api.nvim_set_current_win( working ) end
assert( cleaned, cleanup_error )
assert( ok, failure )
end )
scenario( 'new/export: fresh history in same process, HTML export temp only', function()
local client = current.client
vim.cmd 'Pi new'
await( function() return #request( { type = 'get_messages', } ).messages == 0 end )
assert( current.client == client )
responses { { content = 'fresh answer', }, }
finish( function() vim.cmd 'Pi ask fresh question' end )
assert( not text():find( 'rebound answer', 1, true ) )
local output = directory .. '/export.html'
vim.cmd( 'Pi export ' .. output )
await( function() return vim.fn.filereadable( output ) == 1 end )
end )
scenario( 'stop: owned Pi child exits, published state removed', function()
if vim.env.PIVI_E2E_INJECT_HANDLER_FAILURE == '1' then
local handled = false
current.request( { type = 'get_state', }, function()
handled = true
error 'injected owned RPC handler failure'
end )
await( function() return handled end )
request { type = 'get_state', }
end
vim.cmd 'Pi stop'
await( function() return not current.client.is_running() end )
assert( #pivi.session.list() == 0 )
assert( pivi.summary() == '' )
end )
return results
end