summaryrefslogtreecommitdiff
path: root/init.lua
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-10-27 16:25:43 -0600
committert <t@tjp.lol>2026-10-27 16:25:47 -0600
commit009d5705a59bcf86f3579a78e25c8b3fcd4cd476 (patch)
treec6496041305da98deac9e40531fbeec0874268f1 /init.lua
Add panto-rsvp: RSVP reading mode for assistant text
Standalone opt-in extension that plays the latest assistant text block one word at a time above the input, using the core mount, input-mode and highlight("markdown") APIs settled in pantograph's rsvp-extension-plan. Registers /rsvp; users bind it via "ctrl+s" = "/rsvp" under [keys]. Player and config logic are pure Lua with an assert-based self-check run against a host/timer stub, since the core APIs land separately.
Diffstat (limited to 'init.lua')
-rw-r--r--init.lua67
1 files changed, 67 insertions, 0 deletions
diff --git a/init.lua b/init.lua
new file mode 100644
index 0000000..532152a
--- /dev/null
+++ b/init.lua
@@ -0,0 +1,67 @@
+local config = require("rsvp.config")
+local Player = require("rsvp.player").Player
+
+local function activate()
+ local panto = require("panto")
+ local ext = panto.ext
+ local settings, err = config.read(ext.config and ext.config.rsvp)
+ if not settings then error("panto-rsvp: " .. err) end
+ local uv = require("luv")
+
+ local player = Player.new {
+ settings = settings,
+ highlight = function(body) return ext.highlight(body, "markdown") end,
+ width = panto.text.width,
+ truncate = panto.text.truncate,
+ style = function() return ext.style() end,
+ new_timer = function() return uv.new_timer() end,
+ mount = function(component, placement) return ext.mount(component, placement) end,
+ input_mode = function(spec) return ext.input_mode(spec) end,
+ }
+
+ local function subscribe(name, handler)
+ ext.on(name, handler)
+ end
+
+ subscribe("session_ready", function()
+ local ok, text = pcall(function()
+ local latest
+ for _, message in ipairs(ext.agent:conversation():messages()) do
+ if message.role == "assistant" then
+ for _, block in ipairs(message.blocks or {}) do
+ if block.type == "text" then latest = block.text or "" end
+ end
+ end
+ end
+ return latest or ""
+ end)
+ player:snapshot_source(ok and text or "")
+ end)
+ subscribe("render_assistant_text", function(event)
+ player:set_source(event.text or "", false, event.index)
+ end)
+ subscribe("render_assistant_text_delta", function(event)
+ if event.text ~= nil then player:update_source(event.text, false, event.index) end
+ end)
+ subscribe("render_assistant_text_complete", function(event)
+ player:update_source(event.text or "", true, event.index)
+ end)
+ subscribe("suspend", function() player:suspend() end)
+ subscribe("turn_start", function() player:cancel("turn") end)
+ subscribe("turn_interrupt", function() player:cancel("turn") end)
+ subscribe("session_end", function() player:cancel("session_end") end)
+
+ ext.register_command {
+ name = "rsvp",
+ description = "Read the latest assistant text one paragraph at a time.",
+ handler = function(arguments)
+ if arguments and arguments ~= "" then return "Usage: /rsvp" end
+ local ok, why = player:activate()
+ if not ok then
+ return "RSVP unavailable" .. (why and (": " .. tostring(why)) or "")
+ end
+ end,
+ }
+end
+
+return { name = "rsvp", activate = activate }