Skip to content

v0.1.11

Choose a tag to compare

@github-actions github-actions released this 02 Jul 16:36

更新日志

这是一个稳定性大版本:一次六路并行的全库审计(上下文核心 / 服务层 / 工具层 / 消息层 / Web 前端 / 文档漂移)排出的全部 P1/P2/P3 问题,在本版全部修复——每一处修复都有回归测试钉住,并由独立的只读对抗审计复核。1419 项后端测试 + 239 项前端测试全绿,另用无头浏览器对全新实例做了「引导 → 唤醒 → 对话 → 错误可见」的端到端真机验证。

修复(正确性 / 安全)

  • 打断不再毒化上下文:一个回合在工具调用中途被打断(你发新消息抢占了角色的自我工作,或点了停止),过去会在持久上下文里留下「有 tool_calls 却没有对应结果」的残缺记录——此后每个回合都会被严格的服务端以 400 拒绝,直到 /reset。现在中断路径会为每个未应答的工具调用合成一条 [interrupted] 结果(hermes 同款),上下文永远完整。顺带:因网络 / 流错误被截断的回复,现在标注的是诚实的「连接中断」,而不再伪造成「被操作者打断」。(b0105e4)
  • 僵尸回合根除:强制接管一个超时未退出的旧回合后,旧回合曾可能「复活」并与新回合同时写同一份上下文/转录。中断标志改为每回合独立,旧回合再也无法吞掉新回合的打断信号。
  • execute_code 死锁解除:工具网关的调度锁过去罩住整个工具执行体,子脚本回调工具时从另一线程撞锁,每次都卡满 300 秒。锁现在只护住审计与守护记录的临界区——子任务(delegate)也因此获得了真正的并行。
  • 消息网关安全修复:陌生人给机器人发一条私信(即使被拒绝),过去会把角色后续所有主动发言(superchat)的投递目标劫持到陌生人。现在只有通过白名单的发信人才会更新持久投递目标(五个平台 + 宿主 + 独立网关全部修正)。
  • 浏览器工具守卫补全browser_console 的 JS 求值与 browser_cdp 的导航类调用(Page.navigate / Target.createTarget / Runtime.evaluate 等)过去可以绕过 browser_navigate 的 file:// 与内网/元数据(SSRF)拦截,读取宿主文件。现在三条路径共用同一套 scheme + SSRF + 密钥目录守卫。

修复(行为)

  • 切换模型不再遗留旧窗口/model/provider 与重配置现在同步重算真实上下文窗口裁剪缓冲。过去从 1M 窗口的模型切到 128K 会带着过期的 1M 上限逢回合 400;更糟的一种组合会把裁剪目标算成 0,下一条消息静默清空整个活动上下文。两者都不可能再发生。(b0105e4)
  • 压缩不再复读历史:上下文压缩为恢复而重存的「保护尾」过去会在每次重开聊天与导出里重复出现(实测一次压缩 83 条重复)。重存行现在带 replay 标记:恢复用它,显示与导出跳过它。
  • 变更型命令与回合互斥/compact/model <id> 等会改写共享上下文的命令,过去可以和进行中的回合并发执行(丢消息、请求视图损坏)。现在回合进行中它们干净地拒绝,运行时也会占住回合槽。
  • 搜索不再谎报「0 匹配」search_files 在超时或沙盒拒绝时过去返回干净的 0 结果,把模型带进死胡同;现在如实报错「搜索未完成」。
  • 子任务超时按各自起点计:过去按等待顺序累加墙钟,排队未跑的任务会被伪造成「超时」;现在每个子任务从自己启动时刻计时,从未启动的如实报告。
  • browser_vision 在沙盒下可用了:截图过去写往被所有浏览器沙盒拒绝的 ~/.lunamoth/cache,且给出的绝对路径无法通过 MEDIA 投递。现在落在工作区 workspace/screenshots/ 并给出相对路径。
  • 消息层整轮加固:QQ 发送改为按 echo 关联 OneBot 回执(被拒发送不再假装送达);适配器崩溃即时反映为 error 状态(不再永远显示「运行中」),微信 / Telegram / Slack 的一次性启动检查移入重试循环;Discord / Slack 出站对 429(尊重 Retry-After)与 5xx 有界重试,长消息中段失败不再无声丢尾;gateway.stop 落入每平台开关,不再被无关的配置保存悄悄翻回;关闭自主模式不再切断正在进行的微信回复。
  • Web:重进对话不再复读、断线自动重连:离开再进入一个对话过去会把已完成的回合渲染两遍(帧重放与转录恢复叠加);睡眠 / 网络抖动后聊天窗过去永久失联。现在全新进入同步序号、绝不重放;断线按退避永久重试,原位续流,接不上就干净地整页重挂——不会留下无声的空洞。

新增 / 改进(Web)

  • 聊天背景与立绘终于有了开关:聊天设置面板新增 聊天背景 / 背景淡化 / 立绘位置 / 立绘浓度——按角色记忆、即调即生效(渲染管线早已就绪,此前只是没有写入侧 UI)。(27bf74d)
  • 流式中带着草稿也能停止:停止(■)现在只要在生成中就一直可见,与发送(↑)互不挤占——打了一半的字不再挡住你叫停。
  • 市场适配手机:详情页在小屏上撑满整个面板,导入栏吸底并避开安全区;筛选浮层限高可滚动。
  • 键盘与读屏可达:看板卡片、卡组书脊、聊天面板行都是真按钮(Enter/Space、可见焦点环、aria 标签,内层按钮不会双触发);弹窗获得完整对话框语义(焦点陷阱、Escape、焦点归还)。
  • Superchat 已读态失败开放superchat.read 写入失败过去让整段历史永远显示未读;现在打开即本地记已读并带重试持久化,水位取气泡的服务器时间戳(客户端时钟偏移不可能「预读」未来消息)。

其余(防御纵深)

请求日志剥离图片字节并按字节裁剪;OpenRouter 目录拉取失败不再被终身缓存(120 秒冷却后重试);/reset 的纪元推导并入写事务,瞬时读失败不可能把角色回卷到远古历史;卡片保存/删除在解析后的真实路径上确认归属;网络开关缺省值漂移修正(旧 env_status.json 不再把角色悄悄断网);MCP 媒体写入路径消毒并确认在媒体目录内。


Changelog

This is a stability release: every P1/P2/P3 finding from a six-track full-codebase audit (context core / server / tools / messaging / web / doc drift) is fixed here — each fix pinned by regression tests and re-checked by an independent adversarial review. 1419 backend + 239 frontend tests green, plus an end-to-end headless-browser verification of a fresh install (onboarding → wake → chat → visible-error).

Fixes (correctness / security)

  • Interrupts no longer poison the context: interrupting a turn mid-tool-call (your message superseding self-work, or the stop button) used to leave tool_calls without matching results in the durable context — after which every later turn got a 400 from strict endpoints until /reset. The interrupt path now synthesizes an [interrupted] result for every unanswered call (the hermes shape), so the context is always well-formed. Relatedly, a reply cut by a stream/HTTP failure is now marked honestly as a connection error instead of being fabricated as "cut off by the operator". (b0105e4)
  • Zombie turns eradicated: after a forced takeover of a stuck turn, the old turn could wake up and write the same context/transcript concurrently with the new one. The interrupt flag is now per-turn; a zombie can no longer swallow the new turn's interrupt.
  • execute_code deadlock fixed: the tool gateway's dispatch lock used to cover the whole tool body, so a child script calling back into a tool from another thread wedged every such turn for ~300 s. The lock now guards only the audit/guard-record critical section — which also makes delegate workers genuinely parallel.
  • Messaging security: a stranger DMing the bot (even while refused) used to hijack the delivery target of all future proactive speech. The durable target now moves only after the sender passes the allow-list (all five platforms + host + standalone gateway).
  • Browser guard completed: browser_console JS eval and navigate-shaped browser_cdp calls (Page.navigate / Target.createTarget / Runtime.evaluate …) could bypass browser_navigate's file:// and private-network/metadata (SSRF) screening and read host files. All paths now share the same scheme + SSRF + secret-dir guard.

Fixes (behavior)

  • Model swaps resync the context window: /model, /provider and reconfigure now recompute the real window and the trim buffer together. Swapping from a 1M-window model to a 128K one used to carry the stale 1M cap (400 every turn); one combination could compute a trim target of zero and silently wipe the entire live context on the next message. Neither can happen now. (b0105e4)
  • Compaction no longer duplicates history: the protected tail re-persisted for restore used to appear twice in every chat reopen and export (83 duplicated messages measured after one compaction). Re-appended rows are now marked replay: restore uses them, display and export skip them.
  • Mutating commands are exclusive with a turn: /compact, /model <id> and friends used to run concurrently with an in-flight turn (lost messages, corrupted request view). They now refuse cleanly while a turn runs, and hold the turn slot while they execute.
  • Search never fakes "0 matches": a timed-out or sandbox-refused search_files used to return a clean zero, sending the model down the wrong path; it now reports "Search did not complete" honestly.
  • Delegate timeouts are per-child from each child's own start: the wall-clock used to accumulate in wait order, fabricating "timed out" for tasks that never ran; never-started tasks are now reported as such.
  • browser_vision works under the sandbox: screenshots used to target ~/.lunamoth/cache (denied by every browser jail) and advertise an undeliverable absolute path; they now land in workspace/screenshots/ with a MEDIA-deliverable relative path.
  • Messaging hardening across the board: QQ sends correlate OneBot acks by echo (a rejected send is no longer treated as delivered); a crashed adapter flips status to error instead of a stale "running", and weixin/Telegram/Slack one-shot startup checks moved into retry loops; Discord/Slack outbound retries 429 (honoring Retry-After) and 5xx bounded, and a failed middle part of a long message no longer silently drops the tail; gateway.stop materializes per-platform flags so an unrelated config save can't flip it back on; turning autonomy off no longer cuts a live WeChat reply.
  • Web: no duplicated turns on re-enter, and the chat reconnects: leaving to the board and re-entering used to render completed turns twice (frame replay stacked on transcript restore); after sleep or a network blip an open chat was permanently dead. Fresh entries now sync the sequence and never replay; the socket retries forever with backoff, resumes in place, and falls back to a clean re-attach when the gap can't be bridged — never a silent hole.

Added / improved (web)

  • Chat backdrop & sprite finally have controls: the chat settings pane gains 聊天背景 / 背景淡化 / 立绘位置 / 立绘浓度 — per-character, instant apply (the render path existed; only the write-side UI was missing). (27bf74d)
  • Stop works with a draft typed: the stop (■) button now stays visible whenever streaming, alongside send (↑) — a half-typed message no longer hides the way to interrupt.
  • Market on phones: the detail sheet fills the screen with a sticky, safe-area-aware import bar; the filter popover is height-capped and scrollable.
  • Keyboard & screen-reader access: board cards, deck spines and chat-panel rows are real buttons (Enter/Space, visible focus ring, aria labels, no double-fire on inner buttons); modals gain full dialog semantics (focus trap, Escape, focus restore).
  • Superchat read-state fails open: a failed superchat.read used to leave the whole history rendered unread forever; opening a chat now marks read locally with retried persistence, and the watermark uses the bubbles' server timestamps (client clock skew can never pre-read a future superchat).

Everything else (defense in depth)

The request log elides image bytes and trims byte-aware; a failed OpenRouter catalogue fetch is retried after a cooldown instead of being memoized for the process life; /reset's epoch derivation moved into the write transaction so a transient read failure can never rewind a character to ancient history; card save/delete confine on the resolved path; the network-default drift is fixed (an old env_status.json can no longer silently take a character offline); MCP media writes are sanitized and confined.