Skip to content

Releases: Fish-under-sea/FISH-Dock

FISH Dock 2.1.1

Choose a tag to compare

@Fish-under-sea Fish-under-sea released this 05 Nov 13:42

FISH Dock 2.1.1

定稿:2026-11-05。宿主 2.1.1,兼容插件 2.x(官方目录当前 2.2.0:Clash / Magpie / 飞书)。
补丁版,两件事:第三方插件识别排除官方三个插件(#72),以及把自动检查更新里的 Gitee 拉取恢复。

一、第三方插件排除官方三个(#72)

现象:官方三个插件在 npm 上也有合规包名——@fish-under-sea/fish-dock-plugin-feishu、…-clash、…-magpie(plugins/tools/build-npm-packages.mjs 发布的名字)。第三方页的默认发现词就是 fish-dock-plugin,npm 检索因此把这三个官方包排在最前面,当作「可安装的第三方插件」列出来。它们不是第三方:身份由官方目录授予(Source::OfficialCatalog + 主仓 Star),从第三方通道装进去会被登记成第三方身份,装完也和商店条目对不上。

改动(只动 Dock,插件仓 catalog/stable.json 一字未动):

  • 官方身份只剩一份定义:plugin_store::OFFICIAL_IDS(fish.feishu / fish.clash / fish.magpie)。目录校验与第三方页排除都读它,不再各抄一份数组;官方 npm 包名也仍由 plugin_store::npm_package 构造。
  • 判据只认官方那份包名:plugin_thirdparty::official_package 比对 @fish-under-sea/fish-dock-plugin-<slug>,大小写无关。无 scope 的 fish-dock-plugin-magpie、@acme/fish-dock-plugin-magpie 都不是官方包,照常按第三方处理——依据是「官方身份」,不是「名字里带 magpie」。
  • 检索两路都排除:精确包名元数据查(①)与 /-/v1/search(②)命中官方包时不展示,并计入新字段 official;hidden 仍是「不符合 fish-dock-plugin-* 命名」的口径,两者不混在一个数字里。命令返回体 search_json 与前端夹具 app/test/fixtures/thirdparty-search.json 同步新增 official。
  • 安装口零网络拒绝:resolve_via 在命名判据之后、任何请求之前拦下官方包,返回明确理由(…` 是官方插件:请到「官方插件」页安装或更新),不再出现「装了但起不来」或「装成第三方身份」。
  • 界面说出来,不静默消失:第三方页提示「本页另有 N 个官方插件的 npm 包:官方插件请到「官方插件」页安装或更新」,规则行也补一句「官方插件不在本页」。

二、自动检查更新恢复 Gitee 拉取

背景:2.1.0 时 Gitee 通道是「配了才启用、默认空」,理由是当时主仓 Gitee 只有 tags、没有任何发行附件。现在那边已经有发行资产,实测(2026-11-05,匿名 API):

  • api/v5/repos/Fish-under-sea/FISH-Dock/releases 返回 v2.1.0,附件 FISH-Dock-Update-2.1.0.exe 与 FISH-Dock-Update-2.1.0.exe.sha256;
  • 那份 .sha256 是 5513ee3a5605a67e256fc23ecb68625ae92a0c0c18fe1f1a18e97b9fadd84406 FISH-Dock-Update-2.1.0.exe,与 GitHub v2.1.0 的 assets[].digest(sha256:5513ee…4406)逐字相同。

改动:

  • config::GITEE_REPO_DEFAULT = https://gitee.com/Fish-under-sea/FISH-Dock,url.gitee_repo 改为默认启用;off / none / 0(大小写不敏感)是明确的关闭写法——空串在这套 getter 里的既定语义是「没覆盖,用默认」,不能兼作开关。
  • 设置页「自动检查更新 · 从哪里下载」不再显示「Gitee 未配置」,自动 / Gitee / GitHub 三个选项都在(giteeConfigured 由 gitee_repo() 是否为空决定,不变)。
  • 探针名错位修复(AGENTS.md §8.1 记的上游缺陷①):检查器一直按 wakuwaku-v<ver>-windows-x64.exe(便携 exe 的历史名)去发行里找资产与 <exe>.sha256,而 2.0.x 起的发行只有 FISH-Dock-Setup-<ver>.exe / FISH-Dock-Update-<ver>.exe——所以 Gitee 侧连 .sha256 都探不到,整条通道等于没接。新增 updates::update_asset_name(FISH-Dock-Update-<ver>.exe,即应用内更新真正先下载的那份),Gitee 探针改按它取摘要。
  • 两侧比的是同一个文件:github_release 的 digest 也改取安装器载荷(Update 包)的摘要——它是 Gitee 侧唯一放上去的那份,两边摘要因此可以直接比对;两边不一致时仍按 2.0.4 的口径判 conflict、不装。发行里没有安装器载荷(例如只有便携 exe 的历史版本)时摘要为 None,不拿便携 exe 的摘要去比,避免造出假冲突。
  • Found.exe(便携载荷)语义没变:Gitee 不放便携 exe,所以恒为 None;不拿安装器顶替「换掉自己的 exe」。
  • 只认完整的 Gitee 发行:探不到 FISH-Dock-Update-<ver>.exe.sha256 就返回「那一侧没有」,不用半份发行(只传了 Setup 的那种)宣布有新版本,也不制造跨来源摘要冲突。Gitee 发布仍需「Update exe + 它的 .sha256」两件齐全。
  • 放行范围:allowed() 仍是「前缀 + 边界字符」;默认 gitee_repo 非空只意味着同一个 repo 的 Gitee 资产可下载,插件仓(FISH-DOCK-Plugins)与别人的仓照旧拒绝。

三、版本位

  • app/src-tauri/tauri.conf.json 与 app/src-tauri/Cargo.toml:2.1.1(同版)。
  • app/integrations/claude-code/.claude-plugin/plugin.json:2.1.1。它此前停在 2.0.4,而 one_version_everywhere 要求三处一致——2.1.0 那版漏了这一处,本版把这条测试修回绿。
  • AGENTS.md §八 与 app/docs/config-keys.md 同步(Gitee 现状从「有 tags 无附件」改为「有 v2.1.0 发行资产、默认启用」)。
  • README 的版本卡与发行段由 tools/render-readme.mjs 从 tauri.conf.json 渲染,出包时统一重渲染,不手改 PNG。

四、测试

  • plugin_thirdparty::tests::the_three_official_plugins_are_never_third_party(新增):判据只认官方包名、大小写无关;发现词检索把官方三个全部排除且 official = 3、hidden 只数真正命名不合规的包;安装口命中官方包时 asked == 0(零网络)且理由指回官方商店。
  • plugin_api::tests::the_search_command_carries_every_field_the_ui_reads:命令返回体与前端夹具字段一一对应,新增 official。
  • config::tests::mirror_prefixes_default_to_empty_and_prefer_whitelists_or_falls_back:gitee_repo() 默认等于 GITEE_REPO_DEFAULT;off/ none / 0 关掉;空白=没覆盖。
  • sources::tests:the_gitee_attachment_candidate_is_on_by_default_and_off_switches_it_away、a_malformed_gitee_repo_yields_no_candidate、default_candidates_are_locked_with_the_gitee_channel_off 按新默认重钉。
  • updates::tests:the_gitee_endpoint_is_on_by_default_and_switches_off_explicitly、the_gitee_candidate_stays_inside_the_same_allowance、the_allowance_stops_at_the_repository_boundary、github_read(摘要取安装器载荷、便携资产不掺和)。
  • 前端(node --test --test-isolation=none app/test/extension-panel.test.js,75 passed):被排除的官方插件数量单独提示,并指回官方插件页、官方插件与命名不合规同时出现时,两句提示都在。

五、安装包

安装包 使用场景
FISH-Dock-Setup-2.1.1.exe 首次安装、基础资源缺失或修复安装,包含完整离线运行时。
FISH-Dock-Update-2.1.1.exe 已有 2.1.0 且基础资源完整时的精简更新,只含相对 2.1.0 的变更;缺失或损坏时提示改用完整包。

精简包只对 2.1.0 有效(差分包按基线生成,跨版本会缺文件)。

六、已知限制

  • 便携版仍无应用内更新载荷:发行不提供便携 exe,Found.exe 保持 None;应用内更新只对安装版生效(装的是发行里的安装器)。
  • Gitee 侧必须传 Update 包:只传 FISH-Dock-Setup-*.exe 时 Gitee 不参与「有新版本」的判断(见第二节「只认完整的 Gitee 发行」)。app/scripts/gitee-release.ps1 默认只传 Setup,要恢复完整的国内更新链,发布时需再跑一次并传 Update 包和它的 .sha256。
  • 官方插件仍只以 catalog/stable.json + npm 双源 + GitHub Release 为来源;本次改动没有把 Gitee 接进插件三源。

FISH Dock 2.1.0(插件下载回退 / 卡面与滚动锁 / 自启可观测性 / 国内镜像)

Choose a tag to compare

@Fish-under-sea Fish-under-sea released this 04 Nov 15:21

FISH Dock 2.1.0

定稿:2026-11-04。宿主 2.1.0,兼容插件 2.x(官方目录当前 2.2.0:Clash / Magpie / 飞书)。
本版收口主仓六个 OPEN issue 里的四条 + 一条历史欠账,并补齐 2.0.4 的两处遗留。

一、插件下载:三源全灭时按来源回退上一可用版本(#68)

目录里的版本跑在制品前面时(目录写了新版、npm 与 Release 还没有),用户过去只会看到「检查网络或代理后重试」——而网络是好的。

  • 版本候选:优先读官方目录的版本历史条目;目录没有该字段时用编译期内置表 KNOWN_GOOD(12 条,摘要取自官方发行,经 GitHub Releases API 的 assets[].digest 逐条核验 12/12 一致)。比目录更新的内置条目不作回退目标。
  • 下载前探活:对每个来源每个候选做 HEAD,只对 404/410 定论「制品不存在」;403 不定论,照旧发起完整下载(限流或 CDN 拒 HEAD 证明不了缺制品,一次限流不该把可用来源判死)。
  • 按来源降级:fetch_from 改为「逐来源 × 逐候选(新→旧)」,某条来源最近可用版本成功即返回,并带出实际装上的 effective_version;三源都失败时把各候选的 attempts 一并交给面板。
  • 界面保留新版:目录数据一个字节不改,卡片照常显示最新版;装完后提示「已安装 vX;目录最新 vY 暂不可用」,走绿色成功态而不是红色失败。
  • 摘要零放宽:期望摘要只取候选自带的官方目录摘要,装上的 digest 绝不等于自算摘要;错摘要按该来源失败处理并继续下一候选。
  • 跳转判据修正:hop() 从「只认两个 GitHub 资产域」放宽到「官方发行资产域 或 与当前请求同站点域」。这修掉了一个真实缺陷——npmmirror 的 tarball 的 HEAD 与 GET 都 302 到 cdn.npmmirror.com,此前一律被拒,于是「三源」实际只有两源可用,而国内用户最需要的恰好是死掉的那条。仍要求 HTTPS、拒绝跨域与端口、拒绝后缀伪装(npmmirror.com.evil.example 照样拒)。
  • 可诊断性:结构化下载失败按内容语义记进 recent.log(恰好一条,不重复),探活判死补 stderr;失败面板逐源显示 来源 · 结论 · HTTP 状态码 · 失败阶段 · 可复制 URL,title 保留原始技术串。

二、插件卡与浮层(#69)

  • 「更多」展开区从约 210px 压到 105px(目标 ≤120),日志↔卸载 9px(目标 ≤40);菜单行 32px 整行可点、组内仅一条分隔线、卸载置组末且常态红。
  • 目录名与功能页入口合并成一行(12px 次级色 + 省略 + 点击复制)。
  • 已安装卡标题 17px/600,状态点 6px、状态↔版本 8px,行内最大留白 10px。
  • Dock 弹窗锁滚动:确认弹窗打开时底层列表不再被滚走;进度浮层与失败块在锁定期仍可滚动(UNLOCKED_SCROLLERS 具名清单),关闭/取消/Escape/点背景四路都解锁并还原 scrollTop。
  • 新增「插件卡与浮层视觉规范」注释块(字号/行高/间距/危险色/滚动锁口径),防 #63 式反复。

三、自启失败可定位(#70)

预算与重试调度在 2.0.4 已存在(冷态一次尝试共用 60s、整轮 180s、每插件最多 4 次尝试),本版只补可观测性与对称性:

  • Failure 增 phase(verify/spawn/hello/activate/service)、attempt、elapsedMs(增字段,旧 plugin-failures.log 仍可解析)。
  • log_finish 的 ready/stopped 也落盘(复用单一写入路径,failed 不重复)。
  • 手动启用首次失败后补一次 3s 退避重试:退避在锁外做(不会冻住插件页 3 秒),醒来重取锁并复检 closed/stopping/代次。

⚠️ 这条的真机重启自启实测仍待执行(装本版后重启,看 plugin-failures.log 与托盘)。代码侧已闭环,真机结论未取。

四、国内镜像与 Gitee(历史欠账)

  • url.gitee_repo 仍是「配了才启用」,默认空;updates::endpoints() 在配置后加入 Gitee 候选。
  • sources::picks 增 Gitee 附件候选(仅配置时生效),candidates() 排序语义与共用 deadline 未改。
  • 行为变化(请留意):allowed() 从纯前缀比较收窄为「前缀 + 边界字符」。原先配 …/FISH-Dock 会连带放行 …/FISH-DOCK-Plugins(同名前缀),现在只放行边界匹配的地址。只收窄,不放宽。
  • 新增 app/docs/domestic-mirror-matrix-2026-11-04.md:六类资源逐项实测。结论摘要——raw 文件是唯一开箱即有免费国内镜像的一类(stable.json 在 raw.githubusercontent 与 jsDelivr 三域名字节与 sha256 完全相同);npm 官方与 npmmirror 的 tarball 四项校验全同;pet 图库 DNS 污染级不可达且无任何已确认镜像;Windhawk / LHM 无免费国内镜像。缺口如实标注,不编地址、不要求用户自建服务。
  • Gitee 现状(不吹):主仓 Gitee 有 13 个 tags 但没有任何 Release 附件(releases API 返回 [],具体附件 URL 全 404);插件仓 Gitee 的匿名可见性两次探测结果相反(待人工确认)。因此本版仍以 GitHub Release 为唯一发行源,Gitee 通道保持默认关闭。

五、安装包

安装包 使用场景
FISH-Dock-Setup-2.1.0.exe 首次安装、基础资源缺失或修复安装,包含完整离线运行时。
FISH-Dock-Update-2.1.0.exe 已有版本且基础资源完整时使用,只含相对 2.0.4 的变更;缺失或损坏时提示改用完整包。

精简更新包只对 2.0.4 有效:它是差分包,跨版本使用会缺文件。更早的版本请用完整安装包。

六、已知限制与残留

  1. 便携版更新入口仍不可用:updates::exe_name() 还在拼历史文件名 wakuwaku-v<ver>-windows-x64.exe,而真实发行名是 FISH-Dock-{Setup,Update}-<ver>.exe → 便携版的 can_install() 恒为 false(安装版走 installer_names,不受影响)。列为独立条目。
  2. npm 两源仍无新版:catalog/stable.json 已写 2.2.0,但 npm 官方源与 npmmirror 的 latest 仍是 2.1.5/2.1.5/2.1.6、2.2.0 tarball 404,只有 GitHub Release 有。插件更新目前依赖 GitHub 一条腿;回退逻辑正好覆盖这种情况。
  3. 真机未验:卡面尺寸(getBoundingClientRect)、触控惯性下的滚动锁、深浅主题的 .note.ok 对比度、以及 #70 的重启自启实测。
  4. .bad 无独立颜色规则:整套 CSS 里只有组合选择器带红,所以历史上那些“红色失败”其实不红。属既有观感问题,独立条目处理。
  5. 探活无整轮总 deadline:最坏 3 源 × 6 候选 = 18 次 HEAD;单次 fetch 内共用 deadline,整轮候选没有。
  6. KNOWN_GOOD 靠人工纪律维护:表过期后回退能力会静默退化到“只有目录当前版”。
  7. hop() 的同站点域是逐跳评估,比旧版略宽(仍限 GitHub 自有域 + 目录 sha256/size/manifest 三重校验)。

七、验证

  • 整 crate cargo check --offline --bin FISH-Dock → exit 0。
  • 全量 cargo test 并行连跑 3 次:每次 876 passed; 2 failed; 23 ignored,两条失败恒为 lhm_setup::tests 的沙箱 DACL 与 Task Scheduler 环境问题。
  • 前端:node --check 三文件 exit 0;前端夹具 466 passed(基线 462)。
  • node app/scripts/check-hardcoded.mjs → exit 0(合计 128/128/0、HTTP_URL 70/70/0);对比度无新增违规。
  • 插件仓 node --test preinstall.test.mjs → 26/26。
  • 改动面:主仓 15 文件 +2083/−175;插件仓 3 文件;catalog/stable.json 未改动。

八、插件侧配套

插件仓同期提交了 #71 预装登记改按基线差集增量补登记(plugins/magpie/preinstall.mjs):marker 升级 schema 2 带 baseline,老用户升级后能拿到 2.1.4 才进基线的 Meoo;状态文件的 registered/added 改为按实际清单计算。该修复要等插件仓发新版才到用户手里,本版宿主不包含它。

FISH Dock 2.0.4

Choose a tag to compare

@Fish-under-sea Fish-under-sea released this 03 Nov 14:02

FISH Dock 2.0.4

修订版。宿主为 2.0.4,兼容 插件 2.1.x(Clash 与 Magpie 2.1.3、飞书 2.1.0);不是「宿主与插件同版本」。

这一版收 #63、#65、#66、#67 四条 issue,并回归 #64,主线是插件的两条链路——自启与取资源——
外加插件页交互与外链、Agent 会话 API、OFM 业务解耦与发布工程收口。

一、插件自启:一次尝试一份预算

旧实现的问题不是「慢」,而是账算不清:每个阶段各自计时、失败各自重试,重试预算还会层层叠加,
于是一台冷启动的机器上可能出现「clash / magpie 双超时、内置服务根本没被拉起」。

  • 自启收进单一调度器 plugin_start.rs:一次尝试共用一份预算(冷启动 60 秒、热启动 10 秒),
    整轮生命周期 180 秒封顶,首次尝试加最多 3 次重试(0.5 / 2 / 5 秒),最多两插件并发。
  • 每个插件同时只有一次在途:在途 token 由 id、代次、版本、摘要组成;迟到的结果按代次丢弃,
    不会回填到新一轮卡片上。
  • 到期仍未就绪的尝试记为失败并标 late,不再冒充成功;到期后不再开启新一轮。
  • 备份暂停不再二次拉起:暂停时把在途的插件一并 retire 并把代次推走,恢复后旧 worker 回来只能被丢弃,
    不再出现「后到的实例覆盖并杀掉前一个健康实例」。
  • 失败分类拆成永久(清单/摘要/缺失、身份不符)与瞬时(读文件、元数据失败):前者不重试,
    后者可重试,不再靠文案猜。
  • worker panic 会被兜底回收槽位;手动启动成功后清掉本轮调度记录,卡片立刻跟实时状态。

二、资源获取:一层择源,一条总预算

  • 新增 sources.rs 作为唯一取资源层:原站地址永远第一优先,镜像按配置作为候选,每条候选再按可用
    代理路重试;候选 × 代理路共用一条总预算,换路不重新放满时间。
  • 落地只经唯一临时文件 + 原子改名;失败清掉半截文件,错误按「已试过哪些源、为什么失败」聚合上报。
  • 更新包下载同样走这一层:镜像失败回原站,进度与「只保留当前版本」的语义不变。
  • pet 的列表、详情、立绘与预览改由宿主取回并缓存,页面只拿本地 asset URL,不再自己外联。
  • PawnIO 仍保留固定 SHA-256 与允许主机约束;LHM、Windhawk 沿用各自的校验与安装流程。
  • Gitee 通道配了才启用;加速前缀默认为空。

说明:本轮没有为 pet、Release、Windhawk、LHM 找到可确认的免费国内镜像,因此「双源」目前是
「原站 + 可选镜像候选」的能力,不是「已配好两套源」。这一点在下面的「已知限制」里再列一次。

三、pet 的更新提醒

检查到宿主有新版本时,pet 会主动弹提醒,而不是只在设置页里等用户自己翻。

四、插件页:#63 交互与检索

  • 「更多」从弹出菜单改成整行可点的动作列表:纵向两行、每行 32 px、行间发丝分隔,
    日志在上、卸载在下;卸载常态用危险色、悬停只加 8% 底色。
  • 搜索框占位改为短句示例,命名规则移到说明行,长内容省略显示。
  • npm 发现式检索补齐分页契约:后端返回 total / filtered / hasMore / nextFrom,
    前端按 nextFrom 推进;短页(如 20 条里只给 5 条)不再出现 0 → 20 → 40 这种跳号或死循环,
    被过滤的整页也会正常前进。

五、#67 插件页外链

插件页原来是 sandbox="allow-scripts",没有 allow-popups 也没有 allow-top-navigation,
页面里任何 <a href>、target=_blank、window.open 都点不动。

  • 采用宿主受控通道,不放开 iframe 沙箱:桥拦截 a[href] 点击(要求真实用户手势 isTrusted),
    把 http/https 链接交给宿主 dock.open_external;宿主先过页面票据,再复用既有的 URL 判定
    (只认 http/https、必须有主机、禁 userinfo、禁 NUL)后才调系统浏览器。
  • file:、javascript:、data:、vbscript: 一律不拦也不打开;程序化调用需在用户激活窗口内;
    同一链接短时内重复点击会被节流。
  • 通用 FishDock.call('dock.open_external', …) 被列入保留名单直接拒绝——否则脚本可以绕过手势与节流。
  • 沙箱属性保持 allow-scripts 不变;服务页 iframe 的 allow-popups 是既有业务窗口,未改动。

六、Agent 会话 API 与 agent-core

  • 新增本地 crate agent-core(只依赖 serde_json),承载中性事件与「消息 → 事件」投影;
    各 agent 的磁盘 I/O 仍在宿主。
  • 新增 /v1 接口:/v1/info(免鉴权,可带 proof 防重放)、/v1/sessions(Bearer)、
    /v1/events(SSE,带 epoch:seq 游标与环形缓冲)。
  • 隐私边界:/v1 用独立投影,恒不外露 detail/step/reply/error/cwd;关闭详情时清空历史环、
    队列与 replay,旧订阅收到 reset 后结束;所有响应带 Cache-Control: no-store。
  • 资源上限:SSE 活跃写线程单独计数(≤8),慢客户端按真实 socket 写超时收回;退出时唤醒阻塞写。
  • token 落在私有目录(Windows 按当前用户 SID 收紧并回读校验,Unix 0600/0700),轮换走临时文件 + 原子替换,
    失败保留旧 token。

七、OFM 解耦、命名清理与发布工程

  • OFM 业务彻底解耦:判定集中到 legacy_ofm.rs,开发目录不再接受 ofm,旧网关插件走单一特判,
    安装器对退役载荷前缀硬拒;资源清单里 OFM 条目为 0。
  • 活动上游命名迁移到新名,集成脚本、邮件集成与示例统一为 FISH Dock 命名。
  • 配置收口到 config.rs 单一入口,新增键全部支持 FISH_DOCK_* 覆盖;旧前缀只读兼容。
  • 发布工程:路径扫描同时覆盖 UTF-8 与 UTF-16LE;release 二进制不再内嵌本机构建路径
    (env!("CARGO_MANIFEST_DIR") 在 release 下为空),构建脚本不再在未设 vendor 目录时传空参。
  • 对比度门禁回到 0 违规;check-hardcoded 与 check-contrast 均接入测试。

八、已知限制与未验项

以下项目没有在本次自动化里被证明,需要真机或后续确认,不应读作「已通过」:

  • 真机 WebView2:原生首帧时序、40 ms 决策窗口、审批气泡二次生长;插件页外链在真机上点开系统浏览器
    只验到「桥发出 → 宿主判定 → 调起接口」。
  • 登录与开机自启:真实登录流程与「开机后首次自启」需人工重启配合,本次未做。
  • 升级与回滚:2.0.3 的 Setup 包本体在本机缺失,只做了合成/隔离环境的升级链路验证。
  • 免费国内镜像缺口:pet、Release、Windhawk、LHM 均无已确认的免费国内镜像;Gitee 通道需自行配置。
  • /v1 的运行期细节:no-store 原始响应头断言、「阻塞写中退出」的组合用例仍属补充验证项。
  • token 私有 ACL 的现场核验:自定义数据根(FISH_DOCK_APP_HOME / --data-root)下的实际 ACL 回读
    需要在真机上确认一次。
  • Codex Desktop 内部请求现场:分类与过滤按真实 rollout 字段实现,但未在 Desktop 发起内部请求的那一刻
    同时抓取 hook 与 rollout。
  • 产品壳宽度:固定 520 px 的产品壳在 480 px 视口下会横向溢出,属既有产品决策,本版未改。

九、安装与校验

安装包 使用场景
FISH-Dock-Setup-2.0.4.exe 首次安装、基础资源缺失或修复安装,包含完整离线运行时(约 312 MB)。
FISH-Dock-Update-2.0.4.exe 已有版本且基础资源完整时使用,只下载本次变更(约 30 MB);缺失或损坏时提示改用完整包。

下载后请用同版本的 SHA256SUMS.txt 或包旁的 <exe>.sha256 核对摘要。
同版本号修订不会自动提示更新,请重新下载安装并核对最新摘要。
安装包当前未做 Authenticode 签名。

FISH Dock 2.0.3

Choose a tag to compare

@Fish-under-sea Fish-under-sea released this 02 Nov 12:10
84db441

FISH Dock 2.0.3

补丁版。宿主为 2.0.3,兼容 插件 2.1.x;不是「宿主与插件同版本」,安装时请按各自的版本号对待。

这一版是上游同步 + 插件启动链路修复,两半:

  • 上游同步:把 Wakuwaku 从 27104eab(v0.3.8)推进到 ab0eb65a(v0.3.9),按「本仓是否已有实现」
    逐项判定后纳入四项修复,另修正两处与版本位有关的既有缺陷(来源清单里一条自 2.0.1 起就写错的选择项、
    plugin.json 漏掉的版本位)。上游该版共 11 个提交(8 个非合并 + 3 个合并)、16 个文件、+211/−67;
    其中两项本仓早在 2.0.1 之前就自己做过且更严格,不重复移植。
  • 插件启动链路(任务书 plan-dock-2.0.3.md,诊断
    plugin-startup-diagnosis-2026-11-02.md):任务书十项里落了九项
    ——T01、T03、T04、T05、T06、T07、T08、T09 与维护线程的互斥收口(t19);T02 留 2.1.0(见「已知限制」)。
    起点是两条用户可见的病症:「启用自启动后首次启动插件拉起异常」与「设置页连开十次起十个 node.exe」——
    第一条不是「慢」,是误杀(见下)。

探针超时不再杀进程(T01,诊断 A1)

  • plugin_process.rs 拆出 alive() 与 exited():running() 仍然表示「存活 + 握手可应答」,
    但不再被当成死亡证明。plugin_runtime.rs 的 view() 因此只在 exited() 真的给出退出码时
    才 remove 实例、记一条失败。
  • 修的是:以前 !running() 就直接 self.processes.remove(id),而 Process 的 Drop 会
    child.kill()——插件进程还活着、只是那 2 s 探针没应答,就被判死并强杀,连它拉起的
    magpie.exe / mihomo.exe 一起带走。plugin-failures.log 里那批 exitCode:null 的记录
    就是这么来的。冷启时 I/O 与杀软最重、探针最容易超时,所以它只在「开机自启的首次启动」出现,
    之后「恢复服务」又正常。
  • 这一轮没应答时新增 unresponsive 字段,插件卡显示「未响应,正在重试」——它不再冒充「启动失败」,
    也不覆盖运行态,下一轮重绘自己会好。

Node 探测放宽并缓存(T05,诊断 A5)

  • plugin_dependencies::probe 的死线 2 s → 8 s(冷启那次是被 Defender 与磁盘占走的,不是 Node 慢)。
  • 成功的探测按 (路径, 长度, 修改时刻) 缓存在进程生命周期里:设置页每次重绘都要走一遍
    discover(),以前每轮现起一次 node --version,连开十次就是十个进程;现在同一个二进制只探一次,
    换了版本或原地升级会因长度/时间变化自动重探。失败不缓存(用户装好 Node 后下一次重绘就该认出来)。

审批快捷键(上游 #97,3efcc47d)

  • ZCode、DeepSeek Harness 的审批与计划,Alt+Y / Alt+N 现在能直接回答,和按钮一样。
  • 之前 canAnswerWithHotkey 把这两个应用(IN_APP)排除在外——IN_APP 本来只管「去 XX 处理」
    那颗按钮的措辞;结果是按钮能答、快捷键只开岛,按钮上也没有组合键提示。
  • canAnswer === false(明确答不了)仍然只开岛;问题(question)仍然不替人答。
  • 回归:app/test/shortcuts.test.js 原来钉的是旧行为(zcode 只开岛),改为钉新行为,
    并补一条 canAnswer === false 的反例。

全屏判据(上游 #98,c019eb70 + 7e8af4a7)

  • fullscreen.rs 新增 fills(style, zoomed, rect, monitor):没有标题栏的,盖住整块显示器就算全屏;
    样式上有标题栏的,「盖住整块且没有最大化」或「窗口矩形恰好等于显示器」才算;最大化的永远不算。
  • 修的是 Kazumi 这类播放器:它全屏用的是 window_manager 0.5.1 的 setFullScreen,保留
    WS_CAPTION、靠 WM_NCCALCSIZE 不画边框、把窗口设成显示器的 rcMonitor。以前「带标题栏一律不算
    全屏」,她的任务栏就压在视频上,底部、顶部都是。
  • 新增 what_fills_a_display:七种真实窗口形态(Kazumi 全屏、全屏前已最大化、任务栏顶部时被挪到
    每边 +11 像素、差一像素、两种最大化、HELLDIVERS 2 的每边 +1 像素、副屏)各一条断言。
  • 本仓原有的截图工具豁免(SKIP_PROGRAMS 六项:SnippingTool、ScreenClippingHost、ScreenSketch 等)
    与工作区自愈 resolved() 一并保留,未被上游版本覆盖。

灵动岛跟随槽位(上游 #98,ba4c6cab)

  • 任务栏左、中、右任一段的宽度变了(托盘图标几秒后才陆续到、之后新增一个、监控的数字变宽),
    她的胶囊跟着重新对齐。
  • 之前胶囊按「画任务栏那一刻」的位置算 --isl-x,之后槽位被挤走它却留在原处,压在监控上、
    严重时压住托盘的箭头,要展开一次胶囊才归位。
  • 做法:对三段 zones 挂 ResizeObserver(island.js 的 slotWatch),只在
    「任务栏已建 + 在岛里 + 家在任务栏」时重放 placeSlot();横向与侧边两条路都走这一个入口。

退出时崩溃(上游 #100,9ca1c269)

  • jump.rs 的 console_window:找命令行会话的窗口时要临时 AttachConsole 到那个会话的控制台,
    断开(FreeConsole)会把她自己的标准句柄一起关掉。这些句柄号之后被她打开的文件重用,
    任务栏模式下退出时 tray-icon 往「错误输出」写一句,写不进去(os error 5),eprintln! 当它是
    panic,进程就这么结束了(上游 v0.3.8 的 0xCFFFFFFF 一类退出)。
  • 现在连之前先 GetStdHandle 记下三个(STD_HANDLES:-10 / -11 / -12),断开之后
    SetStdHandle 原样放回去。发行版本来没有标准句柄,放回的就是空,行为与从前一致。

宠物图库走代理的说明(上游 #99,513f9979)

  • 代码不移植:本仓 fetch.rs 自 2.0.1 前(6376a35)就自己实现了这条路,而且更严格——
    走 proxy::web_ways、按 pet_timeout 的预算、http_status_as_error(false)、站点地址走
    config::pet_site()、错误分 network / http / notFound 三档、另有离线读缓存兜底。
  • 补的是说明:设置页「网络代理」的中英文案(s.net.systemNow / s.net.manualNote)原来列了
    软件更新、崩溃报告、插件联网、LibreHardwareMonitor,唯独漏了宠物图库;proxy.rs 与
    docs/DEVELOPMENT.md 同样补上。
  • 补的是测试:上游那条「图库真的照网络代理走」的断言本仓没有,补为
    fetch.rs 的 the_gallery_goes_the_way_the_network_proxy_says(起一个本地代理,钉住它收到的是
    CONNECT codex-pets.net:443 HTTP/1.1)。属特性回归(characterization),不是本轮修复的证明。

不移植的两项(本仓已有)

  • 任务栏「抢任务栏」去抖(上游 #98,efbcfb52):本仓 taskbar.rs 的 Tug 早就有同一个守卫
    (self.seen > 0 && self.at[(self.seen + TUG_TIMES - 1) % TUG_TIMES] == tick → 同一个 tick 只算一次),
    自 6376a35 起如此。上游的写法与它等价,改了只是换个写法。
  • 图库走代理的实现:见上一节。

资源校验改文件指纹(T06,诊断 B1)

  • state/verified 从「两行:清单摘要 + 资源目录」改成一份 JSON:{schema, resources, files: {path: {bytes, mtime}}}。
  • 启动判据 pending() 逐 manifest 文件比 size+mtime,只对指纹变了的文件重算 SHA-256(verify_pending_at);
    read_manifest 不再对 manifest.json 算摘要,因此换一份清单不再等价于全量重校。
  • 旧两行格式解析失败即按「没有记录」处理(等价于升一次级自愈),不写迁移脚本;
    用例 the_old_two_line_marker_counts_as_missing。
  • 标记写入改成原子替换:同目录临时文件 .verified-<pid>-<n> → write_all + sync_all → storage::replace,
    任何一步失败只删临时文件、旧记录原封不动,并印一行「旧记录保留(下次启动再校验)」。
  • 用例:the_start_checks_the_required_files_and_remembers_a_fingerprint_per_file、
    one_changed_file_is_verified_again_and_the_unchanged_ones_are_not、
    a_new_manifest_alone_does_not_ask_for_the_tree_again、
    the_marker_records_the_stamps_the_judgement_saw_not_a_later_stat、
    a_failed_write_of_the_marker_keeps_the_old_record。

全量校验移出首帧(T07)

  • 同步半 prepare_at 只做清单校验与必需文件 symlink_metadata;逐文件哈希交给后台线程。
    后台校验失败与从前一样 show_failure + exit(1),只是晚几秒而不是挡住首帧。
  • 用例 the_start_up_path_hashes_nothing_and_a_damaged_file_is_named_in_the_background、
    the_background_half_is_told_where_and_says_so_when_it_is_not。

WebView2 授权按需(T08)

  • 只在校验通过、且判据认为缺权限时做一次,不再每次启动递归授权:判据是目录与 msedgewebview2.exe 两处 DACL 的启发式
    (含 S-1-15-2-1 在本机被写作别名 AC 的情况),不逐文件试读。
  • 用例 the_webview_permission_is_written_only_where_the_sandbox_cannot_read、
    a_refused_grant_for_a_new_directory_is_not_remembered。

插件校验缓存(T03)

  • 新增 host/plugin-verified/<id>-<version>-<digest>.json,逐文件记 size+mtime。verify() 改成快照优先:
    plugin.json 照读 → 逐文件 stat → 全等即通过(一个字节都不进 SHA-256)→ 只有变了的文件才重算并回写快照。
  • 清理面:cleanup() 增 prune_snapshots()(只留当前版本与当前摘要,别的插件不碰),uninstall() 收掉该 id 的整族快照。
  • resume() 不再同步跑 cleanup() / cleanup_staging(),改由启动 30 s 后的维护线程 maintain() 做。
  • 用例 verification_is_cached_by_size_and_mtime_and_a_missing_snapshot_hashes_again、
    snapshots_follow_the_active_version_and_vanish_on_uninstall、upgrade_leaves_only_the_current_version_and_current_digest。

插件页面入口放锁(T09)

  • open() 去掉同步 verify;present() 只查实例表,把目录、清单与指纹表记进 Grant。
    call() 与 gateway.open 走新的 call_outside():借 Arc<Mutex<Process>> 句柄、放锁等回答、回来按代次结账;
    等待期间被停用或卸载的实例,其回答被丢弃,不写进新代次的失败记录。
  • resource() 不再读注册表(无 EDIT 锁)、不再每资源哈希:指纹命中直接发,变了才重算那一个文件。
  • 实例表改成 Arc<Mutex<Process>> + generations,view() 用 try_lock(在途 RPC ⇒ 活着),event() 跳过,
    stop_all() 立 stopping 闸;T01 的 Liveness 判据与 liveness() 原样保留。
  • 用例 resources_are_served_from_the_grant_fingerprint_without_rehashing、
    page_grants_bind_identity_version_enabled_state_and_lifetime、
    an_in_flight_page_call_is_discarded_once_its_instance_is_retired。

冷/热分档超时(T04)

  • 冷态判据 config::plugin_startup_cold:登录后 0–300 s(含端点)算冷;取不到登录时刻(非 Windows / WTS 查询失败)
    或时钟回拨算热。
  • 调用超时热态 5000 ms、冷态 20000 ms,plugin_host_call_timeout_ms_of 夹在 1–30000 ms;同一组数经 host.hello
    的 hostCallTimeoutMs 下发,插件侧缺字段回落 5000(SDK 注释同口径)。
  • 冷态 Process::start 失败重试一次(500 ms 退避),热态不重试;错误文案带「(冷启已重试一次,首次:…)」。
  • 用例 cold_window_is_five_minutes_with_none_and_rollback_as_warm、
    a_cold_start_retries_once_and_a_warm_start_does_not、
    a_cold_failure_retries_exactly_once_and_a_warm_one_does_not、a_failed_start_removes_the_new_version_directory(gated)。

维护线程与安装的互斥收口(t19)

  • 模块级上层串行锁 MAINTENANCE:install() / uninstall() / maintain() / set_autostart() /
    set_enabled_starting() 五个入口先过它,锁序 CURRENT → MAINTENANCE → EDIT_REGISTRY → EDIT。
  • 修的是「维护线程把正在写入的新版本目录当过期版本删掉」:改动前 cleanup 与 install 同在 CURRENT 下天然互斥,
    挪到线程后这条互斥丢了。
  • 为不造自死锁(std::Mutex 不可重入),两个开关入口各拆出 set_autostart_locked / set_enabled_starting_locked
    无锁体,命名即契约:锁内体绝不回头调用会再次取锁的公开入口。
  • 结算段(spawn_starts)锁内只剩 install_instance(&id, generation, process)——签名里没有 Manager,
    想验哈希或写注册表在类型上写不出来。
  • 用例 maintenance_waits_for_the_install_lock_before_it_prunes_anything、
    the_toggle_entries_wait_for_the_maintenance_lock、
    a_pending_journal_is_recovered_under_the_lock_and_the_new_version_survives_maintenance。

插件页卡面重做(t11)

每张卡按「一台在跑的服务」重排:左身份(名字)→ 中信号(状态灯 + 运行态 + 版本迁移)→ 右操作(一颗实心)。

位置 改前 改后
状态 3 个并列标签(版本 / 运行中 / 有更新) 1 条 ● 运行中 · v2.1.2 → v2.1.3;同号重发写「v2.0.0 → 同号新版」,不能更新时不画箭头
卡内按钮 5 颗同高(更新 / 关闭自启动 / 停用 / 卸载 / 打开插件日志) 4 颗(更新实心 + 关闭自启动描边 + 停用描边 + 更多),卸载与日志收进「更多」
实心按钮 红色的「关闭自启动」(另有绿色「去 Star」) 卡内唯一实心是「更新」;红色只留卸载的 hover/focus
元数据 2 行正文级(路径、安装来源各一行) 1 行 12px 次级(路径与来源同排)
内容行 带功能页的卡 4 行 ~145px、纯服务卡 3 行 ~92px 3 行 ~117px(−19%)、2 行 ~77px(−16%)
功能页入口 与按钮同款的小胶囊 32px 高、带 › 的整行入口
视图切换 3 颗按钮靠 aria-pressed 假装切换 真 tablist:aria-selected + roving tabindex + ←/→/Home/End,与 tabpanel 关联
检索 与八个按钮同权重的「检索」 输入框内的字段按钮,回车即搜;输入框补 aria-label

高度与行数是按 CSS 盒模型与渲染出的 HTML 结构算的,没有截图核对,见「已知限制」第 10 条。

来源清单与版本位修正

  • UPSTREAM.json 的 version → 2.0.3,selectedUpstreamThrough → ab0eb65a,
    selectedChanges 补本轮四项。
  • 删掉一条一直写错的:"agy approvals read globalPermissionGrants from config.json (upstream #91, 5a0fee56)"
    自 2.0.1 起就挂在「已选」里,但本仓没有 needs_approval / classify_tool / globalPermissionGrants
    任何一处代码(events_agy.rs 317 行,上游同一文件 1115 行),它没有可附着的审批子系统。
    现移入 excludedChanges 并在 notes 里写明原因,对应 #51。
  • integrations/claude-code/.claude-plugin/plugin.json 的版本位停在 2.0.1:2.0.2 只改了
    Cargo.toml / Cargo.lock / tauri.conf.json 三处,漏了它,于是 updates.rs 的
    one_version_everywhere 在 2.0.2 上一直是红的(它断言四处说的是同一个版本)。本版补到 2.0.3。

已知限制

下列 12 条全部如实保留,其中第 1、9、10 条决定了这一版能承诺到什么程度。

  1. T02「插件 → 宿主」回调解耦仍留 2.1.0:它是 L 级结构改动,需要 t29 / t30 / G1 全组重跑,本版没动。
  2. plugin_api.rs 的 install 提交段仍持 CURRENT:T03 只把 Runner 侧的重活挪出锁,API 提交段这一小段仍在全局锁里(t13-F2、t17-F5)。
  3. 冷态重试在「用户点启用 / 装完即启」路径仍占 CURRENT:这两条走的是同步 Process::start,冷窗最坏约 40.5 s(20 s 首试 + 0.5 s 退避 + 20 s 重试),期间插件页的其他操作要等。
  4. is_running 对「已死但实例锁被占」的实例会短暂答 true:取的是 T01 同向取舍——try_lock 抢不到即视为在途 RPC,错在少杀而不误杀,下一轮重绘自愈。
  5. sandbox_may_read 只判 ACE 类型、不判权限位:只认 DACL 里有没有对应的 ACE 条目,不去核实它给的是 RX 还是别的位。
  6. 后台校验失败走 show_failure + exit(1) 硬退:T07 把全量校验挪出首帧,但失败仍是硬退(与 2.0.2「校验通过才启动」同语义),只是晚几秒。
  7. ...
Read more

FISH Dock 2.0.2

Choose a tag to compare

@Fish-under-sea Fish-under-sea released this 01 Nov 15:35

FISH Dock 2.0.2

补丁版。宿主为 2.0.2,兼容 插件 2.1.x(插件 2.1.2 稍后单独发布);不是「宿主与插件同版本」,
安装时请按各自的版本号对待。

这一版不引入新功能,只修插件的三件事:看不见(失败没有日志)、查不到(错误只剩一句兜底话术)、
停不掉(残留内核被当成自己的进程杀错)。逐条对应到来源 issue。

插件诊断(#53 / T01–T03)

  • stderr 不再被丢弃:插件进程的 stderr 从 Stdio::null() 改为管道接管,逐行写到
    host/plugin-logs/<插件 id>.log;单文件 1 MB、保留 2 份滚动,写入前过统一脱敏(令牌、路径、
    邮箱、IP、主机名),单行截断 300 字符。写入失败只丢这一行,不盖掉原始错误。
  • 失败可查:host/plugin-failures.log 每行一条 JSON,含插件 id、版本、失败阶段、退出码、
    时间、原始错误与对应日志路径;宿主重启后仍可查。
  • 错误带定位:插件卡的 error 文本改为「原始原因 + 退出码 + 日志路径」,
    并新增结构化字段 failure / exitCode / logPath;无退出码时说的是「无响应(握手超时)」,
    不冒充「已退出」。
  • 日志入口:插件卡新增「打开插件日志」,直接打开 host/plugin-logs/。
  • 日志目录不在整仓备份范围内(config_backup 不收),备份体积不因此增长。

运行态与标签页(#14 宿主侧 / T04–T05)

  • 运行态判据:running() 从「进程存活」改为「进程存活 + 握手可应答」——发一次 host.hello,
    任何编号匹配的应答都算活着(2 秒超时、正结果缓存 2 秒)。进程还在但事件循环卡死时不再判为运行中。
  • 残留内核回收:按 pid + 实际启动时刻(±5 秒)+ 控制器可达三重判定,只回收「连不上控制器」的
    僵尸内核,能连上的留给插件 attach;不根据持久化 pid 结束外部程序。
  • 标签页不再消失:插件页标签的显示条件从「插件正在运行」改为「已安装 + 已启用 + 已解锁」。
    插件进程退出后标签页仍在,点进去显示错误态(原因 + 「启动」+「打开插件日志」),启动后当场恢复。
    这修的是「退出再进 Dock,Clash 标签页没了」这类现象。

错误码透传(#14 / T12)

  • 插件侧抛出的业务错误不再被折成一句通用文案。三处折叠已打通:sdk/node/plugin.mjs 的命令包装、
    宿主 plugin_protocol::answer()、插件页 sdk/browser/bridge.js。
  • 页面按 code 查表显示专属文案(含「下一步做什么」),未知码才落到唯一的「未识别错误」。
  • 兼容旧行为:publicServiceError 仍返回字符串(gateway.mjs 依赖);业务错误不再被当成致命错误
    停掉插件进程(is_business_error(&error) || error == OPERATION_ERROR)。

版本位

  • Cargo.toml、tauri.conf.json、Cargo.lock 与安装器 --version 统一为 2.0.2。
    漏掉 Cargo.lock 时 cargo check --locked 会直接报 cannot update the lock file,本版一并修正。

已知限制

  • 真实 mihomo 内核的 attach / 停止、真实 Windows 注册表读写路径、3 秒 attach 冷却的真机手感,
    均未在本轮自动化里覆盖,属 G1(双插件自启高压测试)与 G2(成包校验)的范围。
  • Clash 侧部分启动失败的 operationError 仍是固定文案(例如端口冲突时页面显示「启动失败,请检查
    内核路径、端口和订阅缓存」而不是精确码)。可归因信息在同一次启动的插件日志里;精确码化列入
    下一版候选,不算本版缺陷。

安装

  • FISH-Dock-Setup-2.0.2.exe(约 312 MB):首次安装、资源缺失或修复安装。
  • FISH-Dock-Update-2.0.2.exe(约 30 MB):已有版本且基础资源完整时使用,只含本次变更。
  • 两个包都附同名 .sha256,另有 SHA256SUMS.txt 汇总;安装包当前未做 Authenticode 签名。

FISH Dock 2.0.1

Choose a tag to compare

@Fish-under-sea Fish-under-sea released this 31 Oct 18:45

FISH Dock 2.0.1

维护版。宿主为 2.0.1,兼容 插件 2.1.x(插件仓已到 2.1.0);不是「宿主与插件同版本」,
安装时请按各自的版本号对待。本版按用户能感知的变化整理,逐条对应到来源 issue。

设置页与公告(#39)

  • 公告有内容时也提供「刷新」入口,不再只在空状态才给。
  • 打开「关于」页超过 5 分钟后自动重新获取一次公告(原来只有一小时门)。
  • 公告标题不再重复版本号(Release 名自带版本号时去重显示)。

全局快捷键(新能力,#45)

  • 设置页新增「全局快捷键」录制节:为动作录入组合键。
  • 组合键被其他程序占用时给出提示;30 秒内未录到可用组合就放弃,不静默挂起。

插件平台(#37 / #38)

  • 更新 / 卸载改为事务:中断可续做,校验不过回滚,失败不阻断其它操作(#37)。
  • 回滚点可选择性导出;更新成功后旧版本目录清零,只留当前激活版本(#37)。
  • 卸载彻底:清掉安装载荷与注册,但保留用户配置,不误删数据(#37)。
  • 插件自启动在 Dock 运行中拨开关即时生效;resume() 失败不再整批静默跳过(#38)。

窗口与任务栏、显示(#48 P2 / #43 / #44 / #47)

  • 无标题窗口也会出现在任务栏(不再因没有标题被漏掉)(#48 P2)。
  • 托盘图标点击可到达自身(左键/右键都有响应)(#48 P2)。
  • 右键菜单精简:她的菜单只说关于她的事,任务栏自己的项(任务管理器、Windows 设置等)
    留在条带自身的右键菜单,不重复(#48 P2)。
  • 工作区自愈:换显示器或改缩放后,最大化窗口不再停在条带上方;副屏条带移动不误改主屏(#43)。
  • 文字 110% 叠加显示缩放时,任务栏部件不再上浮、卡片画面不再偏小错位;任务栏按 Windows
    同厚度呈现(#44)。
  • Codex:失败命令计为失败;「Approve for me」交还 Codex 处理(#47)。

崩溃(#40 / #41)

  • 卡死抓栈:界面卡死时抓取调用栈写到 crashes/hang-<pid>.txt(#40)。
  • WebView2 被外部结束不再被误报为崩溃;只有真正的崩溃才记(#41)。

网络韧性(#42)

  • Wi-Fi 没有出口时读取瞬时为空不再中断(同形加固,#42)。说明:本仓是同形加固,
    不是「修复了一个闪退」——避免把它读成上游那条独立故障。

商店与模型能力

  • OFM 本地模型正式下架:商店目录与宿主三处白名单同步移除;已装用户不受影响,
    仍可正常启用/卸载(商店目录调整,依用户要求,无单条 issue)。
  • 模型能力由 Magpie 插件提供:关于页与 README 的致谢同步更新,不再把模型能力记在
    OFM / Our Free Model 名下(文案调整,依用户要求,无单条 issue)。

明确不包含(转 2.1.0)

  • agy 岛上审批:整链约 +848 行,未纳入 2.0.1,转 2.1.0(见 issue #51)。
  • 内存账 #29 / #30:内存长跑与 GPU 归因转 2.1.0。

兼容性

  • 宿主 2.0.1 与 插件 2.1.x 配套;插件版本号高于宿主,属正常。
  • 双包:FISH-Dock-Setup-2.0.1.exe(完整安装/修复,190 MB)与
    FISH-Dock-Update-2.0.1.exe(已有安装且基础资源完整时只下载变更,31 MB)。
  • 数据目录、注册表身份等沿用 2.0.0 的约定,本版不再迁移。

FISH Dock 2.0.0

Choose a tag to compare

@Fish-under-sea Fish-under-sea released this 30 Oct 19:48

FISH Dock 2.0.0

宿主升到 2.0.0:所有版本位对齐,旧发行名按「沿用 / 迁移 / 清理」三档收尾,用户
数据目录迁到 FISH Dock 之后旧目录直接删除。宿主与插件同批发布,四个官方插件
也全部 2.0.0。

版本锚点

一处改了别处没跟上,是这次最容易被测试抓住的地方。2.0.0 起下面这些位置写的是
同一个版本号,updates::tests::one_version_everywhere 盯着前两项:

位置 作用
src-tauri/tauri.conf.json → version Tauri 元数据,安装器 ProductVersion 取自这里
src-tauri/Cargo.toml → package.version 宿主二进制版本(CARGO_PKG_VERSION)
src-tauri/Cargo.lock → name = "wakuwaku" 锁文件里的自身条目,随 Cargo.toml 同步
integrations/claude-code/.claude-plugin/plugin.json → version Claude Code 集成清单,同一条测试比对
README.md / README.en.md / Wakuwaku/README.md / Wakuwaku/README.en.md 版本徽标、下载与更新记录链接
tools/check-readme.mjs 不再自带版本字面量,改读 tauri.conf.json 再校验四份文档

双包

v2.0.0 照旧发布两个包,校验值在同一条 Release 里:

安装包 使用场景
FISH-Dock-Setup-2.0.0.exe 首次安装、基础资源缺失或修复安装,含完整离线运行时
FISH-Dock-Update-2.0.0.exe 已有安装且基础资源完整时只下载变更文件;缺资源会提示改用完整包

旧名三档

改名的风险不在「改」,在「改错档」:动到用来匹配的名字,升级后的程序就认不出
自己写过的东西,安装器也清不掉旧值。所以按「动了会不会丢用户数据、会不会让升级
找不到已登记的安装位置」分成三档。#t16 + #t18 之后,原来的「沿用不动」一档已经
不存在:用户看得见的名字全部换成 FISH Dock,旧名只留在「读取/识别」这一侧。

已改名(写出的一律是新名)

  • 主程序文件名 FISH-Dock.exe(Cargo 包名 fish-dock,[[bin]] name = "FISH-Dock");
  • 应用标识 com.fish.dock,通知 AUMID 同步(notify.rs 的 APP_ID);
  • 任务名 FISH Dock at logon-<SID>、传感器任务 FISH Dock sensors-<SID>;
  • 命令行开关一律写 --fish-dock-*(12 个:ensure-running / codex-hook / agy-hook /
    zcode-hook / dsh-hook / reasonix-hook / out-of-job / hw-dump / lhm / sensors /
    sensors-setup / sensors-remove,另加 at-login 与 uninstall-cleanup);
  • 传感器助手 FISH-Dock-sensors.exe,助手目录 %ProgramFiles%\FISH Dock\sensors;
  • 用户数据目录 %LOCALAPPDATA%\FISH Dock、卸载表登记名 FISH Dock、Run 值名 FISH Dock。

仍兼容读取(旧名只读不写,至少留一个版本)

  • 旧 exe 名 DSH-FISH.exe、wakuwaku.exe、wakuwaku-sensors.exe:安装器与更新器按旧名
    识别并删除(LEGACY_EXE_NAMES / EXE_NAMES),有清单的安装也会补扫;
  • 旧开关 --wakuwaku-* 与更早的 --claude-pets-ensure-running:src/flags.rs 的别名表
    在进程入口归一成新名;旧 hooks/旧任务里的写法仍能跑,connection.rs 在修复/连接时把
    它们改写成新名(is_ours 认新旧两套);
  • 旧任务名 Wakuwaku at logon-<SID> / Wakuwaku sensors-<SID>、旧 Run 值 Wakuwaku、
    旧卸载表登记 DSH-FISH / Wakuwaku:只认、修正与删除,不再写;
  • 旧占位符 __WAKUWAKU_PORT/EXE/DATA__:omp 扩展模板与新名并存、两套都填。

明确不改(外部身份,不是本产品的名字)

  • Claude Code 插件 id wakuwaku@wakuwaku 与其 marketplace 命令:已发布到插件市场,
    改了等于让所有人的已装插件失效;
  • integrations/ 下的文件名(wakuwaku.js / wakuwaku.ts)与 omp 的 extensions/wakuwaku.ts:
    其他 agent 按文件名加载,改名会让它们找不到;
  • 事件上报里的 from=wakuwaku 埋点:远端统计与崩溃去重按它归属,属于对外协议;
  • 上游许可文件名 licenses/Wakuwaku.txt:这是许可方的名字,不是我们的。

迁移:用户看得见的旧名 → FISH Dock

  • 任务计划里给人看的作者与说明(logon.rs 的 task_xml / DESCRIPTION);
  • 用户数据目录 %LOCALAPPDATA%\DSH-FISH → %LOCALAPPDATA%\FISH Dock(见下一节)。

任务名本身(Wakuwaku at logon-<SID>)不改:它是用来匹配的,不是给人看的。

清理:旧发行名留下的登记与文件,安装时按归属删掉

  • 旧主程序 $INSTDIR\wakuwaku.exe:安装段就删,不等卸载(删旧建新,不留旧 exe);
  • 指向旧 exe 的 Run 值,与它在 StartupApproved\Run 里的同名标记:只按
    「本安装目录 + 我们自己的旧文件名」精确匹配整条值文本,别的程序写的同名值不动;
    指向当前主程序的那条是用户的选择,保留;
  • 旧卸载表登记项(Wakuwaku、FISH Dock):登记位置就是本目录、或显示名是我们
    自己用过的旧名,才整键删除;
  • 卸载表里那条带旧发行名的 ProductName:2.0.0 起只用 DisplayName +
    DisplayVersion。

卸载段再兜一遍底:Run 值、同名标记、旧 exe、旧登记项都按同一套归属判定清理,
不递归删目录、不碰用户数据。旧版回滚点不再保留 —— 只留当前版本。

用户数据目录迁完即删

安装器带上 --migrate-home 才会动用户数据目录;不带这个参数(包括开发版直接跑
install-update.cjs)一个目录都不碰。迁移顺序是「整树盘点 → 逐个复制 → 逐个核对
SHA-256 → 两份清单对齐 → 才删旧目录」,所以:

  • 迁完:旧目录直接删除,不留回滚副本;
  • 没迁完(新目录已存在、复制中断、摘要不符、旧目录不在):旧目录原样保留,程序
    照常可用,安装器只提示「完全退出后重跑一次安装程序」;
  • 旧目录被 WebView2 占用删不掉:数据已经在新目录里核对过,不算迁移失败,旧目录
    留下并记一行,交给用户手动清;
  • 树里有链接或非普通文件:拒绝迁移,不跟着链接把树外的东西算进来。

安装器读 home-migration-failed 标记必须排在删除暂存目录之前 —— 失败分支同样会
把暂存目录整份删掉,读晚了就什么也读不到。

兼容性

本版是 breaking:只支持 Dock ≥ 2.0.0 与同批发布的插件 2.0.0。插件目录条目结构
从 author + maintainer 改成 authors[] + note,旧 Dock 读不出新目录,插件页
会是空的;1.x 装 2.0.0 插件同理。升级时请整套一起换。

验证

  • node scripts/installer.test.mjs:15/15,含「离线 makensis 真编出安装包」(makensis
    在本仓的中文路径下写不出安装包,测试把 NSIS 工具树暂存到 ASCII 临时目录再编)。
  • node scripts/installer-upgrade.test.mjs:9/9,含「NSIS 通过真实 Node 和 PowerShell
    5.1 完成中文路径安装与覆盖」与 #27-D 的迁完删除 / 未迁完不删 / 重试 / 删旧失败 /
    拒绝跟随链接。
  • node scripts/prepare-installer.test.mjs:6/6。
  • node scripts/branding.test.mjs:6/6。
  • node tools/check-readme.mjs:四份文档的版本锚点、相对链接与锚点全部通过。

FISH Dock 1.2.3

Choose a tag to compare

@Fish-under-sea Fish-under-sea released this 28 Oct 13:50

FISH Dock 1.2.3

给插件更新收个尾:升完级不再留旧版本目录,正在看的那一页也会换成新票重建;顺手修了主仓两条 issue。

插件更新不再留旧版

以前每次装新版,旧的 packages/<id>/<version>/ 都留在磁盘上:一次升级多几十
MB,旧目录里还带着旧 manifest。现在升级提交成功之后才清理,只保留当前激活
版本和最近写入的那一个(回退点),更老的删掉。失败路径不受影响:装不上时旧版
仍然在,回滚照旧。

同时修了页面一侧的漏洞:插件升级后 digest 变了,旧 iframe 会被移除、旧票据
会被关闭,但页面宿主还记着"当前就在这一页",于是下一次进入直接返回、不重建,
用户盯着空白。现在退场的若是当前页,会清掉这一状态,下次进入用新票据、新
地址
重新建帧。

主仓 issue

  • #17 密码框加可视按钮:邮件表单与 LibreHardwareMonitor 表单的密码行右侧多
    一枚眼睛,点击只切换 type,值、光标、表单数据都不动;空值禁用,关闭后一律
    回到隐藏;aria-pressed 与中英文案齐备,Tab 顺序是输入框 → 按钮。
  • #16 安装日志乱码:安装器本身是 Unicode 版,界面文字不乱;乱的是子进程那
    一路——nsExec::ExecToLog 按 ANSI/OEM 解释 node.exe 的 UTF-8 输出。现在面
    向安装器的输出全部改英文,并统一过 ascii() 兜底,同时往安装目录写一份
    UTF-8 BOM 的 install.log 存中文细节,卸载时一并删除。

配套

插件页的写操作(签到页的「关 / 开」「立即签到」)需要 Magpie 插件 1.2.0 的页面
调用桥才真正生效;本版与此配套发布,安装后到插件页把 Magpie 更新到 1.2.0。

验证

  • cargo test --bin wakuwaku:572 通过 / 2 失败(lhm_setup 两项,沙箱内
    PermissionDenied 与计划任务根目录 0x80070003,环境性)/ 14 ignored。
  • node --test test/extension-page.test.js:4/4。
  • node --test scripts/installer.test.mjs scripts/install-update.test.mjs scripts/install-update-performance.test.mjs:23/23,含「安装日志只用 ASCII」
    的断言。

FISH Dock 1.2.2

Choose a tag to compare

@Fish-under-sea Fish-under-sea released this 28 Oct 09:02

FISH Dock 1.2.2

发行版本:v1.2.2。主程序、Tauri 元数据、Cargo 锁文件与安装器统一为 1.2.2;插件保持独立版本。

修复:插件页的「自启动」开关点不动

1.2.1 给插件页加了 host.autostart 接口,但页面桥按设计拒绝 host. 与 plugin. 开头的方法(extension-page.js 里那条校验把这两类名字留给插件后端)。于是插件页调用 host.autostart 时消息被直接丢弃:既不成功也不报错,前端 Promise 一直挂着,开关停在禁用状态——看起来就是「点不动」。

本版把宿主侧接口改名为 dock.autostart,走同一套逻辑(读 Installed.autostart、写回注册表),并保留 host.autostart 作为别名兼容。插件页面同步改用 dock.autostart 后:

  • 打开网关页即可读到当前状态,点击立即生效,失败会回滚勾选并在页面上报错;
  • 宿主早于 1.2.2 时开关自动隐藏(旧桥会丢弃 dock. 以外的命名空间,用它只会静默失败)。

同一批发布里 Magpie 1.1.1 与 Clash 1.0.4 都已改用新接口。

其余

本版只动这一处宿主代码与版本号,认证、备份、插件安装、更新检查的行为与 1.2.1 相同。1.2.1 的完整说明见 release-1.2.1.md。

安装

  • FISH-Dock-Setup-1.2.2.exe:首装或修复。
  • FISH-Dock-Update-1.2.2.exe:精简更新包,适用于基础资源完整的 1.2.1 安装。
  • 同版本 SHA256SUMS.txt 一并发布。

安装包不改动 %USERPROFILE%\.fish-dock 里的凭据与配置,升级后登录与已装插件都保持原样。

FISH Dock 1.2.1

Choose a tag to compare

@Fish-under-sea Fish-under-sea released this 28 Oct 06:28

FISH Dock 1.2.1

发行版本:v1.2.1。主程序、Tauri 元数据、Cargo 锁文件与安装器统一为 1.2.1;插件保持独立版本,不随宿主升级。

本次是修复版,重点在「掉登录」与「备份不完整」两件事,另外合入两个社区 PR。

GitHub 登录:认证一次,之后长期有效

  • 凭据不再因版本变化读不出来:凭据记录加 schema_version 并去掉字段严格校验,旧记录照旧能读;读不懂时另存 github-access.dpapi.bak,不再直接丢弃。
  • 失效分级:只有 GitHub 明确回 bad_refresh_token / expired_token / invalid_grant 才清凭据;网络失败、限流、边缘节点 401 一律保留会话。
  • 启动复核:每次启动(含更新到新版本之后)后台自动续期并复核身份与主仓 Star,不必等用户点插件;失败只停在「待复核」。
  • Star 回执 7 天:一次联网核验管一周,离线不把官方插件锁死。
  • 接受长期令牌:应用关掉「令牌过期」后签发的是不带 expires_in、不带 refresh_token 的令牌,现在照收(expires_at 记 0,视为永不过期)并清掉不再需要的刷新凭据;仍带 8 小时有效期却缺刷新令牌才算异常。

配置备份:只带凭据与名册,导入不再被版本卡住

  • 运行态不进包:trash、routing、用量日志、模型与市场缓存、*.bak、*.tgz、dist、node_modules 统一排除。
  • 邮件正文不入包(host/mail-store);邮件账号名册与登录凭据照常带走。
  • 解不开的凭据原样带走:别的用户或别的机器留下的 DPAPI 不再让整包导出失败,按 opaque 编码带走并计入「需重新登录」数。
  • 导入放宽:只要求 data_schema 相等,插件版本升级过也能恢复,目标机器已装的版本号保持不变;不再用备份里的旧 host/storage.json 覆盖宿主版本。
  • missingCredentials 回真实值,不再固定为 0。

插件

  • 自启动开关(宿主侧):Installed.autostart 持久化,启动恢复只看 enabled && autostart;插件页可通过 host.autostart 读写。插件页面上的那个开关要等插件重新打包发布后才出现,安排见仓库外待办。
  • 认证只约束官方插件:安装、启用、页面与扩展调用改用 require_plugin,GitHub 与主仓 Star 只对 fish.feishu / fish.clash / fish.magpie / fish.ofm 生效;第三方插件不受这条约束。
  • 第三方插件(新):插件中心新增「第三方插件」页,「官方商店」改名「官方插件」。按 npm 包名检索公开 registry 并安装;包需含 plugin.json 与 README.md,声明摘要逐一比对,按第三方命名空间登记,与官方插件共用安装事务、备份与回滚。

社区 PR

  • #14 灵动岛窗口铺满工作区:窗口宽度恒为工作区宽、左上角固定,中/右锚点切换宽窄页不再横移重绘。
  • #15 连接判断与账单分账:设置快照改用完整渠道列表(ZCode、DSH、Reasonix 不再被误判未连接);token 组件新增 ZCode / OpenCode / DSH 三份账,与「今天」卡同步翻页。合入后修掉 selAgent 在两块之间互相重置的缺陷,并把 DSH 解析抽成生产与单测共用的一份。

验证

  • Rust:565 项通过 / 2 项环境相关失败(lhm_setup 的目录 ACL 与 Task Scheduler 根目录)/ 14 项忽略。
  • 前端:35 个测试文件全部通过。
  • 新增用例:认证 6 项(多字段记录可读、无过期令牌永不重登、启动复核失败不清凭据、恢复后补齐 Star、长期令牌照收、短期响应缺刷新令牌仍报错)、备份 4 项、第三方插件 3 项、账单 1 项。

安装

  • FISH-Dock-Setup-1.2.1.exe:首装或修复,覆盖安装会保留数据根与已装插件。
  • FISH-Dock-Update-1.2.1.exe:精简更新包,适用于基础资源完整的已有安装。
  • 同版本 SHA256SUMS.txt 一并发布,下载后请核对。

两份安装包都不改动 %USERPROFILE%\.fish-dock 里的凭据与配置;升级后原有 GitHub 登录继续有效。