Repository navigation
Releases: Mieksido/surfcore
Release list
SurfCore v0.7.9(修复 zip 安装自动更新 100% 失败)
修复:点「立即更新」反复失败,版本始终不变(verify.staging-mismatch)
这是开发端的结构性错误,影响所有 zip 安装(无 git)用户。
根因(两层):
scripts/release-manifest.mjs生成清单时扫的是开发工作目录,把 gitignore 的_backups/(119 个)、audio/、obs-diag.json、本地改过的 docs 都列了进去;- 用户下载的是 GitHub 归档,天然不可能包含这些文件,而
verifyStaging()要求清单里每个文件都存在且哈希一致 → 恒假 → 每次都在应用前失败,永远到不了暂存。
修法:
- 清单只列
git ls-files(归档里有什么就列什么);工作区有未提交改动时生成脚本直接报错; - 有版本号时下载 tag 归档(
refs/tags/vX.Y.Z.zip),不再用会漂移的main.zip; - 校验改为交集:只比包里真实存在的文件;缺失只计数记日志,不判失败;
- 旧脏清单自愈:出现「新包没有的路径」就用包内清单覆盖安装记录(git 工作副本跳过);
- 按提交判定有更新时用
releases/latest的 302 补取版本号(显示与 tag 下载都需要); - 失败不再静默:
state.lastApplyFailure持久化,弹窗内常驻红块(code / 原因 / 建议 / 复制诊断 / 去下载新版);失败即删除本次 staging,不再累积磁盘。
⚠️ 升级方式(重要)
0.7.1–0.7.8 的 zip 用户无法自动升到本版——旧版仍会执行旧的“全量校验”,必然失败。请手动覆盖一次:
- 下载 https://github.com/Mieksido/surfcore/archive/refs/tags/v0.7.9.zip
- 解压后覆盖到安装目录(不要删除目录本身)
- 完全退出 DSH 再打开
从 0.7.9 起,清单与归档一致,zip 自动更新恢复正常。
验证
- 新增
test-release-manifest:清单只含 git 跟踪文件、无运行数据目录、版本与 package.json 一致; test-update-apply-result新增:清单缺文件不判失败、tag 归档地址、失败清 staging、失败状态常驻;npm run test:audit85/85;插件测试全绿。
调查记录:docs/cases/2026-10-30-update-verify-staging-mismatch.md
SurfCore v0.7.8(语音页保存不再误报失败)
修复:填完云端密钥点保存,红字「保存失败: toast is not a function」——其实已保存
根因:设置页里 toast 是浮层文案字符串,真正的通知函数叫 notify。h(VoiceTab, { toast }) 把字符串当函数传,保存成功后调用它抛 TypeError,又被 catch 改写成「保存失败」。语音页 8 处提示全部因此失效;真正的保存错误也会被这句顶掉。
修法:
- 接线改为
toast: notify;浮层状态改名toastText,彻底消除同名遮蔽; - 云端保存顺序改为「保存 → 刷新表单 → 通知」,通知失败不再改写保存结论;
- 契约测试新增:所有 Tab 的
toast必须是函数、VoiceTab 必须收notify、状态必须叫toastText。
文档:跨层契约规范
docs/guides/ERROR-REPORTING.md 新增一节(近期三起同类事故的总结):
- 同名不同物必须改名(状态
toastText/ 函数notify); - 成功路径上的界面失败,不得改写业务结论;
- 跨进程/跨层数据必须显式投影并读回断言(配置投影、启动快照)。
验证
npm run test:audit 84/84;插件测试全绿(含 test-client-contract 新断言)。
SurfCore v0.7.7(手动覆盖安装后不再提示旧版本)
修复:手动覆盖到 v0.7.5 后,更新弹窗仍显示「v0.7.3 → 提交 db18896」
根因(两层):
- 手动覆盖只换文件,旧的
.installed-manifest.json/.installed-meta.json还留在目录里(版本 0.7.3、旧提交号)。检查更新拿「旧提交 vs 远端提交」比,得出“有新提交”的假结论。 updates.json里缓存着上一次检查结果;手动升级后缓存没有失效,弹窗继续显示旧结论。
修法:
- 检查时做安装证据版本校准:包里
release/install-manifest.json的版本与现存清单不一致 → 刷新清单、重写只带版本号的安装记录(丢掉旧提交号),改走版本号比对; - 服务启动时丢弃已过期结果:安装目录
package.json版本与缓存结果里的installedVersion不一致 → 丢掉该条,下次检查按当前版本重新算。
效果:手动覆盖到 0.7.5 的设备,弹窗会显示「v0.7.5 → v0.7.7」,而不是「v0.7.3 → 提交 db18896」。
验证
- 新增两个用例:覆盖安装后证据刷新(清单/记录版本对齐、不再按旧提交判定)、过期结果被丢弃;
npm run test:audit84/84;插件测试全绿。
说明
只改 surfcore;没有动语音包仓库。
SurfCore v0.7.6(语音工具常注册 + 设置不再被覆盖)
修复:语音“三个闸门”与设置自己回弹
1. 工具常注册(不再“重启才生效”)
以前 qq_send_voice / qq_list_voices 是在 MCP 进程启动时按配置决定注不注册。改了开关没重启,模型手里根本没有这个工具,只能说“我没这个功能”。
现在工具始终注册;是否允许发送由桥接在调用时判定。
2. 拒绝时说清卡在哪一关
VOICE_DISABLED—— 语音总闸未开;VOICE_TOOL_DISABLED—— 工具开关未开;
两者都带 hint 指到具体设置位置(如「设置 → SurfCore → 全部参数 → socialV2.voice.enabled」);MCP 保留 code/hint,模型能照着提示让用户去开。
3. 设置不再自己回弹
保存配置时回传加载时的版本戳(文件 mtime+size)。服务端发现配置已被控制台或其它窗口改过 → 返回 CONFIG_STALE 并拒绝整份回写,提示「重新读取后再改」,不再把别人刚改的语音开关覆盖回 false。
验证
- 新增
test-voice-gates(21 项断言:版本戳语义、工具常注册、拒绝 code/hint、客户端带戳与 409 处理); npm run test:audit84/84;插件测试全绿。
说明
这一版只改 surfcore(插件/桥接/MCP),没有动语音包仓库。
SurfCore v0.7.5(更新提示不再反复弹同一提交)
修复:点「立即更新」后又弹回来,提交号不变
现象:设备显示「SurfCore 本体 v0.7.2 → 提交 ddc0d81」,点更新后弹窗再次出现,仍是同一个提交。
根因:该安装目录既没有 .installed-manifest.json(上次安装清单)也没有 .installed-meta.json(安装记录)。v0.7.2 只在应用阶段做这个检查,失败后不记录 dismiss,所以下次检查又弹同一条;而弹窗不展示失败原因,看起来像“点了没反应”。
修法:
- 检查阶段就判定:缺安装证据但有新版 →
status=blocked(code=evidence-insufficient),带blocked字段,不再进入待办列表,弹窗不再重复; - 卡片常驻显示「更新被阻止:原因 + 建议」;
- 自愈:目录里存在包自带的
release/install-manifest.json时,自动补写.installed-manifest.json与.installed-meta.json—— 手动覆盖安装过一次后即可恢复自动更新; - 远端版本号取不到时,用
releases/latest的 302 tag 兜底,界面显示v0.7.4而不是提交号; apply()的 blocked 结果保留 code/hint,不再只给一句 error。
影响用户的操作
- 升级到 0.7.5(手动覆盖一次,因为当前目录缺安装证据,自动更新会被正确阻止);
- 覆盖后插件启动会自愈补登记;
- 之后再检查更新就能正常自动更新。
验证
- 更新 fallback 用例 10 组(含 blocked、自愈、tag 兜底)全过;
- audit 83/83;插件测试全绿。
SurfCore v0.7.4(管理员称呼投影修复)
修复:设置了称呼「弥叭」,QQ 里仍叫「管理员」
现象:v0.7.2 用户把管理员称呼设为「弥叭」,QQ 提示词仍生成 称呼「管理员」,模型输出「弥叭管理员」。
根因:
- 保存管理员卡片时,
projectAdminSave只把ownerQQ投影到 QQ 的config.json,displayName/biliName/gentle留在 unified; - 桥接是独立进程,只读 QQ
config.json,读不到 unified 里的称呼; - 没名字时
adminDisplayName兜底返回'管理员',把身份词当成了称呼; - 桥接只在启动时读配置。
修法:
- 完整 admin 投影写入 QQ config(
displayName/biliName/gentle); adminDisplayName没名字时返回空串,不再把「管理员」当称呼;提示词明确「管理员是身份/权限标记,不是称呼」;- 插件启动时
ensureAdminProjection()自动补投影;桥接在跑会自动重启; adminFromSources在 unified 未写 admin 时回退 QQ 已有投影,避免升级丢称呼。
存量用户:升级后无需手动重存;插件启动会补投影并重启桥接,QQ 提示词变为 称呼「弥叭」。
验证
test-admin-identity、test-admin-prompt-wiring通过;npm run test:audit83/83;- 本机 config 实测提示词含 称呼「弥叭」、不含 称呼「管理员」。
调查记录:docs/cases/2026-10-29-admin-display-name-projection.md
SurfCore v0.7.3(中文目录选择乱码修复)
修复:原生「浏览…」选中文目录后路径乱码
- 现象:设置页「导入离线包」选中文目录(如
D:\...\中文oice-local),弹窗报IMPORT_PATH_NOT_FOUND,路径里出现�/◆;实际目录存在。 - 根因:Windows PowerShell 5.1 重定向 stdout 使用 GBK,Node 按 UTF-8 解码,中文路径损坏。
- 修法:PowerShell 把选中路径写进 UTF-8 临时文件,Node 读文件后删除,不再解析 stdout;导入 / 安装目录 / SnowLuma 三处「浏览…」共用
handleBrowse,一处修复全部生效。
调查记录:docs/cases/2026-10-29-browse-chinese-path.md
验证
node --check plugins/surfcore/lib/index.js通过;- PowerShell 中文路径写 UTF-8 临时文件、Node 读回一致(已实测);
- 真实选择框需重启 DSH 后手点一次「导入离线包」选中文目录。
说明
这一版基于 v0.7.2;未改动更新检查/备份/回滚逻辑。
SurfCore v0.7.2(可选 GitHub 令牌,解决限流)
新增:可选 GitHub 令牌
更新卡片下新增「GitHub 令牌(可选)」:
- 粘贴一个只读 token 后,检查更新走你账号的 5000 次/小时,不再受匿名 60 次/小时(按 IP 共享)限制;
- 留空仍按匿名请求,行为不变;
- 令牌只存在本机
config.json的updates.githubToken,界面不回显、日志不打印、诊断信息不含它,可随时「清除」。
生成方式:GitHub → Settings → Developer settings → Fine-grained tokens → 只需要 Public repositories (read-only)。
文案
- 未配置时明确提示「匿名 60 次/小时,共享 IP 时可能遇到“查不到:限流”」;
- 配置后显示「已配置(走账号额度 5000 次/小时)」。
验证
token注入/不注入两态断言通过;- audit 83/83;插件测试全绿。
说明
这一版只是把“限额”从 60 提到 5000;GitHub 的匿名 60 次限制本身无法在设置里取消(平台固定规则)。
SurfCore v0.7.1(更新系统不再静默失败)
修复:更新系统不再把失败伪装成成功
一、检查更新必须说清三种结果
- 没有 git、没有安装记录时,改为按版本号比对,不再直接判「最新」
(事故现场:本地 0.6.22、远端 0.7.0,却显示「最新 v0.6.22→1ad2042」且没有更新按钮); - 取不到远端版本号 / 远端不可达 /
rev-list失败 → 显示「查不到:原因(建议)」,不再冒充「最新」; - 卡片按状态渲染:有更新 / 已是最新 / 查不到 / 被阻止 / 检查失败。
二、失败必须给出原因与下一步
- 下载:
download.http-XXX/download.timeout/download.network/download.write-failed; - 解压:
verify.unzip-failed/verify.bad-archive; - 更新助手:
stage.helper-missing/stage.spawn-failed(不再「下载成功即成功」); - 「已下载待重启」单列
pending_restart,明确写「尚未更新」; - 重启后卡片常驻「上次更新」:已校验成功 / 已应用未验证 / 待重启 / 失败原因 + 建议;
- 新增「复制诊断」。
三、不弄丢用户的东西
- 覆盖前读包内
release/install-manifest.json,先校验包与清单一致; - 本地改过的文件 → 阻止自动更新并列出;既无清单也无安装记录 → 阻止并给手动下载指引;
- 覆盖前真备份(逐文件回读 sha256),备份失败不覆盖;
robocopy /MIR改为/E:只覆盖、不删除,用户自建文件永不误删;- 更新后校验目标版本,不符写
.failed(verify.version-mismatch); - 新增回滚:从备份逐文件恢复并校验哈希与版本,失败保留备份并给人工恢复指引。
工具
- 新增
scripts/release-manifest.mjs:发版前生成release/install-manifest.json(当前 605 文件)。
验证
test-update-apply-result 40 项断言、test-update-version-fallback 7 组、audit 83/83、插件测试全绿。
已知残余
下载包的 sha256 / 数字签名尚未做(当前校验的是「包内文件与清单一致」);签名硬化列入后续版本。
SurfCore v0.7.0(离线导入 + 身份审计 + 云端凭据安全 + 失败可见)
新增
语音包离线导入(本地/云端)
- 设置页新增「导入离线包…」:选解压出来的
voice-local/voice-cloud文件夹即可,先展示类型/来源/目标/大小/版本/完整性确认; - 默认复制源目录、不移动;先校验类型/必需文件/manifest 哈希/目标运行状态/磁盘空间,再写目标;旧包先备份,失败自动恢复;
- 导入后改写
.venv/.venv-gsv的pyvenv.cfg指向包内 runtime;没有config.toml时从示例生成并清空云端凭据; - 失败有
code/error/hint/missing弹窗,可重选目录、先停止语音包;支持进度/取消、删除源目录/旧备份。
安全
- 云端语音包不再携带任何预置密钥:安装/导入时清空
cloud_app_id/cloud_access_key,用户自行填写;未配置时启动直接给出可照做的提示; - 回答者身份由程序标注:主人 / 群友(非主人);
[仅主人]只收窄回答权,非主人不消费提问、不吞消息; - 主人特权操作审计:7 类事件写入
state/owner-audit.jsonl,1MB×5 轮转 + 90 天清理,设置页可查,agent token 403。
修复
- SnowLuma 装在自定义目录时检测不到:增加手选路径「浏览…」,显式路径只验证它本身;检测失败弹错误报告(列出试过的位置 + 下一步);
- 参考音频「下载」在 Electron 里点击无反应:改成 fetch→Blob 下载,并加「打开所在目录」;
- 语音安装进度文案去重(阶段名已带百分比时不再拼日志尾)。
文档
- 新增
docs/guides/PERMISSIONS.md、docs/guides/ERROR-REPORTING.md、docs/guides/12-LESSONS-ARCHITECTURE.md、docs/RELEASE.md、docs/RELEASE-QUEUE.md; - 案例 6 篇(离线导入、参考音频下载、云端凭据、SnowLuma 检测、提问身份审计等)。
重要说明:离线语音包本版不发布
- 本地离线包(4.7G)内含 age12 微调权重,其素材链为「微软 Edge 晓伊 → CosyVoice 克隆 → 参考音频 → 训练」,上游授权不可考,不得公开分发;
- 云端离线包(249M)内含 piper(GPL-3.0)且为同进程组合,组合定性未决;
- 两个 zip 只留在本地 POC 目录;导入功能已在 SurfCore 内,拿到合规包后可自行导入。
验证
npm run test:audit 81/81;npm run test:regressions 全过;插件测试全绿;真实 7.9G 导入跑通(源目录保留、pyvenv 指向最终 runtime)。