From 53947c687711116c74ec59cff6895e30e2d3111c Mon Sep 17 00:00:00 2001 From: t Date: Sat, 31 Oct 2026 09:28:40 -0600 Subject: Sanitize Herdr status text only at the OSC boundary Stop rewriting tool and command names into ASCII labels before Herdr sees them, so its label selection and truncation work on the real names. Replace non-printable characters only when encoding the OSC 7501 msg field, whose free-text payload forbids controls. --- mise.toml | 78 ++++++++++++++++++++++++++++++++++++++++++++ panto-report-status/init.lua | 18 ++++++---- 2 files changed, 89 insertions(+), 7 deletions(-) diff --git a/mise.toml b/mise.toml index 196f848..0ea0f26 100644 --- a/mise.toml +++ b/mise.toml @@ -1,4 +1,82 @@ +# Release plumbing. `mise run version ` rewrites the rockspec, then you +# commit it and `mise run release` tags, pushes and uploads. +# +# Why not `luarocks new_version`: it regenerates the rockspec from the parsed +# table, so it silently drops every comment and reflows the tables. Two sed +# lines keep the file intact. + [tasks.check] description = "Run the self-check without emitting to a live terminal" dir = "{{config_root}}" run = "panto lua _selfcheck.lua" + +[tasks.version] +description = "Rewrite the rockspec for a new version (then commit it)" +dir = "{{config_root}}" +run = """ +set -eu +NEW="{{arg(name='version')}}" +SPEC="$(ls -1 ./*.rockspec)" +case "$SPEC" in *" +"*) echo "expected exactly one rockspec, found:" >&2; echo "$SPEC" >&2; exit 1;; esac + +PKG="$(sed -n 's/^package = "\\(.*\\)"/\\1/p' "$SPEC")" +NEWSPEC="./$PKG-$NEW-1.rockspec" + +# Write through a temp file: without it, a re-run at the same version would +# have the redirect truncate the file sed is still reading. +TMP="$(mktemp)" +sed -e "s/^version = \\".*\\"/version = \\"$NEW-1\\"/" \\ + -e "s/^\\( *\\)tag = \\"v[^\\"]*\\"/\\1tag = \\"v$NEW\\"/" "$SPEC" > "$TMP" +mv "$TMP" "$NEWSPEC" +[ "$SPEC" = "$NEWSPEC" ] || rm -f "$SPEC" + +luarocks lint "$NEWSPEC" +echo "wrote $NEWSPEC -- commit it, then: mise run release" +""" + +[tasks.release] +description = "Tag the committed version, push the tag, upload to luarocks.org" +depends = ["check"] +dir = "{{config_root}}" +run = """ +set -eu +SPEC="$(ls -1 ./*.rockspec)" +case "$SPEC" in *" +"*) echo "expected exactly one rockspec, found:" >&2; echo "$SPEC" >&2; exit 1;; esac + +VER="$(sed -n 's/^version = "\\(.*\\)-[0-9]*"/\\1/p' "$SPEC")" +[ -n "$VER" ] || { echo "could not read version from $SPEC" >&2; exit 1; } +TAG="v$VER" + +# jj's working copy is itself a commit, so @ is normally the empty commit a +# `jj new` just made and the release content sits in @-. Override with +# REV=@ (or any revset) when that is not the case. +REV="${REV:-@-}" + +# The tag is what `luarocks upload` clones to build the .src.rock, so the +# rockspec being published has to already be in the revision being tagged. +# Uncommitted, they diverge silently: the upload would ship a rockspec that +# says $VER over sources from a commit that never had it. +if ! jj file show -r "$REV" "$SPEC" 2>/dev/null | diff -q - "$SPEC" >/dev/null 2>&1; then + echo "$SPEC at $REV differs from the working copy -- commit it first" >&2 + exit 1 +fi + +if jj tag list | grep -q "^$TAG:"; then + echo "tag $TAG already exists -- bump the version first" >&2 + exit 1 +fi + +# jj 0.42 creates tags natively and exports them to git in a colocated repo, +# but `jj git push` only pushes bookmarks -- tags still need git. +jj tag set "$TAG" -r "$REV" +jj tag list | grep "^$TAG:" +for remote in $(git remote); do + git push "$remote" "$TAG" +done + +# Reads the API key from ~/.luarocks/upload_config.lua. Packs a .src.rock by +# cloning source.url at the tag just pushed, so the push has to come first. +luarocks upload "$SPEC" +""" diff --git a/panto-report-status/init.lua b/panto-report-status/init.lua index 07c6110..e2fe5c6 100644 --- a/panto-report-status/init.lua +++ b/panto-report-status/init.lua @@ -13,16 +13,16 @@ local function base64(text) return table.concat(out) end --- Names are diagnostic labels, not arbitrary terminal text. Keep messages short, --- ASCII and control-free even when a tool or command supplies an unusual name. -local function label(name) - return (name or "?"):gsub("[^ -~]", "?"):sub(1, 27) +local function tool_status(name) + name = name or "?" + return "tool:" .. name:sub(1, 27) end local function command_status(name) + name = name or "?" if name == "compact" then return "compacting" end if name == "!" or name == "!!" then return "shell" end - return "cmd:" .. label(name) + return "cmd:" .. name:sub(1, 28) end return { @@ -56,7 +56,11 @@ return { -- put control sequences in a pipe, file, or a different terminal. if terminal_key() ~= terminal then return end local sequence = "\27]7501;state=" .. state .. ":app=panto" - if status then sequence = sequence .. ":msg=" .. base64(status) end + if status then + -- Preserve Herdr's label selection/truncation; sanitize only at + -- the OSC boundary, whose free-text fields forbid controls. + sequence = sequence .. ":msg=" .. base64((status:gsub("[^ -~]", "?"))) + end sequence = sequence .. "\27\\" local offset = 0 while offset < #sequence do @@ -105,7 +109,7 @@ return { panto.ext.on("render_compaction", function() if foreground_working then foreground("compacting") end end) - panto.ext.on("render_tool_details", function(event) foreground("tool:" .. label(event.tool_name)) end) + panto.ext.on("render_tool_details", function(event) foreground(tool_status(event.tool_name)) end) panto.ext.on("render_assistant_text", function() foreground("responding") end) panto.ext.on("turn_end", finish) panto.ext.on("background_work_start", function() -- cgit v1.3