diff options
| -rw-r--r-- | init.lua | 20 | ||||
| -rw-r--r-- | spec/test_init.lua | 59 |
2 files changed, 58 insertions, 21 deletions
@@ -53,22 +53,6 @@ local function subscribe(ext, name, handler) end end -local function wrap_plain(text, width) - width = math.max(1, width or 1) - local lines, from = {}, 1 - while #text - from + 1 > width do - local window = text:sub(from, from + width - 1) - local cut = window:match("^.*() ") or width - lines[#lines + 1] = text:sub(from, from + cut - 1):gsub("%s+$", "") - from = from + cut - while from <= #text and text:sub(from, from) == " " do - from = from + 1 - end - end - lines[#lines + 1] = text:sub(from) - return lines -end - local function install_header(ext, profiles, workflows) local profile_names, workflow_names = {}, {} for _, profile in ipairs(profiles.list) do @@ -91,9 +75,7 @@ local function install_header(ext, profiles, workflows) { "workflows", workflow_names }, }) do local value = #inventory[2] == 0 and "(none)" or table.concat(inventory[2], ", ") - for _, line in ipairs(wrap_plain(" " .. inventory[1] .. ": " .. value, width)) do - extras[#extras + 1] = DIM .. line .. RESET - end + extras[#extras + 1] = DIM .. " " .. inventory[1] .. ": " .. value .. RESET end local at = (#lines > 0 and lines[#lines] == "") and #lines or (#lines + 1) for index = #extras, 1, -1 do diff --git a/spec/test_init.lua b/spec/test_init.lua index e3b4d55..a9956c4 100644 --- a/spec/test_init.lua +++ b/spec/test_init.lua @@ -138,8 +138,13 @@ return { assert(not plain:find("unusable", 1, true), "invalid workflows stay out of the usable inventory") assert(handle.commands_by_name["workflow:unusable"] == nil, "the header inventory matches command registration") - assert(rendered[#rendered] == "", "annotations stay before the trailing blank") - assert(#rendered > 4, "inventories wrap at the component width") + assert(rendered[1] == "Panto", "inner component output stays first") + assert(rendered[2] == "\27[2m subagents: reviewer\27[0m", + "the profile inventory stays one styled text chunk") + assert(rendered[3] == "\27[2m workflows: review-chain\27[0m", + "the workflow inventory follows the profile inventory") + assert(rendered[4] == "", "annotations stay before the trailing blank") + assert(#rendered == 4, "header injection does not hand-roll wrapped rows") local primary_messages = handle.ext.agent:conversation():messages() local guidance = primary_messages[#primary_messages].blocks[1].text @@ -150,6 +155,56 @@ return { has(guidance, "Do not actively wait or poll with `sleep`") end }, + { "the header injects both inventories verbatim, in order, style-isolated", function() + -- Phase 4 of the row-normalization plan deletes the extension's own + -- wrapping. What the extension still owns is the ORDER of the two + -- inventories, the insertion point, style isolation (each chunk resets + -- itself so nothing leaks into the host's rows), and handing names over + -- byte-intact so Engine.fit measures the real display width. + local header + activate_bare(function(handle, ok, err) + assert(ok, tostring(err)) + -- No trailing blank line from the inner component: this exercises + -- the append-at-the-end branch of the insertion point, which the + -- other header case (with a trailing blank) does not. + handle.emit("session_start", { + get_component = function() + return { render = function() return { "Panto" } end } + end, + set_component = function(_, component) + header = component + end, + }) + end) + assert(header, "session_start must wrap the header component") + + -- The extension performs no string processing on the names beyond a + -- `table.concat`, and reacts to `width` only by forwarding it to the + -- inner component. Rendering at an absurdly narrow width and at a wide + -- one must therefore be byte-identical: that is what guarantees long + -- and non-ASCII profile/workflow names reach Engine.fit intact. + local narrow = header:render(1) + local wide = header:render(500) + assert(#narrow == 3, "expected 3 chunks (inner + two inventories), saw " .. #narrow) + for index = 1, #narrow do + assert(narrow[index] == wide[index], + "the header must not depend on width; chunk " .. index .. " differed") + end + + assert(narrow[1] == "Panto", "the inner component's output stays first") + local subagents = narrow[2]:match("^\27%[2m subagents: (.*)\27%[0m$") + local workflows = narrow[3]:match("^\27%[2m workflows: (.*)\27%[0m$") + assert(subagents, "chunk 2 is the profile inventory: " .. narrow[2]) + assert(workflows, "chunk 3 is the workflow inventory: " .. narrow[3]) + + for _, chunk in ipairs({ narrow[2], narrow[3] }) do + assert(chunk:sub(1, 4) == "\27[2m", "each inventory chunk opens its own style") + assert(chunk:sub(-4) == "\27[0m", "each inventory chunk resets, so styles cannot leak") + assert(not chunk:find("\n", 1, true), "the extension emits no rows of its own") + assert(not chunk:find("\226\128\166"), "the extension truncates nothing") + end + end }, + { "layered config sets max_concurrent with later layers winning", function() local tmp = os.tmpname() os.remove(tmp) |
