Merge #3971 (hide Worklog reasoning when Thinking disabled) onto master

This commit is contained in:
nesquena-hermes
2026-06-13 02:52:31 +00:00
4 changed files with 59 additions and 2 deletions

View File

@@ -121,6 +121,10 @@
- **Internal Stable Assistant Turn Anchors Phase 0 scaffold.** The browser now ships an inert `HermesAssistantTurnAnchors` helper surface plus a documented state-layer inventory for #3926, pinning current live/replay/settled source classifications and event dedupe precedence without changing visible chat rendering.
- **New RFC: Stable Assistant Turn Anchors for Live-to-Final rendering.** Defines a frontend presentation/reconciliation model for anchoring one assistant turn across live streaming, settlement, replay/reload/recovery, Compact Worklog, Transparent Stream, terminal states, artifacts, and side effects. (#3926)
### Fixed
- **Hide Thinking now also hides Worklog reasoning rows without hiding tool activity.** The `show_thinking=false` path now suppresses `.wl-reason` entries that come from reasoning text and prunes already-rendered rows when Thinking is toggled off, while preserving tool cards and ordinary Worklog anchor/progress rows. (#3903)
## [v0.51.358] — 2026-06-11 — Release LV (first-run password bootstrap hardening)
### Security

View File

@@ -1402,6 +1402,7 @@ function cmdReasoning(args){
const on=(arg==='show'||arg==='on');
// Update the UI render gate immediately for responsiveness.
window._showThinking=on;
if(!on&&typeof removeThinking==='function') removeThinking();
if(typeof renderMessages==='function') renderMessages();
// Persist via /api/reasoning → config.yaml display.show_reasoning
// (CLI reads the same key). Also mirror into WebUI settings.json

View File

@@ -7241,6 +7241,7 @@ function _renderWorklogReasonInto(row, text){
row.innerHTML=html;
}
function _worklogReasonNodeFromText(text, attrs){
if(window._showThinking===false) return null;
const html=_worklogReasonHtmlFromText(text);
if(!html) return null;
const row=document.createElement('div');
@@ -11477,9 +11478,10 @@ function removeThinking(){
const turn=$('liveAssistantTurn');
const blocks=_assistantTurnBlocks(turn);
if(blocks) blocks.querySelectorAll('.agent-activity-thinking').forEach(el=>el.remove());
if(blocks) blocks.querySelectorAll('.tool-call-group[data-agent-activity-group="1"]').forEach(group=>{
if(blocks) blocks.querySelectorAll('.wl-reason[data-worklog-reason-source="reasoning"]').forEach(el=>el.remove());
if(blocks) blocks.querySelectorAll('.live-worklog[data-live-worklog-shell="1"],.tool-worklog-group[data-live-tool-call-group="1"],.tool-call-group[data-live-tool-call-group="1"],.tool-call-group[data-agent-activity-group="1"]').forEach(group=>{
_syncToolCallGroupSummary(group);
if(!group.querySelector('.tool-card-row,.agent-activity-thinking')){
if(!group.querySelector('.tool-card-row,.agent-activity-thinking,.wl-reason')){
if(typeof _clearActivityElapsedTimer==='function') _clearActivityElapsedTimer();
group.remove();
}

View File

@@ -20,6 +20,23 @@ def read(rel):
return (REPO / rel).read_text(encoding='utf-8')
def function_body(src, name):
start = src.find(f"function {name}")
assert start != -1, f"{name} not found"
brace = src.find("{", start)
assert brace != -1, f"{name} body not found"
depth = 0
for idx in range(brace, len(src)):
ch = src[idx]
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return src[brace + 1:idx]
raise AssertionError(f"{name} body did not close")
# ── api/config.py ─────────────────────────────────────────────────────────────
class TestShowThinkingConfig:
@@ -80,6 +97,36 @@ class TestUiJsThinkingGate:
)
break
def test_worklog_reasoning_rows_are_gated_by_show_thinking(self):
src = read('static/ui.js')
node_fn = function_body(src, "_worklogReasonNodeFromText")
assert 'window._showThinking===false' in node_fn and 'return null' in node_fn, (
"reasoning-source Worklog rows must not be created when thinking is hidden"
)
def test_show_thinking_gate_does_not_hide_worklog_anchor_text(self):
src = read('static/ui.js')
html_fn = function_body(src, "_worklogReasonHtmlFromText")
assert 'window._showThinking' not in html_fn, (
"the low-level Worklog text renderer is also used for anchor/progress text; "
"only reasoning-source rows should be gated"
)
def test_remove_thinking_prunes_reasoning_rows_but_preserves_tool_or_anchor_rows(self):
src = read('static/ui.js')
fn = function_body(src, "removeThinking")
assert '.wl-reason[data-worklog-reason-source="reasoning"]' in fn, (
"removeThinking must sweep already-rendered reasoning Worklog rows"
)
assert '.tool-card-row,.agent-activity-thinking,.wl-reason' in fn, (
"empty-group cleanup must preserve groups that still contain tool cards "
"or non-reasoning Worklog anchor rows"
)
assert '.live-worklog[data-live-worklog-shell="1"]' in fn, (
"live Worklog shells must be considered for cleanup after their reasoning "
"rows are removed"
)
# ── static/messages.js ────────────────────────────────────────────────────────
@@ -158,6 +205,9 @@ class TestReasoningCommand:
assert 'renderMessages' in fn, (
"show/hide branch must call renderMessages()"
)
assert "typeof removeThinking==='function'" in fn and "removeThinking()" in fn, (
"hide/off must prune already-rendered live Thinking and Worklog reasoning rows"
)
# Persistence: POST to /api/reasoning (CLI-shared config.yaml) AND
# /api/settings (boot.js mirror).
assert "api('/api/reasoning'" in fn, (