From d2a53d2fc748831ec56ee08bd8c7638d35a4189d Mon Sep 17 00:00:00 2001 From: Markus Fritsche Date: Tue, 12 May 2026 23:36:44 +0000 Subject: [PATCH] renderer: Norris autonomous-mode frames (Phase 3 commit #3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 commit #3 per docs/PHASE3.md §12. Four new renderer functions for Norris mode visual feedback. M.norris_begin(goal) Bold cyan banner on Norris entry, with the goal text on a dim indented line. Frames the start of the planning loop. M.norris_step(n, max_n, descr) Compact one-line step counter ("─ step 3/16 ─") with optional description. Renders before each iteration of the planner. M.norris_halt(step_n, max_n, reason, action) Bold red banner when the destructive-op gate fires. Three indented lines: step counter, reason (red), action text (truncated at 400 chars, newlines collapsed). The interactive proceed/skip/abort prompt is shown after this banner by repl.lua. M.norris_end(status, reason) Closing banner. status ∈ {"done", "aborted", "budget_exhausted", "stalled", "broker_error"}. Color cyan on "done", red otherwise. Optional reason text on a dim line. The interactive prompt `[aish: ⚡]>` activation lands in commit #5 (repl.lua's prompt() function). Smoke-tested all five frames visually — clean ANSI output, correct truncation on long action strings, color discrimination on done/aborted/budget_exhausted. Co-Authored-By: Claude Opus 4.7 (1M context) --- renderer.lua | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/renderer.lua b/renderer.lua index cd21d7d..55ab68c 100644 --- a/renderer.lua +++ b/renderer.lua @@ -107,4 +107,55 @@ function M.tool_call_end(content, is_error) end end +-- Phase 3: Norris autonomous mode frames. Banner-style on enter/exit, +-- step counter per iteration, red HALT banner when the destructive-op +-- gate fires. The interactive prompt also gets a ⚡ marker when Norris +-- is active (handled in repl.lua's prompt() function per PHASE0.md §9). +-- See docs/PHASE3.md §3 renderer row. + +function M.norris_begin(goal) + emit(A.bold, A.cyan, "─── NORRIS MODE ─────────────────────────", + A.reset, "\n") + if goal and goal ~= "" then + emit(A.dim, " goal: ", A.reset, goal, "\n") + end + emit(A.bold, A.cyan, "─────────────────────────────────────────", + A.reset, "\n") +end + +function M.norris_step(n, max_n, descr) + emit(A.dim, (" ─ step %d/%d ─ "):format(n, max_n), A.reset) + if descr and descr ~= "" then emit(A.dim, descr, A.reset) end + emit("\n") +end + +function M.norris_halt(step_n, max_n, reason, action) + emit(A.bold, A.red, "─── NORRIS HALT ──────────────────────────", + A.reset, "\n") + emit(A.dim, " step: ", A.reset, ("%d/%d"):format(step_n, max_n), "\n") + emit(A.dim, " reason: ", A.reset, A.red, tostring(reason), A.reset, "\n") + -- action may be a long string (command line or JSON-serialized tool call); + -- truncate at 400 chars to keep the banner readable + local act = tostring(action or ""):gsub("\n", " ") + if #act > 400 then act = act:sub(1, 397) .. "..." end + emit(A.dim, " action: ", A.reset, act, "\n") + emit(A.bold, A.red, "──────────────────────────────────────────", + A.reset, "\n") +end + +-- Norris loop exit. status ∈ {"done", "aborted", "budget_exhausted", +-- "stalled", "broker_error"}. +function M.norris_end(status, reason) + local color = (status == "done") and A.cyan or A.red + local label = status:upper():gsub("_", " ") + emit(A.bold, color, "─── NORRIS ", label, " ──", + (" "):rep(math.max(0, 28 - #label)), + A.reset, "\n") + if reason and reason ~= "" then + emit(A.dim, " ", reason, A.reset, "\n") + end + emit(A.bold, color, "──────────────────────────────────────────", + A.reset, "\n") +end + return M