Skip to content

v0.5.4

Choose a tag to compare

@github-actions github-actions released this 06 Oct 04:19
· 140 commits to main since this release

MusePi 0.5.4

Platform 下载
macOS (Apple Silicon) MusePi-0.5.4-arm64.dmg
Windows MusePi-0.5.4-setup.exe · MusePi-0.5.4-arm64-setup.exe
Linux x64 MusePi-0.5.4-x86_64.AppImage · MusePi-0.5.4-amd64.deb
Linux arm64 MusePi-0.5.4-arm64.AppImage · MusePi-0.5.4-arm64.deb
Android MusePi-0.5.4-app-debug.apk (debug build, adb 安装)

[0.5.4] - 2026-10-06

Added

  • 外观新增「始终显示滚动条」开关:打开后已经出现的滚动条不再自动淡出,会一直亮着;横向也能拖动了,此前只有纵向有把手。
    • EN: Appearance gains an "Always show scrollbars" switch — a scrollbar that has appeared stays lit instead of fading out; they are also draggable horizontally, which only the vertical axis supported before.
  • 左右并排的两个可滚动区域现在各有一条滚动条:此前只有最后滚动的那一个区域有轨道,另一个区域的滚动状态完全看不到。滚动条槽位按容器分配,左右两栏、上下分栏互不抢占了。
    • EN: Two independently scrolling areas side by side each get their own scrollbar. Previously only the most recently scrolled area had a rail, so the other one's position was invisible. Rail slots are now keyed per container, so side-by-side and stacked panes no longer steal each other's indicator.
  • 内容自己滚动(比如新消息到达时自动跟随到底)不再凭空冒出滚动条:只有你真的滚了(滚轮、触摸、PageDown 这类按键、拖动把手)滚动条才出现,此前程序化的滚动也会点亮它。
    • EN: Content scrolling on its own — an incoming message auto-following to the bottom, say — no longer summons a scrollbar out of nowhere. The rail now appears only when you actually scrolled (wheel, touch, scrolling keys like PageDown, or dragging the thumb); previously programmatic scrolling lit it up too.
  • 发出去没回应的消息,现在会告诉你:一条消息发出去,会话已经空闲,既没有回复也没有报错 —— 转录里只能表现为「到这儿就断了」,看上去像还在想。这个状态现在会在输入框上方亮一张横幅。这个判断本身是要核实的:实时流可能只是漏掉了这一轮的事件,而且也没有别的东西会重读会话。所以横幅出现之前会按 0 / 10 / 30 秒的节奏重读:读到了回复就不出现,读不到才出现;重读本身失败也算「读不到」,因为一次读失败不该把一次真实的静默藏起来。重试次数从那张不再显示的卡片并到了正文里,所以「试了几次」不会因为卡片退场而丢失。
    • EN: A message that got no answer now says so. A prompt that was sent, on a session that has since gone idle, with neither a reply nor an error shows in the transcript only as an abrupt stop — indistinguishable from still-thinking. It now raises a banner above the composer. The verdict is checked rather than guessed: a live stream can simply drop a turn's events, and nothing else re-reads the session, so the transcript is re-read at 0, 10 and 30 seconds first. A reply found by a read means no banner; only a read that finds nothing raises one, and a read that fails counts as finding nothing — a failed check must not hide a genuine silence. The retry attempt count moves off the card that no longer renders and onto the body, so "how many attempts" survives the card's retirement.

Changed

  • 界面里的失败不再是一整块红色告警:一次失败的请求此前是一张带边框的卡片,正文用红色等宽字体整段倒出来,长报文还会换行铺满、把折叠开关挤到折线以下。现在这一轮的原因就是那一轮的正文 —— 助手列里的普通文字,正常前景色,不带卡片也不带边框;严重程度由一个小标记和标签承担,供应商原文按需折叠(超过 1000 字才折,预览 400 字 / 6 行,展开后在自己的框里滚动)。运行时就某一轮给出的失败不再挤进消息流,而是停靠在输入框上方,用的是输入框那一层一直在用的液态玻璃,只把色调换成红色。下一次发送就是它的关闭动作 —— 不需要 × 按钮:失败是当前状态,新一轮自然会顶掉它;往上翻它也不跟着走。同一次失败仍然只出现一次。
    • EN: A failure in the interface is no longer a block of red alarm. A failed request used to be a bordered card that printed the provider's body in red monospace, and a long body wrapped to fill the width and pushed the disclosure below the fold. The round's reason is now the round's own text — ordinary message typography in the message column, normal foreground colour, no card and no border. Severity is carried by a small mark and a label, and the provider's own text folds on demand: past 1000 characters it collapses to a 400-character, 6-line preview, and expands into a box that scrolls on its own. A failure the runtime reports for the round no longer crowds the message flow at all — it docks above the composer in the liquid glass the composer's own layer already uses, with the tint switched to the error hue. The next send is the dismissal: there is no × button, because a failure is current state and a new round supersedes it, and scrolling up does not take it along. One failure still surfaces once.
  • 回合之间不再有「模型 · 时间 · 已完成 · 耗时」那一行:它和每条回复下面本来就有的计时行说的是同一件事,但两者的模型来源不同 —— 计时行直接读消息自己的模型,而那一行走的是另一份投影,切换模型之后它还挂着上一个模型的名字,看起来像是上一轮用的。现在这条行被移除,回合之间的间距改由回合起始行自己承担。每条回复下面的「用时 · 模型 · 思考档位」保持不变,它本来就是实时的。
    • EN: The per-round "model · time · done · duration" line is gone. It said what the timer line under each reply already says, but the two read the model differently: the timer line takes it from the message itself, while that line went through a separate projection and kept naming the previous model after a switch, as though the round before had used it. The line is removed and the space between rounds now belongs to the row that starts the turn. The "elapsed · model · thinking level" line under each reply is unchanged, and it was always current.
  • /usage 现在是全屏面板:命令打开一个占满备用屏幕的用量面板,每个账户一栏、每条额度一行,显示供应商、账户、剩余比例、预付费余额与重置时间。回车在摘要与完整报告之间切换,滚轮或 PageUp/PageDown 滚动,Esc 关闭。完整报告就是此前内联打印的那份内容,两处不会再出现不一致。
    • EN: /usage is now a fullscreen panel. The command opens a usage dashboard on the alternate screen — one block per account, one row per quota, showing the provider, the account, what share is left, any prepaid balance and when the window resets. Enter toggles between the summary and the full report, the wheel or PageUp/PageDown scrolls, and Esc closes. The full report is the very text /usage used to print inline, so the two surfaces cannot drift apart.
  • 用量面板里被截断的内容可以悬停看全:额度名、账户与重置时间在窄窗口下会截断,而终端本身没有悬停,此前只能拉宽窗口。现在把鼠标移到那一格,底部会显示完整文本;宽度够、不曾截断的格子不触发。
    • EN: Truncated text in the usage panel can be read in full by hovering it. Quota names, accounts and reset times were cut off in a narrow window, and a terminal has no hover of its own, so widening the window was the only way to see them. Moving the pointer over such a cell now shows the full text along the bottom; a cell that fits never raises a hint.

Fixed

  • Windows 上桌面通知不再找不到会话总线:XDG_RUNTIME_DIR 已设置但 DBUS_SESSION_BUS_ADDRESS 未导出时,程序按 Windows 的路径分隔符去拼 socket 路径,找不到就判定没有桌面会话。现在按 POSIX 分隔符拼接,与实际路径一致。

    • EN: Desktop notifications on Windows no longer miss the session bus. With XDG_RUNTIME_DIR set but DBUS_SESSION_BUS_ADDRESS unexported, the socket path was joined with Windows separators, never matched, and the session was reported as headless. The path is now joined POSIX-style, as the real one is.
  • Windows 与 WSL 上输出积压的上限重新生效:输出被拒后,程序按「写了多少次」判断何时停止输出,而 Windows 终端会把大块绘制拆成多段写出,于是次数被放大、真正的积压量被低估。现在按字节判断,跨平台一致。

    • EN: The output backlog cap counts bytes again. When a write is refused the program decided when to stop by counting write calls, but a Windows terminal splits a large paint into several writes, which inflated the count and understated the real backlog. The accounting is now by bytes, consistently across platforms.
  • 预付费余额现在在每一处用量视图里都能看到:只有余额、没有额度的供应商,此前只在命令行报告里显示;终端用量面板在那个位置只有一排暗点,客户端卡片上则完全没有这行。现在三处都会给出该余额,账户级的共享池只计一次。

    • EN: A prepaid balance now appears in every usage view. A provider that reports only a remaining balance and no allowance showed it in the command-line report alone — the terminal panel drew dim dots in that cell and the client card had no line for it. All three now report the balance, counting an account-wide pool once.
  • 客户端用量卡片不再把「按量计费」的额度显示成 0%:只有绝对数额、没有百分比的额度(Z.ai 的 token 与 credit 额度、按美元计的额外用量),在桌面转录、移动端与鸿蒙端此前显示成「0% used · 0% left」加一条空进度条。现在直接显示供应商报出的数额,进度条按剩余比例画。

    • EN: The usage card no longer shows an amount-metered quota as 0%. Meters that report an absolute amount and no percentage — Z.ai token and credit quotas, dollar-denominated extra usage — read as "0% used · 0% left" with an empty bar in the desktop transcript, mobile and HarmonyOS clients. The card now shows the amount the provider reported and draws the bar from the remaining share.
  • /usage 现在会显示 Z.ai GLM Coding Plan 的 credit 额度:这类额度此前被整条丢掉——只带 credit 窗口的账户连用量报告都拿不到,在界面上等同于「未上报」。现在按 credit 计量显示,进度用服务端的精确比值,而不是取整后的百分比。

    • EN: /usage now shows Z.ai GLM Coding Plan credit windows. They were dropped entirely — an account whose only meter is a credit window produced no usage report at all and read as unreported. Credits render as their own unit, and the bar uses the exact consumed ratio instead of the server's rounded percentage.
  • 走代理的供应商现在按配置的地址校验凭据:/usage 与后台用量服务此前只从已发现的模型里推地址;对只在发现后才出现的供应商,这一刻拿不到地址,于是把代理作用域的密钥发给了供应商的官方主机。现在配置与运行时的供应商地址优先于模型推导。

    • EN: A proxied provider is now probed at its configured address. /usage and the background usage service derived the address only from discovered models, so a provider that appears solely through discovery had none yet and sent a proxy-scoped key to the vendor's canonical host. Configured and runtime provider addresses now take precedence over model-derived ones.
  • /usage 会显示预付费余额:只有余额、没有额度的供应商此前只在每个凭据下逐条列出余额,汇总行里什么都不显示——同一账户挂多个密钥时也看不出那是一个共享池。现在汇总给出池级总额,共享池只计一次。

    • EN: /usage now reports a prepaid balance. A provider that reports only a remaining balance and no allowance listed it per credential and showed nothing in the summary, so several keys on one account never read as a single shared pool. The summary now totals the pool, counting a shared pool once.
  • 工作目录被删除后,会话不再崩溃:长任务里临时目录、已移除的 worktree、整个项目被删掉,都会让会话的工作目录在运行中消失;此前这一刻会在准备子 shell 环境时抛错并中断会话。现在环境的默认工作目录取会话启动时记录的那一个,删除之后仍能继续跑。

    • EN: A session no longer crashes when its working directory is deleted. In a long-running task a temp directory, a removed worktree, or a whole project can disappear underneath the session; that used to throw while preparing the child-shell environment and interrupt the session. The environment now defaults to the directory recorded at session start, so work continues after the deletion.
  • /debug 的内存报告不再因为快照太大而失败:此前堆快照按字符串生成,堆大的时候会超过字符串长度上限直接报错;快照为空时又会静默落下一个 0 字节文件。现在快照按二进制生成,并在为空时明确报错。

    • EN: A /debug memory report no longer fails because the snapshot is too large. Heap snapshots were generated as a JavaScript string, which exceeded the string length limit on a large heap; an empty snapshot silently produced a zero-byte file instead. Snapshots are now generated as binary data, and an empty one raises an explicit error.
  • MCP 服务器重连后不再逐次累积内存:每次重连都会重新包装该服务器暴露的工具,而上一代的包装仍被引用着,于是重连次数越多、占用越大。现在工具包装共享同一个上下文工厂,并且在新一代替换成功之后释放上一代的引用。

    • EN: Reconnecting an MCP server no longer accumulates memory on every reconnect. Each reconnect re-wrapped the tools the server exposes while the previous generation stayed reachable, so memory grew with the number of reconnects. Tool wrappers now share one context factory, and the previous generation's references are released once the new one has replaced it.
  • 内容折叠回去后,滚动条有时会留在屏幕上:内容收进可视高度(切换标签页、折叠一段工具输出、消息被合并)时轨道不会立刻收回,要等窗口尺寸变化才会纠正 —— 那时候它会停在半途,或者干脆指向一个已经不需要指示的地方。现在内容一旦不再溢出,轨道就会收回。

    • EN: After content collapses back into view, the scrollbar could stay on screen. When content folded into the visible height — switching a tab, folding a tool output, messages merging — the rail did not retract immediately and only corrected itself on a window resize, at which point it could sit mid-content or point at something it no longer indicated. The rail now retracts as soon as there is nothing left to scroll.
  • 滚动条可能压在弹出的对话框底下:轨道的层级此前按容器算一次就记住,之后弹窗打开、面板最大化都不再看 —— 于是那条本来该跟着内容走的轨道沉到了遮罩之下。现在每次重新测量都重新读层级。

    • EN: The scrollbar could end up beneath a dialog that opened over it. The rail's stacking level was resolved once per container and then remembered, so a toast opening or a panel maximizing never updated it and the rail sank under the scrim. The level is now re-resolved on every measurement.
  • 滚动条层级会随容器切换越攒越多:每滚过一个容器就把它的尺寸监听器挂上且从不摘下,长会话里滚过的每个列表都被钉在内存里。现改为按槽位复用,回收时一并解除。

    • EN: The scrollbar accumulated observers as you moved between containers: each one scrolled was watched and never unwatched, so every list you had scrolled stayed pinned in memory over a long session. Observation is now per slot and released when a slot is recycled.
  • 标签条上那条拖不动的纵向滚动条没有了:横向滚动的标签条(轨迹 / 文件 / 上下文,右栏面板 tab)为了横向滚动开了 overflow-x,纵向就被裁掉;但被裁掉的溢出仍然算进 scrollHeight,所以只要尺寸差 2px 就会凭空画出一条纵向轨,正好压在标签的关闭按钮上。现在只有真正能滚的那条轴才画把手 —— 被裁掉的溢出不算溢出。

    • EN: The phantom vertical scrollbar on tab strips is gone. A horizontally scrolling tab strip (trajectory / files / context, right-panel tabs) opens overflow-x for the sideways scroll, which clips the vertical one — and the clipped overflow still counts toward scrollHeight, so a 2px difference was enough to grow a vertical thumb, sitting exactly on top of a tab's close button. Only an axis that can actually scroll now grows a handle: clipped overflow is not overflow.
  • 滚动时每帧都在读布局:轨道此前在每个滚动帧里重新读一次容器的样式、位置和尺寸,再把这些结果写回 left / top / height,所以一次滚动会触发多次强制重排。现在这些只在容器尺寸真的可能变化时读(首次出现、窗口变化、内容增删、鼠标移入、换皮肤),滚动帧只读滚动距离、只写位移。

    • EN: Every scroll frame read layout. The rail re-read the container's styles, position and size on each frame and wrote the results back as left / top / height, so one scroll forced several reflows. Those reads now happen only when the container's box can actually have changed (first sight, window resize, content churn, pointer entry, skin switch); a scroll frame reads the scroll offset and writes a transform.
  • 窄窗口下设置侧栏的宽度跟上了折叠:窗口变窄时左侧选项会缩成图标栏,但外框仍停在主侧栏那个可拖动的宽度上 —— 于是图标浮在一条空带里,看起来像是设置页也有一个能随手拖宽的侧栏。现在外框和图标栏一起收成 64px;两者写在同一条规则里,正是因为它们此前分散在两个文件,才会一个跟着折叠、另一个不跟。

    • EN: The settings sidebar now follows the collapse on narrow windows. The left-hand options collapse to an icon rail, but the frame around them kept the main sidebar's user-dragged width, so the icons floated in an empty band and the settings page looked like it had a sidebar of its own to drag wider. The frame and the rail now collapse together to 64px, declared in one rule because keeping them in two files is how one of them came to follow the collapse and the other did not.
  • 界面里的报错不再伪装成模型说的话:一次失败的请求此前会占着助手行的头像与行内缩进,把供应商返回的原始报文用红色等宽字体整段倒出来 —— 读起来就像模型自己说了这句,但它其实是关于这次请求的事实。「重试失败」那张卡也走同一条路径,于是同一次失败能在两副面孔下各出现一次。现在两处都走同一个错误呈现组件:按形状而非按文案切分,摘要只留用户能操作的那句,其余折叠进「显示详情」。判定靠形状而不是供应商措辞,是因为改一次供应商文案就会静默改变分类结果。

    • EN: A failed request no longer masquerades as something the model said. It used to take the reply's avatar and gutter and print the provider's raw body in red monospace, which reads as the model having said it — but it is a fact about the request. The retry-failure card took the same path, so one failure could appear twice in two guises. Both now render through one error component, splitting by shape rather than by phrasing: the summary keeps the line the user can act on and the rest folds behind "Show details". Shape rather than wording, because renaming a provider's phrasing would silently reclassify the failure.
  • 「余额不足」不再显示两遍:请求报文与供应商捕获的响应合并时,去重比对的是带后缀的那份(形如 … (type=1113)),所以当供应商自己的消息就是捕获正文时永远判不出来,正文被追加一次 —— 一个 429 读起来就成了「余额不足… 余额不足… (type=1113)」。现在正文与元数据分开携带、各补各的缺:正文只出现一次,供应商错误码仍然可见。

    • EN: "Insufficient balance" no longer prints twice. When the request message was merged with the provider's captured response, deduplication compared the decorated form — the one carrying a (type=…) suffix — so a provider whose own message was the captured body never matched and the body got appended. A 429 read as "insufficient balance … insufficient balance (type=1113)". The merge now carries body and metadata apart and fills in whichever is missing: the body appears once, and the provider error code stays visible.
  • 终端里的报错按终端宽度折行,不再按固定列截断:TUI 打印失败回合的两个位置(编辑器上方的钉住横幅、会话内联的报错块)此前都先丢掉空行、把每行按固定列截断再渲染。这对供应商报错恰好是反的:400/429 的报文通常是一整行 JSON 或长文本,而解释失败原因的那句在尾部 —— 截断恰好切掉用户最需要的那部分。而且截断发生在折行之前,窄终端下等于被切两次。改为按渲染时的真实宽度折行、折行后再限高,所以任何宽度下块高都可预期。折行本身还需要第二遍处理:Bun.wrapAnsi 只在空白处断行,无空白的整块 token(JSON 信封、base64、长 URL —— 供应商报错大多是这类)会原样超宽返回,现在改为按可见宽度切片,并把该行的转义序列在每片两侧重新输出,颜色既不会跨折行渗出也不会中途消失。

    • EN: Terminal errors wrap to the terminal instead of being cut at a fixed column. Both TUI surfaces that print a failed turn dropped blank lines and truncated every line before rendering, which is backwards for provider errors: a 400 or 429 body arrives as one long line whose tail carries the explanation, so the cut removed exactly the part a user needs — and on a narrow terminal the text was cut twice, once at the column limit and again by the renderer's wrap. Wrapping now happens against the real render width and the clamp follows the wrap, so the block's height is predictable at any width. The wrap needed a second pass: Bun.wrapAnsi breaks at whitespace only, so a token with none in it — a JSON envelope, a base64 blob, a long URL, which is most of what a provider error body turns out to be — came back as one over-wide row. Those rows are now sliced by visible width with the row's escape sequences re-emitted around each piece, so colour neither bleeds past the fold nor stops mid-row.
  • 重试耗尽时不再把重试器的内部账目塞给用户:超过延迟上限而中止时,用户看到的是「Provider requested 1800000ms wait, exceeds retry.maxDelayMs (300000ms). Original error: …」—— 可操作的原因排在最后,压在我们自己的一句配置说明后面。本地续跑失败那条路径取的是相反的赢家:钩子坏了是用户该修的,供应商错误已经钉在屏幕上了,所以以本地原因为主,供应商原文进日志。

    • EN: Retry bookkeeping no longer reaches the user at the point retry gives up. A rate-limit abort over the delay cap showed "Provider requested 1800000ms wait, exceeds retry.maxDelayMs (300000ms). Original error: …", putting the actionable reason last, behind a sentence about our own configuration. The local-continuation path picks the opposite winner: a broken hook is the user's to fix and the provider error is already pinned on screen, so that one leads with the local cause and logs the provider text.