UI restructure (frontend static):
- PLAN moved from sticky toggle chip to a preset card (BUILTIN_PRESETS) that
one-shot-sends `presetPrompt.plan`, now pointing at the real
memory/plan_sop.md (Auto was considered too but dropped — it semantically
overlaps with the existing "自主探索" preset). All plan/auto toggle
plumbing removed: state, chip listeners, applyToggleClass, ga_plan/ga_auto
localStorage, planPrefix/autoPrefix injection.
- Chat composer: model chip moved out of the joined .cb-left pill group to
sit next to the send button (composer-bot is now right-aligned model+send).
- Conductor composer: gained its own #collab-model-chip + #collab-model-menu;
openModelMenu/renderModelMenu/updateModelChip refactored to be ctx-aware
(both chips share the model list; each pops its menu into its own
composer; updateModelChip syncs both names).
- Topbar dissolved into per-page headers: the global .topbar was removed
from <main>; the run-state moved into a .page-top inside the chat page,
so the "topbar" now switches with the page instead of persisting.
- Removed the conductor "{n} 个任务进行中" sticky banner (#collab-sticky);
the same info is already in the progress panel and the sidebar badge.
Conductor persistence (conductor.py + frontend wiring):
- conductor.py: add_chat() carries files/images on each chat item; WS
receive passes data.files/data.images through; hello includes a
running flag (any subagent with status="running"). 5 net edits.
- app.js: ws.send now posts {msg, files, images} (was msg only); onWsData
hello/chat branches map files/images and set S.conductorTyping from
data.running so attachments + run animation survive a page refresh.
Session naming (mirror TUI continue_cmd._preview_text):
- New helper displayTitle(sess): if user manually renamed, use sess.title;
otherwise scan messages — prefer the last <summary>…</summary> in any
assistant reply, fall back to the first user message text (≤40 chars,
attachment placeholders stripped), final fallback t('conv.defaultTitle').
- newSession() now defaults sess.title to '' instead of '新对话'; the
earlier "first message → title" auto-rename block in submitInput is
gone — titles only get written when the user renames explicitly.
- isAutoTitle() recognises '新对话'/'New chat'/'agent-N' as placeholder so
legacy sessions and the brief agent-N experiment get re-derived live
in the sidebar without losing user renames.
- Sidebar render + search filter now go through displayTitle.
Note: requires a one-time restart of conductor.py for its part to take
effect; the static frontend changes are just a refresh.