1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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")
|