Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Fixed

- **PR #2331** by @Michaelyklam (closes #2310) — Live compact Activity rows now show transient user-readable progress such as "Searching workspace", "Reading files", "Updating files", or "Running command" while a long tool-only turn is still working. The label is derived from the current tool category, uses the existing non-persistent Activity summary/duration slot, and does not add synthetic assistant content to the transcript.

- **PR #2315** by @Michaelyklam (closes #2305, refs #749) — WebUI profile creation now seeds bundled profile skills for newly-created non-cloned profiles, matching the CLI's `hermes profile create` behaviour. Pre-fix, creating a profile via Settings → New Profile (without checking "Clone from active profile") left the profile's `skills/` directory empty, which was inconsistent with CLI-created profiles that get the full bundled-skills overlay. The fix calls `seed_profile_skills(profile_path, quiet=True)` after `profile_path.mkdir()` when `clone_from is None`. Cloned profiles still inherit skills from their source — they don't get a second bundled-skills overlay. Seed failures (e.g. `hermes_cli` unavailable in Docker fallback) are logged as warnings, not fatal — profile creation still succeeds.

- **PR #2317** by @Michaelyklam (refs #2312 follow-up #2) — Appearance boot reconciliation now treats explicit `light`, `dark`, and `system` localStorage theme values as user selections when a prior Settings autosave failed. Pre-fix, the predicate `lsHasExplicitTheme = lsTheme === 'system'` only treated 'system' as explicit, so a user who picked `light` on a server defaulted to `dark` (or vice versa) with a failed autosave still reverted to the server default on refresh. Now broadened to `['system','light','dark'].includes(lsTheme)`. Skin handling was already correct (`lsSkin !== 'default'`). Closes follow-up item #2 from the v0.51.66 review (#2312).
Expand Down
24 changes: 22 additions & 2 deletions static/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -5929,10 +5929,11 @@ function _syncToolCallGroupSummary(group){
if(durationEl){
if(group.getAttribute('data-live-tool-call-group')==='1'){
const activeText=_activityElapsedLabel(group);
const progressText=_activityLiveProgressLabel(group);
if(activeText) group.setAttribute('data-active-turn-elapsed',activeText);
else group.removeAttribute('data-active-turn-elapsed');
durationEl.textContent=activeText?`Working ${activeText}`:'';
durationEl.style.display=activeText?'':'none';
durationEl.textContent=[progressText, activeText].filter(Boolean).join(' · ');
durationEl.style.display=durationEl.textContent?'':'none';
}else{
const durationText=_formatTurnDuration(group.dataset.turnDuration);
durationEl.textContent=durationText?`Done in ${durationText}`:'';
Expand All @@ -5941,6 +5942,25 @@ function _syncToolCallGroupSummary(group){
}
}

function _activityProgressLabelForToolName(name){
const key=String(name||'').toLowerCase().replace(/[^a-z0-9]+/g,'_');
if(!key) return 'Working';
if(key.includes('search')||key.includes('grep')) return 'Searching workspace';
if(key.includes('read')||key.includes('view')||key.includes('open')) return 'Reading files';
if(key.includes('write')||key.includes('patch')||key.includes('edit')) return 'Updating files';
if(key.includes('terminal')||key.includes('shell')||key.includes('command')||key.includes('process')) return 'Running command';
if(key.includes('web')||key.includes('fetch')||key.includes('curl')) return 'Checking web data';
if(key.includes('todo')||key.includes('plan')) return 'Planning next steps';
return 'Working';
}

function _activityLiveProgressLabel(group){
if(!group||group.getAttribute('data-live-tool-call-group')!=='1') return '';
const running=group.querySelector('.tool-card.tool-card-running .tool-card-name');
const latest=running || Array.from(group.querySelectorAll('.tool-card-name')).pop();
return _activityProgressLabelForToolName(latest?latest.textContent:'');
}

// ── Live tool card helpers (called during SSE streaming) ──
// Live cards are inserted INLINE inside #msgInner (tagged with data-live-tid)
// so the streaming layout matches the settled layout produced by renderMessages
Expand Down
19 changes: 19 additions & 0 deletions tests/test_ui_tool_call_cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,25 @@ def test_live_tool_activity_defaults_collapsed_unless_saved_open(self):
"A previously-open Activity group should still restore open from persisted state."
)

def test_live_activity_summary_shows_readable_progress_without_persisted_content(self):
sync_fn = _function_body(UI_JS, "_syncToolCallGroupSummary")
progress_fn = _function_body(UI_JS, "_activityProgressLabelForToolName")
live_progress_fn = _function_body(UI_JS, "_activityLiveProgressLabel")
assert "_activityLiveProgressLabel" in sync_fn, (
"Live compact Activity rows should expose a readable transient progress label."
)
assert "durationEl.textContent" in sync_fn and "filter(Boolean).join(' · ')" in sync_fn, (
"Progress should share the existing non-persistent summary/duration slot, not become transcript text."
)
for label in ("Searching workspace", "Reading files", "Updating files", "Running command"):
assert label in progress_fn
assert "tool-card-running" in live_progress_fn, (
"The live progress label should prefer the currently running tool over older completed tools."
)
assert "tool-call-group-list" not in sync_fn, (
"Readable progress must not reintroduce the noisy secondary tool-name list."
)

def test_tools_and_thinking_share_one_collapsed_activity_dropdown(self):
ui_min = re.sub(r"\s+", "", UI_JS)
assert "functionensureActivityGroup(" in ui_min, (
Expand Down
Loading