diff options
Diffstat (limited to '_selfcheck.lua')
| -rw-r--r-- | _selfcheck.lua | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/_selfcheck.lua b/_selfcheck.lua new file mode 100644 index 0000000..67a84c7 --- /dev/null +++ b/_selfcheck.lua @@ -0,0 +1,161 @@ +-- Run with `lua _selfcheck.lua` or `panto lua _selfcheck.lua`; no live terminal writes. +local events, writes, env = {}, {}, {} +local handle, inode, pid = "tty", 10, 100.0 +local write_error, partial_limit +os.getenv = function(name) return env[name] end +package.loaded.panto = nil +package.preload.panto = function() + return { ext = { on = function(name, handler) events[name] = handler end } } +end +package.loaded.luv = nil +package.preload.luv = function() + return { + guess_handle = function(fd) assert(fd == 1); return handle end, + fs_fstat = function(fd) assert(fd == 1); return { dev = 1, ino = inode, rdev = 2 } end, + os_getpid = function() return pid end, + os_setenv = function(name, value) env[name] = value end, + fs_write = function(fd, data, offset) + assert(fd == 1 and offset == -1) + if write_error then return nil, "EIO" end + local chunk = data:sub(1, partial_limit or #data) + writes[#writes + 1] = chunk + return #chunk + end, + } +end +local entry = assert(loadfile("panto-report-status/init.lua"))() +local function activate() + events = {} + entry.activate() +end +local function latest(state, message) + local last + for body in table.concat(writes):gmatch("\27%]7501;([^\27]*)\27\\") do last = body end + assert(last, "missing report") + assert(last == "state=" .. state .. ":app=panto" .. (message and ":msg=" .. message or ""), last) +end +local function unchanged(fn) + local count = #writes + fn() + assert(#writes == count) +end + +for _, kind in ipairs({ "pipe", "file", "unknown" }) do + handle = kind + activate() + assert(next(events) == nil and #writes == 0) +end +handle = "tty" +activate() +assert(env.PANTO_REPORT_STATUS_OWNER == "1:10:2@100") +local registered = 0 +for _ in pairs(events) do registered = registered + 1 end +assert(registered == 13) +events.session_ready() +latest("idle") +unchanged(events.session_ready) +unchanged(events.render_compaction) +unchanged(events.background_work_end) +unchanged(function() events.turn_end({ reason = "completed" }) end) + +for _, name in ipairs({ "turn_start", "agent_submission", "render_thinking" }) do + events[name]() + latest("working", "dGhpbmtpbmc=") -- thinking + unchanged(events[name]) + events.turn_end({ reason = "completed" }) + latest("done") +end +for _, item in ipairs({ + { "a", "dG9vbDph" }, { "ab", "dG9vbDphYg==" }, { "abc", "dG9vbDphYmM=" }, + { "a\27\n\t\127\194\128", "dG9vbDphPz8/Pz8/" }, +}) do + events.render_tool_details({ tool_name = item[1] }) + latest("working", item[2]) +end +events.render_assistant_text() +latest("working", "cmVzcG9uZGluZw==") -- responding +events.render_compaction() +latest("working", "Y29tcGFjdGluZw==") -- compacting +events.turn_end({ reason = "completed" }) +latest("done") +unchanged(events.render_compaction) + +for _, item in ipairs({ + { "compact", "Y29tcGFjdGluZw==" }, { "!", "c2hlbGw=" }, + { "!!", "c2hlbGw=" }, { "status", "Y21kOnN0YXR1cw==" }, +}) do + events.command_start({ command_name = item[1] }) + latest("working", item[2]) + events.command_end({ command_name = item[1] }) + latest("done") +end + +-- Foreground and background work must settle together, including failures. +events.turn_start() +events.background_work_start() +events.background_work_start() +events.turn_end({ reason = "failed" }) +latest("working", "YmFja2dyb3VuZA==") -- background +unchanged(events.background_work_end) +events.background_work_end() +latest("error") +unchanged(events.background_work_end) +events.turn_start() +events.turn_end({ reason = "completed" }) +latest("done") +for _, reason in ipairs({ "interrupted", "exit_requested" }) do + events.turn_start() + events.turn_end({ reason = reason }) + latest("idle") +end +events.background_work_start() +latest("working", "YmFja2dyb3VuZA==") +events.background_work_end() +latest("done") +events.background_work_start() +events.turn_start() +unchanged(events.background_work_end) +latest("working", "dGhpbmtpbmc=") +events.turn_end() +latest("done") + +-- An output redirected after activation must remain clean. +handle = "pipe" +unchanged(events.turn_start) +handle = "tty" +events.turn_start() +latest("working", "dGhpbmtpbmc=") +-- A failed write must not mark the new state as successfully sent. +write_error = true +local ok, err = pcall(events.render_assistant_text) +assert(not ok and tostring(err):find("panto-report-status:", 1, true)) +write_error = nil +events.render_assistant_text() +latest("working", "cmVzcG9uZGluZw==") +-- Synchronous writes may be short; finish the entire sequence in order. +partial_limit = 3 +events.turn_end() +latest("done") +partial_limit = nil +events.session_end() +latest("clear") +unchanged(events.session_end) +unchanged(function() events.turn_start(); events.background_work_start(); events.background_work_end() end) + +-- /new in the owning process reports; children in the same terminal do not. +activate() +events.session_ready() +latest("idle") +pid = 200.0 +unchanged(activate) +assert(next(events) == nil) +-- A newly created terminal may report, even with an inherited owner marker. +inode = 20 +activate() +assert(env.PANTO_REPORT_STATUS_OWNER == "1:20:2@200") +events.session_ready() +latest("idle") +-- Changing terminals after activation cannot redirect an existing reporter. +inode = 30 +unchanged(events.turn_start) +print("panto-report-status self-check: ok") |
