summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md21
-rw-r--r--_selfcheck.lua28
-rw-r--r--panto-herdr/init.lua35
3 files changed, 63 insertions, 21 deletions
diff --git a/README.md b/README.md
index 18e221e..e5017be 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
`panto-herdr` reports panto lifecycle state to [Herdr](https://herdr.dev/) when
panto is running in a Herdr pane. It reports `panto` as the agent label and
-uses `thinking`, `responding`, `compacting`, and `tool:<name>` as short status
-labels.
+uses `thinking`, `responding`, `compacting`, and `tool:<name>` as short report
+messages.
Install it through panto's rock loader:
@@ -26,15 +26,20 @@ its JSON output, so reporting does not affect the panto terminal UI.
| panto event | Herdr state |
| --- | --- |
-| `session_start` | `idle` |
+| `session_start`, `session_ready` | `idle` |
| `user_message`, `thinking`, `compaction`, `tool_details`, `assistant_text` | `working` |
| `assistant_text_complete` | `idle` |
+| `session_end` | agent released |
-Current panto extensions do not receive a turn-complete or interrupt event.
-`assistant_text_complete` is therefore the best available idle boundary; an
-assistant text block followed by a tool call can show `idle` briefly before
-the next lifecycle event restores `working`. Native session restore is also
-out of scope until panto exposes a stable session reference to extensions.
+`session_ready` restores `idle` after panto replays historical lifecycle events
+while loading a resumed session. `session_end` removes panto's agent report when
+the process shuts down, so Herdr also clears the pane/space activity indicator
+once no reported agents remain.
+
+`assistant_text_complete` is the block-level idle boundary; an assistant text
+block followed by a tool call can show `idle` briefly before the next lifecycle
+event restores `working`. Native session restore is out of scope until panto
+exposes a stable session reference to extensions.
## Check
diff --git a/_selfcheck.lua b/_selfcheck.lua
index 8b83fa5..08df12c 100644
--- a/_selfcheck.lua
+++ b/_selfcheck.lua
@@ -16,6 +16,7 @@ end
assert(loadfile(assert(find_rockspec(), "no .rockspec in the repo root")))
+package.loaded.panto = nil
package.preload.panto = function()
return {
ext = {
@@ -24,6 +25,7 @@ package.preload.panto = function()
}
end
+package.loaded.luv = nil
package.preload.luv = function()
return {
spawn = function(command, options, _)
@@ -34,12 +36,14 @@ package.preload.luv = function()
end
local getenv = os.getenv
+local now = 100
+os.time = function() return now end
os.getenv = function(name)
if name == "HERDR_PANE_ID" then return "w1:p1" end
return getenv(name)
end
-local entry = require("panto-herdr")
+local entry = assert(loadfile("panto-herdr/init.lua"))()
entry.activate()
events.session_start()
events.user_message()
@@ -47,11 +51,25 @@ events.user_message()
events.tool_details({ tool_name = "read" })
events.assistant_text()
events.assistant_text_complete()
+events.session_end()
-assert(#reports == 5)
+-- A new process in the same pane/source must outrank the old process's final
+-- sequence number instead of being ignored as stale.
+now = 101
+events = {}
+entry.activate()
+events.session_start()
+events.assistant_text()
+events.session_ready()
+
+assert(#reports == 9)
assert(reports[1].command == "herdr")
-assert(table.concat(reports[1].args, " ") == "pane report-agent w1:p1 --source panto:extension --agent panto --state idle --seq 1")
-assert(table.concat(reports[3].args, " ") == "pane report-agent w1:p1 --source panto:extension --agent panto --state working --seq 3 --custom-status tool:read")
-assert(table.concat(reports[5].args, " ") == "pane report-agent w1:p1 --source panto:extension --agent panto --state idle --seq 5")
+assert(table.concat(reports[1].args, " ") == "pane report-agent w1:p1 --source panto:extension --agent panto --state idle --seq 100000001")
+assert(table.concat(reports[3].args, " ") == "pane report-agent w1:p1 --source panto:extension --agent panto --state working --seq 100000003 --message tool:read")
+assert(table.concat(reports[5].args, " ") == "pane report-agent w1:p1 --source panto:extension --agent panto --state idle --seq 100000005")
+assert(table.concat(reports[6].args, " ") == "pane release-agent w1:p1 --source panto:extension --agent panto --seq 100000006")
+assert(table.concat(reports[7].args, " ") == "pane report-agent w1:p1 --source panto:extension --agent panto --state idle --seq 101000001")
+assert(table.concat(reports[8].args, " ") == "pane report-agent w1:p1 --source panto:extension --agent panto --state working --seq 101000002 --message responding")
+assert(table.concat(reports[9].args, " ") == "pane report-agent w1:p1 --source panto:extension --agent panto --state idle --seq 101000003")
print("panto-herdr self-check: ok")
diff --git a/panto-herdr/init.lua b/panto-herdr/init.lua
index dffef51..cc0b527 100644
--- a/panto-herdr/init.lua
+++ b/panto-herdr/init.lua
@@ -11,10 +11,22 @@ return {
local panto = require("panto")
local uv = require("luv")
- local seq = 0
+ -- Herdr orders reports by source and sequence. Seed each process from
+ -- wall time so a restarted panto cannot be shadowed by the prior
+ -- process's larger sequence numbers.
+ local seq = os.time() * 1000000
local last_state
local last_status
+ local function spawn(args)
+ local handle
+ pcall(function()
+ handle = uv.spawn("herdr", { args = args, stdio = { nil, nil, nil } }, function()
+ if handle then handle:close() end
+ end)
+ end)
+ end
+
local function report(state, status)
if state == last_state and status == last_status then return end
last_state, last_status = state, status
@@ -28,19 +40,25 @@ return {
"--seq", tostring(seq),
}
if status then
- args[#args + 1] = "--custom-status"
+ args[#args + 1] = "--message"
args[#args + 1] = status
end
- local handle
- pcall(function()
- handle = uv.spawn("herdr", { args = args, stdio = { nil, nil, nil } }, function()
- if handle then handle:close() end
- end)
- end)
+ spawn(args)
+ end
+
+ local function release()
+ seq = seq + 1
+ spawn({
+ "pane", "release-agent", pane_id,
+ "--source", "panto:extension",
+ "--agent", "panto",
+ "--seq", tostring(seq),
+ })
end
panto.ext.on("session_start", function() report("idle") end)
+ panto.ext.on("session_ready", function() report("idle") end)
panto.ext.on("user_message", function() report("working", "thinking") end)
panto.ext.on("thinking", function() report("working", "thinking") end)
panto.ext.on("compaction", function() report("working", "compacting") end)
@@ -49,5 +67,6 @@ return {
end)
panto.ext.on("assistant_text", function() report("working", "responding") end)
panto.ext.on("assistant_text_complete", function() report("idle") end)
+ panto.ext.on("session_end", release)
end,
}