The project's README from github.com/MerZlin/dsh-pet-indesktop. As one markdown file: README.md.
一个基于 Python + PySide6 的独立桌面宠物。项目脱离 DSH 运行时,提供透明无边框、置顶、可拖动、角色切换、动画播放、系统托盘和可选 AI 对话能力。
当前版本 v4.2.0(2026-09-10 发布):自 v4.1.0 以来的功能与修复汇总(PR #57/#64/#65/#68/#70/#71/#72/#73/#76/#79/#80/#82/#85/#86/#87/#90/#91/#93/#94/#96)。 亮点:桌宠内存专项治理(长时运行不再单调上涨)、单进程多窗与同角色共享解码链(3 窗 1 进程 1 解码器)、设置与菜单重构(右键菜单可编排、快捷启动、主题即时生效)、边缘探头 / 黄金回旋 / 撞飞彩蛋 / 省电模式、待办提醒、DSH 事件层与探索 Watchdog 控制链、子肥鱼「只退出不删数据」、台词模板 v2 与表达风格、高刷屏流畅度、气泡行尾字裁切修复、DLC 换角色加固。
v4.2.1(2026-09-23 发布):v4.2.0 之后又并入 57 个已合并 PR、192 个提交,逐项清单见 v4.2.0 以来的变更(v4.2.1)、v4.2.1 发布草稿(含发布前测试清单与视频预演脚本)与下方 最近修复与变更记录。新功能:灵动岛重做(#113)、语音报时
voice_chime(#118 / #125)、节日提醒festival_reminder(#127)、事件汇报概率门(#97)、气泡文字大小可调(#153)、Harness 服务重启/停止(#153)、聊天背景裁切取景(#163 / #167 / #169 / #170)、音乐歌词显示与「上一首」(#128 / #129 / #135)、歌词对齐(#175)、点击台词朗读与本机语音预缓存(#176)、音乐播放器路径进设置页(#177)、「互动」域设置页分页(#179)。关键修复:Windows 关机/注销弹0xc0000142(#112,issue #111)、桥接插件归零外部依赖(#104)、DSH 事件层系统性收尾(#105)、启动即按配置装配可选服务(#100 / #102)、单进程多开下主动识屏永不触发(#145)、Linux 屏幕边缘无法贴边(#137)、岛碰撞改同步硬墙(#173)、AI 会话窗不再「贴不到底」、edge 音色被下架后「配了却不出声」自动兜底(#178)、构建不再被 PyInstaller 的 Qt 绑定互斥中止 + 产物 TTS 自检(#180)。回退:#136/#131(歌词显示审计 + 节日动画/生日)已由#143撤出。发布形态为 onedir 目录打包 + Inno Setup 安装包(
.exe)+ 便携 zip 绿色版:安装版与绿色版运行期都不解压、不产生临时缓存,启动快、卸载干净。v4.2.0 完整发布清单见docs/RELEASE-v4.2.0.md,v4.2.1 发布草稿(含测试清单与视频预演脚本)见docs/RELEASE-v4.2.1.md,产物见 GitHub Releases。
本节的约束来自 PR #76 那轮“实测数据驱动的性能 + 结构治理”,此后每个版本都在沿用并加固;给本项目提交代码前请先读本节以及
AGENTS.md、docs/WINDOW_PY_SPLIT_GUIDE.md。
tests/test_architecture.py,红了即 CI 失败)collision.py / physics.py / collision_codec.py 禁止 import PySide6。decode_fanout.py 不得反向依赖 window.py / webm_clip.py,窗口钩子只能通过注入接入。PetWindow 的 win._xxx 只允许 window.py 自身与 collision_client.py 访问;app.py / agent_link.py / context_menus/ 出现即为违规。window.py 行数预算:当前预算 4648 行(常量 WINDOW_PY_LINE_BUDGET 见 tests/test_architecture.py,实测 4648 行,2026-09-23 校准)。历次校准理由逐行记在该文件注释里(含 #101/#102 启动装配与探头闸门、#108 弹射飞行守卫、#109 探头旋转/软撞位移、#112 会话结束的 _closing 守卫、#182 的实时性改造与上游合入)。预算只随实测校准,不靠压缩行宽/合并语句硬塞;确需上调要在 PR 说明理由。modern_settings_dialog.py 行数预算:当前预算 2347 行(常量 MODERN_SETTINGS_DIALOG_PY_LINE_BUDGET,实测 2343 行,2026-09-22 随 #179 整域拆分首次下调;已拆出 pet/settings_interaction.py、settings_widgets / settings_menu_layout_editor / chat/ai_settings_page / settings_theme_qss),再往主对话框塞新页面属于红线。settings_widgets.py、settings_styles*.qss 等文件不允许“存在但零引用”——要么删除,要么真正接线;防再发由测试守护。window.py 处于 “只许瘦不许胖” 的增量拆分公约下:新功能预计超过约 100 行、需改 3 个以上同域方法、或行数预算告警时,先按 docs/WINDOW_PY_SPLIT_GUIDE.md 拆控制器。test_config_schema.py 快照;特例键(version / proactive_screen / agent_link / chat)走专门迁移路径,不塞普通白名单。config-slot-N.json;进程级共享项(托盘/共享解码/Agent 联动/待办提醒等)以主桌宠 config.json 为准,非主窗修改不生效。AGENTS.md“CI cost discipline”)每个 PR 必须随附三份证据,缺一即视为未完成,审查时按缺陷提出(出处:AGENTS.md 的 "Delivery evidence discipline",细则见 docs/DEV-HANDOVER.md §8.2/§8.3):
git diff --numstat),新增/删除的文件都要列出。只写「修了 bug」不算说明。落地形式:报告写入 docs/PR-REPORT-<主题>-<YYYY-MM-DD>.md(骨架照抄 docs/PR-REPORT-TEMPLATE.md),并在 docs/INDEX.md 的「PR 报告存档」登记一行;PR 描述放摘要 + 报告链接,不要把报告全文粘进去。豁免:纯文档/文案/依赖版本号这类不改变运行行为的改动,可只保留第 1 条,但必须在 PR 描述里写明豁免理由。机器化校验:tests/test_pr_report_discipline.py 对 2026-09-22 之后的报告强制校验三个必备章节(标题级)与索引登记。
v4.2.1 覆盖 v4.2.0 之后合并的 57 个 PR / 192 个提交,其中有 5 位外部贡献者各带了一整批(klxxya 26 个、Anguis-L 4 个、KagurazakaChizuru 3 个、Daliuq 2 个、lscatfish 2 个,逐人致谢见发布说明)。逐项清单见下方 v4.2.0 以来的变更(v4.2.1),完整发布说明见 docs/RELEASE-v4.2.1.md。
设置与菜单(本版最直观的变化)
音乐与歌词
music_player_paths 支持。docs/NETWORK-PROXY-AND-VPN-2026-09-22.md。台词朗读、语音与内容
self_talk_speak_enabled):点击桌宠弹出的自言自语会用语音念出来;可选本机语音预缓存(CosyVoice 127.0.0.1:9880,默认关)。self_talk_image_chance,默认 30),修掉「图片目录一多就几乎轮不到文本」(旧行为实测出图 82.8%)。voice_chime(六种调度模式)与节日提醒 festival_reminder(46 个日子 / 314 条文案);edge 音色被微软下架时自动兜底 + 重试,0 字节缓存不再算命中。桌面玩法、碰撞与灵动岛
流畅度、内存与包体
工程与质量
tests/test_pr_report_discipline.py 机器化校验(见上文「开发约束与规则」);构建新增 Qt 绑定排他(否则 PyInstaller 在收集阶段直接中止)与产物 TTS 自检。pet/settings_interaction.py,modern_settings_dialog.py 2441 → 2347 行,行数预算首次因拆分下调。v4.2.0 是自 v4.1.0 以来的开发增量版,主线是「内存长稳与单进程多开」「设置与菜单可配置化」「边缘交互玩法」「Agent/DSH 生态与桌面兼容」。完整发布说明见 docs/RELEASE-v4.2.0.md。
内存与多开
设置与菜单
玩法与提醒
Agent 联动与 DSH
harness_autostart 随桌宠起服务、探测链路静默化、macOS Finder Node 解析;服务本身可重启/停止(见「最近修复与变更记录」的 2026-09-19 条目)。跨平台、安全与兼容
WS_EX_TRANSPARENT)、macOS Dock 隐藏彻底生效 + 原生 Dock 菜单、Linux Fcitx 中文输入随包插件;明文 API Key 自动迁入 keyring;直播捕获(OBS/直播姬)气泡兼容;DLC/换角色加固(缺失动画不崩、启动回退默认角色、点击台词支持外部素材目录)。v4.2.0(2026-09-10 发布)之后,main 上又积累了 54 个已合并 PR、约 175 个提交(另有若干直推修复),全部尚未发布——当前正式版本仍是 v4.2.0。本节是这批变更的总览;带根因与验证细节的逐条记录见下方 最近修复与变更记录;面向发布的完整说明(含 发布前测试清单 与 视频预演脚本)见 docs/RELEASE-v4.2.1.md。
桌宠玩法与外观
bubble_text_scale(50–300%,默认 100,设置 → 外观 → 桌宠显示)。列宽、换行预算、label 尺寸与字号共用同一系数整体等比放大,标准气泡与呼吸气泡都生效;默认 100% 时与旧版逐像素一致。设置与菜单
输入 / 点击反馈 / 自言自语 属对外契约,一律不变。设置搜索命中非当前标签的行时自动切到那个标签**(搜「配图概率」→ 切到「自言自语」)。modern_settings_dialog.py 2441 → 2347 行(首次因拆分下调行数预算),整域搬进新模块 pet/settings_interaction.py。config.json。提醒与音乐
voice_chime(#118 / #125):edge-tts 合成,六种调度(每小时 / 30 / 15 / 5 分钟 / 每分钟 / 自定义时间点);台词库每天整套换新(按日期确定性洗牌,当天固定一套排列、日内按序取用;中文库 120 条、英文库 96 条,每 15 分钟一天 96 次不重句),气泡文本与口播文本解耦(阿拉伯数字 vs 中文读法);提前预合成以降低延迟。默认关闭,且默认不占用右键菜单(#157)。festival_reminder(#127,默认关闭):中国节日 / 24 节气 / 西方节日三类可选,共 46 个节日定义与 314 条氛围文案;命中当天用气泡告知,可选语音念出,并与语音报时在同一分钟自动让位、不叠音。_sample_stop 事件只 set() 没有复位路径),并把 SMTC 查询 / 菜单暂停切歌移出主线程(实测「模拟 SMTC 卡 6 秒」:修复前 _on_tick 阻塞 6.00s、修复后 0.0000s);同时补齐 psutil 依赖声明与构建收集,否则该修复在打包版里等于没修。self_talk_speak_enabled(#176,默认开):点击桌宠弹出的自言自语直接用语音报时的音色念出来(与报时共用音频通道、不叠音);不想要就关掉这一行。同时把**「点击触发自言自语」的显隐与朗读与「气泡自言自语」总开关解耦**——显示与朗读只认 click_show_self_talk(此前关掉定时气泡会连点击台词一起消失)。self_talk_voice_precache_enabled(#176,默认关):保存设置后与每次启动 9 秒后,在后台把「点击台词 + 点击动画绑定台词」合成为本机语音文件(<配置目录>/self_talk_voice/<文本摘要>.wav)——命中即本地播放,点击秒出、断网也能说;幂等补齐(只补没有音频的句子)、单飞不叠线程、0 字节残文件当缺失重合成、服务不可用整轮跳过只记一条日志。需要本机 CosyVoice 语音服务(http://127.0.0.1:9880 的 /health 返回 ok;本机合成一句约 20–60 秒,所以只在后台做);没装就保持关闭,点击仍走在线合成、不会变哑。self_talk_image_chance(#176,默认 30):改为先掷骰决定出图还是出文本,再在对应池内等权选。此前是「文本 + 图片拼成一个列表等权随机」,图片目录里几十张图时实际出图率可达 80%+、文本几乎轮不到(实测 24 图 + 5 句 → 82.8%)。0 = 只出文本、100 = 只出图;想恢复旧观感填 图数 ÷ (图数 + 文本数)。只有一边有内容时不掷骰子(避免「点一下什么都不显示」)。改完需重启桌宠生效。文件解读与隐藏对话
progress_interval_seconds(默认 15s)冒泡汇报「已读 N 字 / 已耗时 X 秒」,完成后气泡展示摘要并把 user+assistant 消息回写会话(AI 对话窗口可见,标题「文件解读:<文件名>」)。可解读 = 文本类后缀(聊天附件白名单 ∪ 常见代码后缀),共享 20 万字符预算;目录 / 图片 / 二进制不解读。新增设置域「文件识别」与嵌套配置键 file_interpret。dynamic_island.hidden_chat(默认开)。Agent 联动与 DSH
pet/report_gates.py,8 个事件聚合类别各一个 0.00–1.00 概率滑块(默认 activity 0.6、其余 1.0),旧布尔开关自动迁移;概率门只作用于「弹气泡」这一步,检测与原始记录链不采样。dsh web,端口被非 dsh 进程占用或反查不到属主时一律不终止,只如实报告。桌面与交互
position / end_time 恒为 0.0,播放中、暂停、切歌、快进四种状态采样结果一致),歌词只能按本地时钟估算,于是快进或从中途开始播放必然错位。新增右键菜单「音乐 → 歌词对齐」(回到开头 / 上一句 / 下一句 / 后退 5 秒 / 前进 5 秒;报进度的播放器会自动置灰并说明理由),并把"开启功能时歌已在播"从静默跳过(连歌名都不显示)改为先亮歌名 + 提示一次可对齐;同时修「向后拖到第一句歌词之前仍显示旧句」与多会话下跳到浏览器里那首歌(会话选取改为粘住当前播放器)。碰撞与物理
平台与系统
0xc0000142 且阻塞关机(#112,issue #111):会话被拆除后仍 CreateProcess 新 ffmpeg;新增进程级「会话结束」闸门,四条 ffmpeg 派生路径全部拒绝,关机不再弹窗。XMoveWindow 同样被钳,已实测),旧的「窗口悬出屏幕、身体贴边」边界算式全部退化为 no-op。改为虚拟窗口位置语义——窗口本体主动钳入工作区、帧按绘制偏移在窗口内平移,身体贴到屏幕边缘;并新增角色包 manifest 的 body_box 稳定身体框通道(alpha≥128 口径,shenshen 实测 [212,60,428,330])。真机实测边缘间距 L/R≈154 → 个位数 px;屏幕中央 delta 恒为 0,Windows/macOS 逐像素不变(#137)。#141 一起被 #143 回退,实测无效);真凶是 #140 新增的音乐播放器预热扫盘(#149,事故 2026-09-19)——_search 对 C:/、D:/ 等盘根做三层浅扫,而预热对网易云 / QQ 音乐各起一条线程并发扫同一批盘根,与 Python GC 交叠后在原生层崩溃(崩溃位置漂移、macOS/Linux 全绿)。修复为 _search 全局串行化 + 测试进程内桩掉扫盘。Agent 联动与 DSH
link: 安装不装被链接包自身依赖,打包副本上解析失败会让 dsh web / headless / desktop 全 profile 起不来;恢复零依赖设计,构建门禁从「必须声明依赖」反转为「禁止声明任何依赖」。link: 残留。_physics_mode 代理返回 False 而非哨兵 None,使交互判定恒真、每次 tick 被静默拦截。¥0.00 与真实值之间跳动」(#107,issue #106)。语音与提醒
zh-CN 音色(晓辰 / 晓涵 / 晓梦 / 晓墨 / 晓秋 / 晓睿 / 晓双 / 晓萱 / 晓颜 / 晓悠),配置指向它们时 edge-tts 抛 NoAudioReceived: No audio was received. Please verify that your parameters are correct.,用户只看到一句「语音合成失败(NoAudioReceived: …)」;更隐蔽的是缓存目录里留下的 0 字节 mp3 会被当成命中——播出一声静音且永远不再重合成。现在:配置音色不在可用表里 → **自动改用默认音色「晓晓」**并提示一次(音色「晓涵」已不在微软在线音色表里(多半被下架了),本次改用默认音色「晓晓」;可在 设置 → 语音 → 语音报时 → 音色 里换一个可用的。,每进程只提示一次);每个候选音色最多重试 2 次(失败后间隔 1.5 s)再退默认音色重试;0 字节缓存判为未命中并清理半截产物(服务端「成功但空文件」也算失败);内置音色清单按微软在线表重建为 36 款(中文 14 + 英文 22),设置页里不在清单的配置值显示为「不在清单里:<值>」。实机复核:用已下架音色触发 → 自动改用默认音色并产出 22 752 字节音频;list_voices() 返回 322 款。quick_chat 气泡形状、连接测试线程安全。#136 / #131 经 #143 撤出:歌词显示的审计修复与节日动画/生日因 Windows CI 出现 C 级 access violation 被整批回退——#143 一个提交内先撤掉自己的 #142(WinRT 闸门修复)、再撤 #141(#136 / #131),净 diff 为 69 文件 +573 / −5612,#141 的改动在 main 上零残留(pet/festival_animations.py、pet/http_util.py、pet/media_window.py 与 assets/characters/shenshen/videos/random/端蛋糕送礼物.webm 均已删除,节日动画与「我的生日」功能不存在)。歌词显示功能本体仍在(pet/music_lyric.py 等),撤下的只是那批审计修复。#143 明确保留:#135 采样线程修复、#137 Linux 贴边、#139 bridge 冒烟。节日提醒(#127)不受影响,仍然保留。#150 内的修复提交写明「#143 把当时的头号嫌疑 now_playing / WinRT 整体回退后崩溃仍现,说明另有凶手」——真凶是 #140 引入的播放器预热扫盘(见上面「关键修复」的 #149 条目)。#164 经 #166 回滚:内置自言自语接回 dialogue_mode 风格路由的改动(thinking 键语义误用)已撤回。webm_clip_loop 三处、agent_link_threads 两处、slot 竞争改 flag 文件放行),并新增「worker 线程喂入时信号槽必须在 GUI 线程执行」的护栏用例。scripts/slim_bundle.py 包体瘦身并接入 onedir 构建(#125):绿色版 zip 176.2 MB → 146.5 MB(−12.8%),onedir 目录约 309 MB → 256.2 MB(移除 124 个冗余文件 / 53.03 MB);白名单移除 + 依赖闭包校验,任一校验失败即中止。docs/INDEX.md——全文档唯一入口索引 + 新文档入场与互链规则。quick_chat 呼吸气泡大圆角回退、连接测试 worker 对已删对话框的安全闸。docs/PR-REPORT-TEMPLATE.md,由 tests/test_pr_report_discipline.py 机器化校验(校验模板存在、AGENTS.md 与 DEV-HANDOVER.md 仍写明要求、生效日之后的报告标题级含三章节且在 docs/INDEX.md 登记);2026-09-22 起生效,历史报告豁免。ERROR: Aborting build process due to attempt to collect multiple Qt bindings packages…),而 edge-tts 正是在收集阶段被打断的——新增 20 个 --exclude-module(其余 Qt 绑定 + 把它们拖进来的 matplotlib / IPython 栈;原有排除一条不减)。另新增 scripts/verify_bundle_tts.py:在打包机算出 import edge_tts 的导入闭包,与产物 exe 的内嵌 PYZ / _internal 清单比对,缺一即中止构建(实测真产物 25 个必需模块全在、exit=0;修复前曾因 .pth 启动期注入模块与运行时合成模块假红,已修并加 7 条回归用例)。assets/characters/shenshen/videos/manifest.json 新增 head_box,pet/catalog.py 新增 character_head_box()(未声明时按身体框上缘取 45% 兜底)——为「边缘探头只露头」铺路;当前零调用方、本版尚未接线。pet/settings_interaction.py,modern_settings_dialog.py 2441 → 2347 行、行数预算首次因拆分下调;「自动化与联动」(94 行)仍是最长域,按同法分页是下一步。docs/ISSUE-EDGE-TTS-VOICE-DEPRECATION-2026-09-22.md(排查链与残留风险)与 13 条离线回归用例 tests/test_voice_chime_edge_voice.py(重试、退默认音色、空音频算失败、非空缓存判定、提示只弹一次)。v4.0.0 是一次大版本升级:在 v3.1.1 的桌宠基础上,合并了社区贡献者的现代桌面体验重构(PR #11/#12/#13)与性能/主动陪伴体系(PR #7),并完成多轮用户反馈修复与新增功能。以下是本版新增能力的总览:
桌面体验重构(现代双 UI)
性能与主动陪伴(PR #7)
稳定与修复批次(多轮用户反馈 + 三方审查)
新增桌宠功能(本版)
本项目改自、源于 PC2005-cloud/dsh-pet。桌宠的基础交互思路、动画链行为模型和部分资源组织方式来自原项目,感谢原作者的开源贡献。
DeepSeek 余额显示(气泡/小部件思路)参考了 MeteorNOX/DeepSeek-Balance-Whale-Widget,本项目的实现为桌宠内置的轻量版(菜单「DeepSeek 余额」+ 可选自动刷新,通过 DeepSeek 官方 /user/balance 接口查询,详见 DeepSeek API 查询余额文档)。
当前动画素材已同步参考项目近期更新后的高清 WebM 资源。项目以 WebM 目录为动画源;assets/characters 包含 106 个 WebM 动画文件。GIF 目录仅在构建 GIF 变体时生成。后续新增或替换动画时,请更新 WebM,需要构建 GIF 变体时再生成对应 GIF。
voice_chime(#118 / #125,默认关闭)、节日提醒 festival_reminder(#127)、事件汇报概率门(#97)、气泡文字大小可调(#153)、Harness 服务重启/停止(#153)、聊天背景裁切取景(#163 / #167 / #169 / #170)、音乐歌词显示与「上一首」(#128 / #129 / #135)、歌词对齐(#175)、点击台词朗读 + 本机语音预缓存 + 配图概率(#176)、音乐播放器路径进设置页(#177)、「互动」域设置页分页(#179);关键修复:Windows 关机/注销弹 0xc0000142(#112,issue #111)、桥接插件归零外部依赖(#104)、DSH 事件层系统性收尾(#105)、启动即按配置装配可选服务(#100 / #102,issue #99)、单进程多开下主动识屏永不触发(#145)、点击桌宠夺走焦点导致全局复制粘贴失效(#121,issue #98)、Linux 屏幕边缘无法贴边(#137)、岛碰撞改同步硬墙(#173)、edge 音色被下架后自动兜底 + 0 字节缓存不再算命中(#178)、构建不再被 PyInstaller 的 Qt 绑定互斥中止(#180);另有 #136 / #131(歌词显示审计 + 节日动画/生日)经 #143 回退、#164 经 #166 回滚。逐项清单见 v4.2.0 以来的变更(v4.2.1) 与下方 最近修复与变更记录;发布说明、验收清单与视频预演脚本见 docs/RELEASE-v4.2.1.md。docs/RELEASE-v4.2.0.md。正式发布时请以 Releases 页面实际上传的文件为准。当前推荐下载的 Windows 产物如下:
| 版本 | 安装包(setup.exe) | 绿色版(zip) | 适合场景 |
|---|---|---|---|
| Chat WebM | dsh-pet-standalone-webm-chat-setup.exe(约 128 MB) |
dsh-pet-standalone-webm-chat-portable.zip(约 156 MB) |
WebM 高清播放 + AI 对话,功能完整 |
| 无 Chat WebM | dsh-pet-standalone-webm-setup.exe(约 128 MB) |
dsh-pet-standalone-webm-portable.zip(约 156 MB) |
只想要桌宠本体,不接入 AI |
选择建议:
两个版本使用同一套高清 WebM 素材(106 段动画),只是入口不同:Chat 版会加载聊天子系统,无 Chat 版完全不携带 AI 对话依赖。
旧版 GIF 超大单文件(约 800 MB,运行时会在 C 盘临时目录解压并可能残留缓存)不再默认发布;确有需要可参考本文档「打包发布」一节自行构建 GIF 变体。
macOS(Apple Silicon)用户:产物为
dsh-pet-standalone-<webm-chat|webm>-macos-arm64.zip(onedir .app),由 GitHub Actions 构建,见下方「macOS 使用」。GIF 变体自 v4.0.0 起不再发布,需要请自行构建。Linux(x86_64)用户:产物为
dsh-pet-standalone-*-linux-x86_64.zip(onedir 目录,解压即用),由 GitHub Actions 构建,见下方「Linux 使用」。
dsh-pet-standalone-webm-chat-setup.exe(或无 Chat 版)放到任意位置。%LOCALAPPDATA%\Programs\dsh-pet-standalone-webm-chat(当前用户目录,不需要管理员权限);D:\、E:\),点「浏览」自己选一个目录即可。常见问题
%APPDATA%\dsh-pet-standalone-webm-chat\%APPDATA%\dsh-pet-standalone-webm\%APPDATA%\dsh-pet-standalone\dsh-pet-standalone-webm-chat-portable.zip。E:\dsh-pet\),保持文件夹内结构完整。dsh-pet-standalone-webm-chat.exe 即可运行。绿色版与安装版是同一套 onedir 产物,运行行为完全一致;区别只是安装版多了快捷方式与卸载器。
设置 → 应用 → 已安装的应用(或「控制面板 → 程序和功能」)→ 找到 dsh-pet-standalone (WebM Chat) → 卸载。Build macOS App(或打 v* tag 自动发布),从 Release / Artifacts 下载 dsh-pet-standalone-webm-chat-macos-arm64.zip(或无 Chat 版)。dsh-pet-standalone-webm-chat.app,可拖入「应用程序」文件夹。xattr -dr com.apple.quarantine dsh-pet-standalone-webm-chat.app
~/Library/Application Support/dsh-pet-standalone-<变体>/(各变体相互独立,与 Windows 行为一致)。brew install node);启动器会自动探测 Homebrew/nvm 等路径并回退 npx @deepseek-ai/dsh。Intel Mac:当前 CI 只构建 arm64;Intel 用户请从源码运行(见下),或在 Intel 机器上自行构建。
Build Linux App(或打 v* tag 自动发布),从 Release / Artifacts 下载 dsh-pet-standalone-webm-chat-linux-x86_64.zip(或无 Chat 版)。dsh-pet-standalone-webm-chat/ 目录,运行其中的同名二进制:unzip dsh-pet-standalone-webm-chat-linux-x86_64.zip
cd dsh-pet-standalone-webm-chat
chmod +x dsh-pet-standalone-webm-chat # 一般无需,zip 已保留可执行权限
./dsh-pet-standalone-webm-chat
# Debian / Ubuntu / Mint 等(其他发行版请找对应包名)
sudo apt install libxcb-cursor0 libxkbcommon-x11-0 libegl1 libgl1 \
libfontconfig1 libdbus-1-3 fonts-noto-cjk
fonts-noto-cjk 用于中文显示(缺失时气泡/聊天中文会显示为方块)。QT_QPA_PLATFORM=xcb ./dsh-pet-standalone-webm-chat。~/.config/dsh-pet-standalone-<变体>/(各变体相互独立,与 Windows/macOS 行为一致)。~/.config/autostart/ 的 .desktop 文件)。paplay(PulseAudio)或 aplay(ALSA);两者都没有时静默跳过。npx @deepseek-ai/dsh。建议在 X11 桌面(GNOME/KDE/Xfce 等)上使用;托盘图标依赖桌面环境的系统托盘支持(GNOME 需安装 AppIndicator 扩展)。
建议使用 Python 3.10 或更高版本(CI 使用 Python 3.11,Windows 实机开发验证覆盖 Python 3.13),并在项目根目录执行:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
python -m pet
Windows 也可以直接双击 run.bat。它实际执行的是:
pythonw -m pet
源码入口默认包含 Chat 能力;如果只想验证桌宠核心功能,可使用无 Chat 的打包入口或在本地配置中关闭聊天。
dsh web(默认端口 38080,可用环境变量 DSH_PORT 覆盖;注:3080 在部分 Windows 上会落入 winnat/Hyper-V 保留段导致无法监听)并自动打开浏览器;启动命令自动适配不同安装方式(PATH 上的 dsh → node + npm 全局包 → 官方 npx @deepseek-ai/dsh),macOS 同样可用(.app 环境会额外探测 Homebrew/nvm 等常见目录,需装有 Node.js)。若本机已有 dsh web 在运行(38080 或官方默认 3080),直接复用并打开现有实例,不再重复拉起。首次运行时 npx 拉取组件 + dsh 自举(可能出现 npm/pnpm 安装窗口)在网络较慢时需要几分钟属正常现象,桌宠会冒泡提示启动中;想跳过首次下载可提前手动 npm install -g @deepseek-ai/dsh。「启动桌面端界面」打开已安装的 DeepSeek Harness 桌面端(当前仅 Windows 支持):默认安装位与注册表卸载项(DisplayName / InstallLocation / DisplayIcon 都算)都会探测,已安装就直接打开,未安装会如实报错、不会悄悄改开网页;桌面端是你自己的应用,桌宠退出不会关掉它。设置中另有「随桌宠启动 dsh 服务」开关与「自启目标」(只影响自启这一路,菜单点哪项就起哪项):开启后桌宠启动即按目标拉起——目标是 dsh web 时后台静默起服务(不开浏览器、不弹窗口),目标是桌面端界面时直接打开应用;自启还要求同时开着 DSH 联动(联动关着时 dsh web 没有消费者,不会拉起);关闭开关只是不再自动拉起,已经在跑的 dsh web 要等桌宠退出或关闭 DSH 联动时才收口(唯一例外:在桌宠拉起探测期间关掉开关,那一次刚拉起的实例会被立刻收掉);非 Windows 上「自启目标」里的桌面端项置灰并写明原因(暂不支持 ≠ 永远不支持)。「重启/停止服务」带确认框(列出将终止的 PID 与端口):--no-open 静默常驻之后没有可关闭的控制台窗口,这两个入口就是关掉它的正规方式——不必再去任务管理器杀进程。停止按「谁在监听该端口」反查进程,并核验命令行确实是 dsh web;端口被别的程序占用时只报告、不终止(避免误杀),macOS 因无 /proc 暂不支持反查(会如实报告「读不到持有它的进程」)。退出桌宠不会顺带停止你自己起的 dsh 服务(桌宠自拉起的那份由退出收口负责收掉)。1.0x 到 2.0x。idle、turn、move、click、drag、random 等目录组织。integrations/dsh-pet-bridge)与 Claude hooks 安装器:感知 AI Agent 状态并切换动作,支持开始干活、过程汇报、任务完成三种气泡反馈,右键 Agent 联动子菜单可独立开关。interaction_id 互不覆盖。config.json 的 agent_link.custom_agents 里声明任意 Agent(key / 显示名 / 事件文件路径),桌宠即对其 JSONL 事件文件做只读监听,联动行为与内置 Agent 一致——不改代码即可接入任何能写本地文件的 Agent,协议详见 docs/AGENT_LINK_PROTOCOL.md。pnpm_bin 配置键手动指定 pnpm 入口。[看看屏幕] 前台窗口:… 记录 + 一条回复),可继续追问;聊天窗未打开时仅气泡显示、不写入。/user/balance 接口,使用当前配置的 API Key 鉴权(需使用 DeepSeek 官方端点);查询结果 30 秒内缓存复用,重复查询秒回。click.wav 放到桌宠数据目录 sounds/ 下即可替换内置音效。todo_items[-instance].json,原子写)。todo_reminder_lead_minutes,默认提前 5 分钟)、10 分钟宽限;一次性待办到期自动归档;错过宽限的提醒静默盖戳,不会一次轰炸。system_notifications_enabled 总门控,默认开);总开关与提前量在「设置 → 桌宠行为 → 待办提醒」。edge_probe_enabled,默认关):拖到屏幕左右边缘自动进入 ±45° 探头姿态(常驻露出 0.55、点击拉直 0.82、数秒自动退回);探头激活时点击不播点击音效,且移动被闸门拦住(不会被平移出屏幕边缘,PR #101)。DecodeFanoutHub),3 窗只启动 1 个 ffmpeg 解码进程;ffmpeg 固定 -threads 1,并按 ffmpeg_recycle_minutes(默认 10 分钟)在圈边界定期回收,避免子进程内存无限爬升。first_frame_cache_max_mb=8(4–64 可配),只保留点击/转向/拖拽等瞬时交互核的 pinned 缓存,idle/move 交给预测式预热与 LRU,避免“每播一段新动画就 +1.76MB 不释放”的慢涨。WS_EX_TRANSPARENT,不再 setWindowFlag 重建原生窗口。DecodeFanoutHub)统一解码——待机时 ffmpeg 解码进程从 N 个减到 1 个,解码 CPU 与内存显著降低;机制与平台无关。单进程多开下的设置作用域:
config-slot-N.json。config.json)。以下键暂无设置 UI,编辑数据目录下的 config.json 后重启生效:
| 键 | 默认 | 说明 |
|---|---|---|
first_frame_cache_max_mb |
8 | 首帧缓存总预算(MB,4-64)。多开时按进程共享一份预算 |
predict_prewarm_lead_ms |
350 | 预测式预热:动画播到结尾前提前多少毫秒预解码预测的下一段首帧;0=关 |
ffmpeg_recycle_minutes |
10 | ffmpeg 解码子进程在圈边界的定期回收间隔(分钟,2-120);0=不回收 |
| 操作 | 效果 |
|---|---|
| 左键点击桌宠 | 触发点击互动动画 |
| 按住并拖动 | 移动桌宠;松开后根据拖动方向和速度处理转向、移动或惯性。多显示器下可以拖到 / 丢到任意一块屏(4.2.1 曾因 #137 的落位收口被限制在本屏,2026-09-23 修复,见下方发布后补丁) |
| 按住 SHIFT + 左键拖动 | 开启了「SHIFT+左键拖动」时,这是唯一的拖动方式;未开启时 SHIFT 无特殊含义 |
| 右键桌宠 | 打开带图标的上下文菜单;可切换新旧菜单模板,或选择「生小肥鱼」启动独立的新桌宠 |
| 双击托盘图标 | 显示 / 隐藏桌宠 |
| 右键托盘图标 | 打开设置、AI 对话、开机自启、DeepSeek Harness(启动/重启/停止)、退出等菜单 |
| 拖拽桌宠时 | 若开启了聊天窗跟随,聊天窗口会一起移动;默认不跟随 |
在「桌宠设置 → 桌宠行为 → 拖拽」与「外观 → 桌宠显示」中:
三者与「鼠标穿透」的区别:锁定/SHIFT 只是禁止拖动,点击互动(点头、音效、彩蛋)照常;鼠标穿透是桌宠完全不接收鼠标事件(点击会落到下层窗口),需要从托盘菜单关闭(或到「桌宠设置 → 互动」里改)。开启「鼠标穿透」时桌宠会气泡提示恢复位置。
速率对当前片段和后续片段均生效;设置范围 1.0x 到 2.0x。
「动作等待间隔」用于降低连续动作过于密集时的节奏:
0。0:保持当前连续播放行为。0:相邻的非待机、非转向动画之间等待指定时间;等待期间仍允许待机和转向动画播放。这个设置只影响动画调度,不会阻塞窗口拖动、点击、设置窗口或聊天窗口。
好女孩……
好模型……
欧鲸鲸……
气泡默认显示在角色当前可见形象边界的正上方并水平居中;屏幕上方空间不足时,会自动选择不遮挡角色的候选位置。自言自语窗口不会改变桌宠的透明 mask,也不会阻止桌宠移动。
用哔哩哔哩直播姬 / OBS 做窗口捕获时,如果窗口列表里找不到桌宠,是因为桌宠默认是"工具窗口"形态(不占任务栏,捕获软件会过滤掉这类窗口)——这就是同类软件 Bongo Cat 能被捕获而桌宠不能的原因。
解决办法:在「桌宠设置」中勾选**「直播捕获兼容模式」**(Windows),保存后立即生效:
除内置角色外,桌宠会自动扫描外部角色目录发现新角色。目录名即角色 ID,按下面的树状结构放置即可(目录里含 webm 或 gif 即被识别):
characters/ ← 外部角色根目录(见下方两个扫描位置)
└── <新角色ID>/ ← 目录名 = 菜单里显示的角色 ID,如 mycat
├── manifest.json ← 可选:角色名 / prompt / 主题色 / 动作映射
└── videos/
├── idle/ ← 待机(可多个)
│ └── 待机呼吸.webm
├── turn/ ← 转向
│ └── 东张西望.webm
├── move/ ← 移动
│ └── 原地踏步.webm
├── click/ ← 点击回应
│ └── 点击开心.webm
├── drag/ ← 拖拽(可选)
│ └── 悬空反馈.webm
└── random/ ← 随机动作池
└── 吃零食.webm
两个扫描位置(exe 同目录优先,其次用户数据目录;用户数据目录跨安装/升级保留):
| 平台 | exe 同目录 | 用户数据目录 |
|---|---|---|
| Windows | <安装目录>\characters\ |
%APPDATA%\dsh-pet-standalone\characters\ |
| macOS | .app/Contents/MacOS/characters/ |
~/Library/Application Support/dsh-pet-standalone/characters/ |
| Linux | 源码运行目录 characters/ |
~/.config/dsh-pet-standalone/characters/ |
把新角色的 videos 放进去后,右键桌宠重新打开菜单即可看到新角色,选中即热加载——不需要重新打包或重启程序。透明 WebM 素材的制作方法见素材生成教学。
不想研究 API 的话,打开「AI 设置」后只需要填一样东西:API Key。其他按下面的值照抄即可:
| 设置项 | 填这个 |
|---|---|
| API 地址 | https://api.deepseek.com |
| 模型 | deepseek-v4-flash |
| API Key | 在 DeepSeek 开放平台创建(步骤见下) |
软件首次打开时,API 地址和模型默认就已经是上面这两个值(DeepSeek),不用改;只要把 API Key 粘进去就能用。
如何创建 API Key(5 分钟搞定):
sk- 开头的密钥小提示:
首期协议是 OpenAI Chat Completions 兼容协议。Gemini 等其他服务只有在提供兼容网关或兼容端点时才可使用。
AI 对话接口返回的常见 HTTP 状态码含义与处理方式(「测试连接」与聊天发送遇到 HTTP 错误时,软件内会显示状态码与原始错误信息):
| 状态码 | 含义 | 处理方式 |
|---|---|---|
| 401 / 403 | API Key 无效或无权限 | 检查 AI 设置中的 API Key 是否正确、是否过期,或服务商账号权限是否足够 |
| 402 | 账户余额不足(Insufficient Balance) | 到服务商开放平台充值后重试;此错误说明网络与证书均正常,请求已到达服务器 |
| 429 | 请求过于频繁(限流) | 稍等片刻后重试;也可降低对话频率或减少会话历史长度 |
| 5xx | 服务端故障 | 服务商临时问题,稍后重试 |
| 网络连接失败 / 超时 | 无法连接 API 地址 | 检查网络与代理;确认地址可达、超时值足够 |
| SSL CERTIFICATE_VERIFY_FAILED | 证书校验失败 | 开着代理/梯子(证书被拦截)或本地网关 / 自签名证书时,可在 AI 设置中勾选「跳过 SSL 证书验证」 |
502/503/504 等 5xx 错误通常不是软件问题;若「测试连接」成功但发送失败,请把服务商返回的原始错误信息发到 Issue 便于排查。
Enter 发送,Shift+Enter 换行;生成中按钮变为「停止」。聊天窗默认为新版现代双栏工作台:左侧是会话导航(新建会话、会话列表、批量管理、跟随桌宠),右侧是消息时间线与输入区,包含:
system prompt 的优先级为:
角色用户自定义 prompt > 角色 manifest 中的 prompt > 全局默认 prompt
角色可在以下文件中声明聊天配置:
assets/characters/<character_id>/manifest.json
可选字段示例:
{
"chat": {
"system_prompt": "你是一个温柔的桌面宠物……",
"theme_color": "#79C7FF",
"chat_actions": {
"thinking": "thinking.webm",
"success": "success.webm",
"error": "error.webm"
}
}
}
非法或缺失的 theme_color 会回退为默认蓝色;缺少头像资源时,聊天窗使用角色 ID 首字母生成圆形头像。
配置与会话目录(目录名按变体分:Chat 版 dsh-pet-standalone-webm-chat、无 Chat 版 dsh-pet-standalone-webm、源码运行为 dsh-pet-standalone):
| 系统 | 数据目录 |
|---|---|
| Windows | %APPDATA%/dsh-pet-standalone-<变体>/ |
| macOS | ~/Library/Application Support/dsh-pet-standalone-<变体>/ |
| Linux | ~/.config/dsh-pet-standalone-<变体>/ |
目录中主要包含:
config.json
sessions/<character_id>/<session_id>.json
pet.log
配置格式当前为 v3,并兼容历史平铺字段,例如 chat_api_url、chat_api_key、chat_model、chat_system_prompt 和 chat_enabled。日志不会输出 API Key。
assets/
└── characters/
└── shenshen/
├── manifest.json
└── videos/
├── idle/
├── turn/
├── move/
├── click/
├── drag/
└── random/
assets/characters 是 WebM 动画源目录,包含 106 个 WebM 动画。assets/characters_gif)仅在构建 GIF 变体时生成。本项目的动画素材沿用参考项目 PC2005-cloud/dsh-pet 的三件套流程:① 提示词(配方)→ ② 素材生成链(引擎)→ ③ 插件(成品)。任何人 clone 参考仓库都可以从零生成自己的桌宠素材,本教学按该流程说明。
用 AI 视频生成工具(如可灵、Runway、豆包等;参考项目素材即由豆包生成),按提示词配方为每个动作生成一段 10 秒绿幕视频。配方硬性规范(参考项目的 prompts/桌面宠物 10 秒动作提示词.md,本项目自定义角色配方在本地 assets/prompt/<角色ID>/图像生成提示词.md):
#00FF00,无阴影、杂物、渐变或边框。一个动作一段视频,按动作名各存一个 mp4(如 video/吃白饭.mp4)。
参考项目 scripts/ 提供完整 Python 素材链(依赖:Python 3 + ffmpeg + numpy + scipy),四步:
cd scripts
# step01:水印遮罩填充(源视频若带水印/角标则先填补为纯绿幕)
python watermark_step01.py
# step02:绿幕抠像 → 透明视频(两条路线二选一)
# 路线 A(默认自动化):HSV 色相抠像,人人可复现
python chroma_step02.py
# 路线 B(精细手工,推荐):PR 手工抠像导出带 alpha 的透明 .mov,
# 放入 pr/(文件名与动作一致,如 吃白饭.mov)后导入
python pr_import_step02.py
# step03:归一化 2160×1215 统一站立居中(对齐脚底锚点)
python normalize_step03.py
# step04:转码 640×360 透明播放变体(VP9 alpha 透明 WebM)
python encode_thumbs.py
参考项目全部 106 个动作均采用路线 B(PR 手工抠像):对含第三方物品/透明边缘复杂的动作,自动 HSV 抠像易残边或误抠;
chroma_step02.py保留为自动化兜底。中间产物 step01~04 由脚本生成、不入仓库;video/源视频与scripts/是成果、入库维护。
把 step04 产出的 640×360 透明 WebM 按分类放入角色目录:
assets/characters/<角色ID>/videos/
├── idle/ 待机(可多个)
├── turn/ 转向
├── move/ 移动
├── click/ 点击回应
├── drag/ 拖拽(可选)
└── random/ 随机动作池
保持几何约定与播放器一致:画布 640×360、24fps、VP9 alpha 透明;角色脚底对齐画布 y=330(catalog.py 中 FEET_Y=330、落地偏移 PAD=30),这样桌宠窗口的脚底落地对齐才准确。
命名保持稳定、避免重复;可参考 assets/characters/shenshen/videos/ 现有 106 段动画的组织方式。
如需 GIF 变体,运行 python scripts/convert_to_gif.py --force --clean 同步生成。
不想重新打包?把做好的透明 WebM 按「切换角色」的外部角色目录结构直接放入
characters/<角色ID>/videos/,右键菜单即可热加载新角色。快速验证:
python -m pytest -q会检查 WebM/GIF 相对路径一一对应;源码运行python -m pet或重新打包后检查对应分类是否正常播放。
更新 WebM 素材后,在项目根目录执行:
python scripts/convert_to_gif.py --force --clean
其中:
--force:覆盖已有 GIF。--clean:删除目标目录中已经不存在对应 WebM 的旧 GIF,防止两套素材残留不一致。转换前请确认 imageio-ffmpeg 已安装。生成后可以用下面的命令检查数量:
(Get-ChildItem assets/characters -Recurse -Filter *.webm).Count
(Get-ChildItem assets/characters_gif -Recurse -Filter *.gif).Count
两者应当相同;还应检查相对路径是否一一对应。
assets/characters/<character_id>/videos/ 下按动画类别建立目录。<character_id>/manifest.json 中填写名称、prompt、主题色和动作映射。pet/
├── app.py # AppShell + PetInstance:进程级/每窗容器与装配(托盘、多窗共享)
├── config.py # 配置读取、迁移和持久化(reload 白名单 + schema 测试)
├── config_domains.py # 配置域 facade(chat/agent_link/proactive/collision/menu)
├── window.py # 桌宠主窗口(组合根;碰撞/平台层/动画链已拆出,受行数预算红线)
├── window_optional_services.py # 窗口可选服务懒装配 mixin(todo/file_eater 等)
├── collision.py # 碰撞物理核心(纯 Python,无 Qt)
├── sprite_collision.py # 进程内 sprite 碰撞世界(多宠统一世界 + 岛静态成员)
├── overlay_shell.py # 单合成窗产品壳(overlay 拓扑入口:sprite 世界/托盘/指令通道)
├── overlay_spawn_state.py # 活跃宠清单 + 无锁 slot 身份分配 + 每身份几何持久化
├── pet_sprite.py # 合成窗内的宠物 sprite(帧签名缓存/拖拽轨迹/抛掷)
├── decode_fanout.py # 同角色共享解码链(进程内帧扇出 DecodeFanoutHub,单向依赖约束)
├── frame_cache.py # 通用字节预算 LRU(webm 元数据缓存等小缓存用)
├── perfstats.py # 性能打点(PET_PERF_STATS=1 启用,atexit 落盘)
├── predictive_prewarm.py # 预测式预解码预热(切动画前预拉下一段,默认提前 350ms)
├── platform_win.py # Windows 平台层(鼠标穿透/全屏判定/PerPixel 输入/WS_EX_TRANSPARENT)
├── platform_mac.py # macOS 平台层(NSWindow level/激活策略)
├── catalog.py # 角色和动画素材发现
├── library.py # 动画库访问(懒加载 + 优先级预热)
├── webm_clip.py # WebM 播放(reader 线程/软停 re-arm/定期回收/fan-out 钩子)
├── speech_bubble.py # 气泡绘制与交互
├── speech_bubble_text.py # 气泡分页/定位纯函数
├── click_sound.py # 点击音效(ClickSoundPool 单例封装)
├── desktop_notify.py # 自绘右下角系统通知
├── slot_manager.py # slot 身份/配置落种/旧 spawn 迁移/runtime 标记(D7 避让通道)
├── file_eater.py # 拖拽文件“吃”动画与统计(不真实删除/移动文件)
├── proactive.py # 主动识屏陪伴(Watcher 编排)
├── proactive_limiter.py # 主动识屏频控
├── proactive_memory.py # 主动识屏记忆
├── agent_link.py # Agent 联动监视器(多 Agent 事件源:CLI/IDE/SQLite 轮询)
├── multi_window_shared.py # 进程级多窗共享子系统(agent_link/proactive/全屏 watcher)
├── vision.py # 视觉模型调用(看看屏幕/主动识屏;PIL 懒加载)
├── harness_launcher.py # DeepSeek Harness 一键启动
├── modern_settings_dialog.py # 新版设置主对话框(已拆分瘦身,保留 re-export;受行数预算红线)
├── settings_widgets.py # 设置控件库(自绘开关/SettingRow/ModernSelect 等)
├── settings_menu_layout_editor.py # 右键菜单布局编辑器
├── settings_theme_qss.py # 设置主题 QSS(明暗)
├── todo_reminder.py # 待办提醒调度(气泡/桌面通知,PR72 合入;进程级单例)
├── todo_panel.py # 待办管理面板(右键菜单「待办提醒」打开)
├── context_menus/ # 新旧菜单模板、图标、彩蛋入口
├── chat/ # 独立 AI 对话子系统(现代双栏 + 经典手机式)
│ ├── models.py # 数据模型(ProviderConfig/ChatSession/...)
│ ├── providers.py # Provider 请求与连接测试
│ ├── service.py # 对话服务
│ ├── session_store.py # 会话持久化(异步 writer + 注册表)
│ ├── geometry.py # 聊天窗跟随定位(双 UI 共享纯函数)
│ ├── utils.py # 会话标题/时间格式化(双 UI 共享)
│ ├── themes.py # 聊天窗背景主题
│ ├── widgets.py # 新版聊天窗
│ ├── legacy_widgets.py # 经典手机式聊天窗
│ ├── ai_settings_page.py # AI 设置页(PR #76 从 modern_settings_dialog 拆出)
│ ├── modern_styles.qss / legacy_styles.qss
│ └── ...
└── updater.py # 检查更新与发布资产解析
integrations/dsh-pet-bridge/ # DSH 桥接插件(Agent 联动)
packaging/
├── pet_entry.py # Chat 构建入口
├── pet_entry_no_chat.py # 无 Chat 构建入口
└── dsh-pet.iss # Inno Setup 通用安装包脚本(/D 参数编译各变体)
scripts/
├── build_onedir.ps1 # Windows onedir 构建 + zip 绿色版打包(本地与 CI 共用入口)
├── build_macos.sh # macOS .app 构建(本地与 CI 共用入口)
├── build_linux.sh # Linux onedir 构建(本地与 CI 共用入口)
├── check_bundle_encoding.py # 产物中文编码自检(issue #26,构建脚本内自动调用)
├── make_icon.py # 从待机动画提取封面帧生成应用图标(assets/icon.ico)
├── convert_to_gif.py # WebM → GIF 全量同步脚本
└── cleanup_mei_cache.py # 检查/清理旧 onefile 版本遗留的 _MEI 缓存(默认预览)
tests/ # 单元测试、Qt offscreen 测试和构建相关验证
# (含 test_architecture.py 架构红线:依赖方向 /
# 窗口私有面冻结 / window.py 行数预算 /
# modern_settings_dialog.py 行数预算 / 孤儿簇守卫)
给 window.py 加功能前必读:docs/WINDOW_PY_SPLIT_GUIDE.md
——window.py 处于「只许瘦不许胖」的增量拆分公约下(CI 有行数预算红线),
新功能先按公约拆对应控制器再动手。modern_settings_dialog.py 同理:
控件库/菜单编辑器/AI 设置页/主题 QSS 已拆出,再往主对话框塞新页面会被行数预算红线拦下。
在项目根目录执行:
pip install -r requirements.txt # 运行时 + 开发依赖(含 pytest/ruff)
$env:QT_QPA_PLATFORM = "offscreen"
python -m pytest -q
python -m compileall pet packaging scripts
最近一轮记录(PR #76 合并后的 main,2026-09-06):
pytest:1322 passed / 7 skipped(CI 三平台 windows/ubuntu/macos 全绿;本机如遇 Windows symlink 权限等环境性失败,与改动无关)。ruff:干净。compileall:通过。window.py 与 modern_settings_dialog.py 行数预算 / 孤儿簇守卫。_MEI 缓存。如果要验证真实窗口,不要设置 QT_QPA_PLATFORM=offscreen,直接运行 python -m pet 或打包后的程序,重点检查:
发布流水线:onedir 构建 → zip 绿色版 → Inno Setup 安装包。onedir 运行期零解压,不产生 _MEI 缓存;安装包免管理员、可选安装目录。
需要 PyInstaller:
python -m pip install pyinstaller
# WebM Chat 版
powershell -ExecutionPolicy Bypass -File scripts\build_onedir.ps1 -Variant webm-chat
# WebM 无 Chat 版
powershell -ExecutionPolicy Bypass -File scripts\build_onedir.ps1 -Variant webm
产物位于 dist-onedir\<name>\(绿色版目录)与 <name>-portable.zip。
GIF 变体(
gif-chat/gif)需要先运行scripts/convert_to_gif.py --force --clean生成 GIF 素材,构建时加-Gif参数;默认发布不含 GIF 版。
macOS / Linux 本地构建与 CI 共用同一份脚本(PyInstaller 打包、中文编码自检都在脚本内完成):
# macOS(.app,输出 build/macos/)
bash scripts/build_macos.sh --variants webm-chat,webm
# Linux(onedir,输出 dist/)
bash scripts/build_linux.sh --variants webm-chat,webm
本机已安装便携版 ISCC:E:\tools\InnoSetup6\ISCC.exe(免管理员)。通用脚本 packaging\dsh-pet.iss 用 /D 定义编译不同变体:
# WebM Chat 版(脚本默认值)
E:\tools\InnoSetup6\ISCC.exe packaging\dsh-pet.iss
# WebM 无 Chat 版
E:\tools\InnoSetup6\ISCC.exe /DMyAppShortName=dsh-pet-standalone-webm /DMyAppExeName=dsh-pet-standalone-webm.exe /DMyAppDir=..\dist-onedir\dsh-pet-standalone-webm "/DMyAppId={{3424d6cc-af3c-4383-8797-ab520b923aa6}}" "/DMyAppDisplay=dsh-pet-standalone (WebM)" packaging\dsh-pet.iss
完整命令(含 GIF 变体)与安装包特性见 docs/ONEDIR_PACKAGING.md。
打包注意事项:
packaging/pet_entry.py,无 Chat 版使用 packaging/pet_entry_no_chat.py;无 Chat 入口会排除 pet.chat 和 keyring,不携带 AI 对话依赖。PrivilegesRequired=lowest),默认目录 %LOCALAPPDATA%\Programs\...,向导中可自行选择任意盘符。构建记录和 SHA256 位于:
docs/BUILD_ARTIFACTS-2026-08-22.md
PyInstaller 不支持交叉编译,Linux 包必须在 Linux 上构建。推荐直接使用仓库内的工作流 .github/workflows/build-linux.yml:
workflow_dispatch),或打 v* tag 自动触发并发布到 Release。dsh-pet-standalone-<变体>-linux-x86_64.zip(onedir 目录,保留可执行权限)。Linux 发布两个 WebM 变体(webm-chat / webm);GIF 变体包体约 800 MB,不发布。本地构建(在 Linux 机器上)——以正式脚本为准(手工 PyInstaller 命令 长期与脚本漂移、会漏菜单模板/QSS 等资源,审查 P2-07):
bash scripts/build_linux.sh webm-chat # 变体:webm-chat / webm / gif-chat / gif
GIF 变体需先运行
python scripts/convert_to_gif.py --force --clean。
旧版单文件 EXE(onefile)运行时会在系统临时目录创建 _MEI数字 目录,崩溃或强制结束时可能残留;当前 onedir 发布版不会再产生该缓存。程序启动时仍会自动尝试清理超过 24 小时的遗留目录,并跳过当前进程正在使用的运行目录;权限不足或目录被占用时只记录日志,不强制修改 ACL。
也可以使用项目提供的专用脚本检查:脚本默认只预览,不会删除任何目录。确认所有桌宠进程都已退出后,才使用 --delete:
python scripts/cleanup_mei_cache.py
python scripts/cleanup_mei_cache.py --min-age-hours 0
python scripts/cleanup_mei_cache.py --delete
如果某些目录因权限异常仍无法删除,请先退出所有桌宠,再用管理员 PowerShell 运行脚本;脚本不会自动接管目录所有权,避免误操作其他临时文件。
localhost;127.* 绕过。逐条实测与推荐设置见 docs/NETWORK-PROXY-AND-VPN-2026-09-22.md。按时间倒序记录。v4.2.0 及更早版本的完整清单见
docs/RELEASE-v4.2.0.md与 GitHub Releases。 v4.2.0 之后的全部变更(含本表未逐条展开的 #113 / #114 / #118 / #120 / #121 / #123 / #125 / #127–#130 / #135 / #137 / #139–#144 / #146–#152 / #155 / #157–#182)已在上方 v4.2.0 以来的变更(v4.2.1) 按功能与修复汇总;面向发布的逐项说明(含发布前测试清单与视频预演脚本)见docs/RELEASE-v4.2.1.md。
main@1c3a59c)——纯桌宠版:桌宠隐藏后单击灵动岛即可把桌宠叫回来hidden_chat 默认开」时单击会发 chat_requested,落到 AppShell._chat_from_island → _show_island_chat;而无 Chat 的打包变体(spec excludes=['pet.chat'],enable_chat=False)里 _island_chat_available() 恒为假,该入口直接 return——请求发出后被静默吞掉。岛又是隐藏后唯一的常驻交互面,于是纯桌宠版被困住;有 Chat 的变体恰好被气泡里的「显示桌宠」按钮兜住,所以只有纯桌宠版暴露。_chat_from_island 先判可用性,没有对话能力时改调 _show_pets_from_island_chat()(显示全部窗 + 同步岛 _pet_visible + 收起气泡,正是气泡内「显示桌宠」按钮那条通路)→ 单击岛 = 显示桌宠。有 Chat 的变体行为一字不变;click_action=toggle_pet 的偏好仍优先;hidden_chat 关闭时仍展开卡片。test_shell_click_when_no_chat_reshows_pets(修复前红:assert False is True「点击后桌宠仍未恢复可见」,修复后绿);test_island_chat.py + test_island_shell_wiring.py 23 passed;全量 2914 passed / 11 skipped;ruff 全清。三平台 workflow_dispatch 重新构建纯桌宠产物并以 gh release upload --clobber 覆盖 Release v4.2.1 的 4 个无 Chat 附件(版本号与 tag 不变)。详见 docs/PR-REPORT-ISLAND-RESHOW-NOCHAT-2026-09-23.md。#137(b418733,不在 v4.2.0 里)把落位统一进 move_window_towards,把身体框与窗口钳进当前屏工作区,throw_bounds 同源——拖拽与抛掷都被钉在本屏。修法:拖拽/抛掷开始时取一次多屏活动区域快照(各屏可用区的包围矩形 + 逐屏可用区),物理 tick 只读快照;活动范围再按「宠物当前所在的屏幕带」收窄(band_bounds),错位拼接(异分辨率/异缩放)时不会落进没有显示器的空洞。单屏、几何异常、没有快照时逐位退回本屏语义(漫游/落位/边缘探头不受影响)。frame_ready 换成角色头像。context_menu_layout 里的残留节点在配置载入时剔除。tests/test_multi_screen_interaction.py(17 例,假双屏驱动真实钳制/边界代码)与 tests/test_tray_icon_ready.py(7 例);两项修前分别 15 failed / 5 failed,修后全绿。真 Windows 平台实测托盘:建托盘瞬间 icon_pixmap() 为空(根因现场)→ 占位图标非空 → 首帧就绪后换成 50×64 角色帧。新增排障工具 scripts/probe_multi_screen_area.py(打印逐屏几何与「往每块屏拖」的修前/修后对照;本机只有一块屏 DISPLAY1 1536×[email protected],真实双屏待用户在双屏机器上验收)。详见 docs/PR-REPORT-ISSUE-186-TRAY-MENU-2026-09-23.md。iterdir 扫盘;「多开」开关长期挂在设置页但拓扑尚未收口。movement.move_anim_tick,位置交付 28.6Hz → ~160Hz @170Hz);webm 冷路径首帧不再 GUI 同步解码、抛掷低速冷切走 warm 闸门、meta 探测后台化;抛掷飞行期动画加速(flight_anim_speed);GUI 冻结采样器;子宠进程补挂远端硬墙 + 岛几何经碰撞 IPC 发布为静态成员(2s 心跳、变化合流 ≤10Hz)+ island_local_owned 双重结算守卫;音效包候选解析改进程级缓存(0.43ms/次命中);设置页隐藏「多开」开关(拓扑收口 Phase A,配置键原样保留回写,存量用户不丢值)。tests/test_island_remote_wall.py / test_move_sync.py / test_first_frame_no_gui_decode.py 等 10 个测试文件;受影响时序族(collision / island / webm / move_sync / throw / spawn / first_frame / meta / click_sound)高负载复跑 3 遍全绿;实机走路 A/B 300 秒、三开互撞 5 分钟、多进程岛墙场景;作者本机全量 3009 passed / 12 skipped。完整证据见 docs/PR-REPORT-PERF-ISLAND-CONSOLIDATED-2026-09-23.md。QPainter,与零拷贝无因果),该崩溃案未结案,跟踪于 .scratch/single-overlay-window/HANDOFF.md。歌词取词完成: 陶喆 - 讨厌红楼梦 -> 0行, 耗时 9.00s(命中缓存的歌正常,63行, 耗时 0.01s);右键「音乐 → 歌词对齐」整组点不动,而控制器刚弹过提示让用户去点它。urllib.request.urlopen,会继承系统代理。用户开着全局模式 VPN(127.0.0.1:12450,进程 18:37:41 启动)时,实测三源单次 41.3 / 22.1 / 20.4 秒,全部超过 HTTP_TIMEOUT(8 秒),于是每首未缓存曲目都在 9 秒死线处放弃。git diff 56f8ad2..main -- pet/music_lyric.py 为空(取词代码与合并前逐字节相同),而最后一次成功取词是 18:12:14(代理起来前 25 分钟)——两件事只是时间上撞在一起。_on_lyrics_ready 给的是本地估算位置,却被 LyricTracker.load 按 position is not None 当成"播放器上报的真值" → align_available() 返回假 → 「歌词对齐」子菜单置灰(registry._music_align_ready)。既有用例都用 _tracker.load(position=None) 直接建状态、绕过了生产路径,所以旧实现全绿。ProxyHandler({}) 的直连 opener(三源都是公开接口、两个还是国内域名);失败原因由 log.debug 升到 log.warning(带主机、耗时、异常类型),检测到系统代理时记一行 INFO;load() 新增 reported 参数,生产路径显式传 reported=reported is not None。fetch_lyrics 9.00s/None → 1.27s/62 行(第二次命中缓存 0.02s);真实网易云会话(《蝴蝶》- 陶喆,position=None)上实测 9.01s/None → 0.88s/53 行,控制器级端到端(真 SMTC + 真网络 + 真 Qt 事件循环)气泡出歌名+歌词、align_available=True、「下一句 / 前进5秒 / 回到开头」全部生效。新增 5 条离线用例(3 条代理与日志、2 条对齐闸门),修复前 4 红 1 绿;test_music_lyric.py + test_menu_layout.py + test_now_playing_session.py 165 passed;全量 2866 passed / 11 skipped;ruff 全清。docs/NETWORK-PROXY-AND-VPN-2026-09-22.md。实现细节见 docs/PR-REPORT-MUSIC-LYRIC-SYSTEM-PROXY-2026-09-22.md。ERROR: Aborting build process due to attempt to collect multiple Qt bindings packages: attempting to run hook for 'PyQt5', while hook for 'PySide6' has already been run!(打包机 site-packages 里装着 PyQt5 148 MB、matplotlib 27 MB、IPython 7 MB…)。而 edge-tts 恰恰是在收集阶段被打断的 → 语音永远打不进包。另有一个静默失败面:打包机没装 edge-tts 时 --collect-all edge_tts 只打印一行 WARNING(skipping data collection for module 'edge_tts' as it is not a package)就继续构建,产物缺 TTS——而打包版用户没有 pip 可装。scripts/build_onedir.ps1 新增 20 个 --exclude-module(其余 Qt 绑定 PyQt5/PyQt6/PySide2 + 把绑定拖进来的上游链 matplotlib 家族、IPython/ipykernel/jupyter/zmq/nbformat),原有排除清单一条不减;新增 scripts/verify_bundle_tts.py,在打包机算出 import edge_tts 的导入闭包并与产物 exe 的内嵌 PYZ / _internal 清单比对,缺一即中止构建。import PySide6.QtCore + import PyQt5.QtCore → 原文报错、exit=1;PyInstaller 报错里给出的处置办法正是 --exclude)。产物自检实测:chat 变体 [tts] bundle TTS stack OK (25 modules required, 2178 frozen)、exit=0、0.605 s;无 chat 变体 2068 frozen / 0.464 s;另现造一个真没 TTS 栈的产物仍报 25 个必需模块全缺(exit=4)——判定强度没降。自检脚本自身踩到一次假红:闭包原先按「import edge_tts 后 sys.modules 里所有非标准库模块」算,于是 .pth 启动期注入的 google/mpl_toolkits/pywin32_bootstrap、Cython 运行时现造的 cython_runtime、pkgutil 空壳 backports 全被算成「必须有」,真产物被判 FAIL;改为「导入前后做差 + 只认磁盘上有真实源码的模块」并加 7 条回归用例(改前 5 failed, 2 passed → 改后 7 passed)。AGENTS.md + docs/DEV-HANDOVER.md §8.2/8.3/8.4 + docs/PR-REPORT-TEMPLATE.md + tests/test_pr_report_discipline.py;2026-09-22 起对报告强制校验三个必备章节 + 索引登记)、pet/catalog.py character_head_box() 与 shenshen 的 head_box(零调用方,探头锚点接线未做)、主动识屏 v4.2.0 的 issue 草稿 docs/issue-draft-主动识屏v420.md。体量:11 文件、+781 / −7。settings_interaction.py输入 / 点击反馈 / 自言自语 属于对外契约,一律不变;设置搜索命中非当前标签的行时自动切标签;整域搬进 pet/settings_interaction.py(167 行)。modern_settings_dialog.py 2441 → 2347、预算同步下调(首次因拆分下调);三档宽度截图 docs/screenshots/settings-interaction-tabs-2026-09-22/。非当前标签只是不可见不销毁,objectName 仍是 settingRow_<键>,保存链一字未改。zh-CN 音色(晓辰 / 晓涵 / 晓梦 / 晓墨 / 晓秋 / 晓睿 / 晓双 / 晓萱 / 晓颜 / 晓悠),配置指向它们时 edge-tts 抛 NoAudioReceived: No audio was received…,用户只看到「语音合成失败(NoAudioReceived: …)」;② 连发时偶发服务端返回空音频,而缓存里留下的 0 字节 mp3 会被当成命中——播出一声静音且永远不再重合成。音色「晓涵」已不在微软在线音色表里(多半被下架了),本次改用默认音色「晓晓」;可在 设置 → 语音 → 语音报时 → 音色 里换一个可用的。);每个候选音色最多重试 2 次(失败间隔 1.5 s)再退默认音色重试;0 字节缓存判为未命中并清理半截产物;内置音色清单按微软在线表重建为 36 款(中文 14 + 英文 22);设置页里不在清单的值显示为「不在清单里:<值>」。可用音色表来自在线接口、缓存 6 小时,只在后台合成线程联网,失败沿用旧表。tests/test_voice_chime_edge_voice.py(重试成功、退默认音色、空音频算失败且不留文件、非空缓存判定、提示只弹一次、edge-tts 缺失仍走既有降级);实机复核:已下架音色触发 → 自动改用默认音色并产出 22 752 字节音频;list_voices() 返回 322 款;事故档案 docs/ISSUE-EDGE-TTS-VOICE-DEPRECATION-2026-09-22.md(已登记进 docs/INDEX.md)。无新增配置键。music_player_paths;自动搜索(深度 ≤3 的浅扫)扫不到时右键菜单项置灰,提示语是「找不到 X,可在配置文件中手动指定路径」——等于要求用户手改 JSON。clear_cache()(否则右键菜单下次要重扫盘 4–5 秒)。C:\Windows\System32\notepad.exe 代替播放器验证「写盘 → 菜单消费」链路(music_player_paths 落盘、菜单项由灰变亮、tooltip 不再报「找不到」);缺陷注入证明用例承重(去掉保存时的 clear_cache() → 2 条变红);设置页宽度探针 1100 / 720 px 无溢出,截图 docs/screenshots/music-player-paths-2026-09-22/。体量:19 文件、+568 / −9。稳态零新增定时器/线程(不点按钮则 QTimer 从不启动)。self_talk_speak_enabled(点击台词朗读,默认开)与 self_talk_voice_precache_enabled(后台把点击台词与点击动画绑定台词合成为本机语音文件,默认关,需本机 CosyVoice http://127.0.0.1:9880);② 显示与朗读只认 click_show_self_talk,与周期气泡解耦;③ 新增 self_talk_image_chance(默认 30):先掷骰决定出图还是出文本,再在对应池内等权选(0 只出文本、100 只出图;只有一边有内容时不掷骰子)。PostMessage 点击窗口中心 + 读日志计数,隔离 APPDATA):chance=0 → 6 次点击 / 4 条朗读 / 6 条音效;chance=100 → 8 次点击 / 0 / 8。预缓存实测四行日志文案(已开始后台预缓存台词语音… / 台词语音预缓存完成:[声线] 台词 -> 文件名 / 播放预缓存台词 xxx.wav(本地,不走网络) / 台词语音预缓存跳过:本地 TTS 服务不可用);本机无 CosyVoice 时验证「整轮跳过 + 只记一条 info + 不抛异常」。聚焦 264 passed、全量 2774 passed / 11 skipped;三档宽度截图 docs/screenshots/self-talk-2026-09-22/。配图概率默认 30% 是有意的行为变更,回滚口径:把它填成 图数 ÷ (图数 + 文本数)。改完需重启桌宠生效(行为在窗口启动时读一次)。position / end_time 恒为 0.0,播放中 / 暂停 / 切歌 / 快进四种状态采样结果一致),歌词只能按本地时钟估算;另修「向后拖到第一句歌词之前仍显示旧句」与多会话时跳到浏览器里在播的那首歌。tests/test_now_playing_session.py(224 行)与 tests/test_music_lyric.py 扩展;性能实测脚本 scripts/bench_music_lyric_align.py;完整证据(含网易云「不上报进度」的实机复现记录)见 docs/PR-REPORT-music-lyric-align-2026-09-22.md。tests/test_desktop_pet_features.py 相关用例同步更新。bubble_text_scale,50–300%,默认 100,设置 → 外观 → 桌宠显示):与既有「配图大小」并列的独立系数。列宽、换行预算、label 尺寸与字号用同一个系数整体等比放大(bubble_column_for_text(text, scale) + scale_bubble_font_px),所以「字号变大、气泡没变大」导致的行尾切字在这条链路上不可能发生;标准气泡与呼吸气泡两种形态都生效(呼吸气泡画布与安全区一起缩放),歌词/标题气泡的锁宽锁高语义不变。默认 100% 时 bubble_column_for_text / bubble_label_size / 字号与旧版逐像素一致(用例硬断言)。文字放大到超过屏幕可用区时,列宽按可用区收窄、气泡高度按可用区上沿钳制,分页(圆点页码 / 逐页停留)与缩放无关照常工作。审批/提问气泡保持既有固定布局(源码注释里写明了它有自己的按钮行布局)。dsh web)可以重启/停止了:菜单项从单项「启动 DeepSeek Harness」改为 DeepSeek Harness 子菜单(启动并打开页面 / 重启服务 / 停止服务)。用子菜单而不是三个平级项,是因为菜单模板与用户自定义布局里只有 harness 一个 id,新增平级 id 对老布局不生效。停止按「谁在监听该端口」反查进程(Windows GetExtendedTcpTable,POSIX /proc/net/tcp + /proc/*/fd),并核验命令行确为 dsh web;端口被非 dsh 进程占用或反查不到属主时一律不终止,只如实报告(防 pid 复用误杀)。踩坑记录:ctypes 调 GetExtendedTcpTable 必须显式声明 restype/argtypes,否则 64 位进程的缓冲区指针被截断成低 32 位、函数返回非 0、表永远是空的——本地第一次跑就是「假绿」(用例 skip 掉),probe-listener-pid.py 才定位到。ChatWindow._add() 只挂 _update_conversation_height、从不贴底,滚底只在 _load() 与 _begin_generation() 里显式调用——于是「用户自己刚发的那条消息」整条落在视口外(本地实测 value=2433 max=5206 gap=2773,单条超长用户消息即可复现),后续流式回复也就跟着停在半路。修复:_add() 贴底(用户角色显式恢复跟随)、resizeEvent 重排后重新贴底、贴底改为欠账式(_scroll_pin + scrollbar rangeChanged 追平),不再依赖「猜 singleShot 时机」;上翻阅读期间被动到达的流式内容不会把读者拽回底部。3 条回归用例(红→绿逐条验证)。_physics_mode 哨兵类型不匹配)experimental_single_process_spawn: true 时主动识屏从不触发——零日志、proactive_screen_state.json 从不创建;手动「看看屏幕」一切正常;dry_run=true + change_threshold=0 组合下仍零输出(已排除截图/频控/dHash/API 全部环节);右键反复开关无效。MultiWindowProxy._physics_mode 是聚合属性,早期实现返回 any(...) 的 bool;而 pet/proactive.py 的 G1 守卫按哨兵语义读取 getattr(self.win, "_physics_mode", None) is not None。单窗 PetWindow._physics_mode 的取值域是 None / 'drag' / 'throw',代理把「无人处于物理模式」表达成 False——False is not None 恒真 → interacting 恒真 → should_watch() 恒假 → 每次 8s tick 都在 G1 被静默拦截。又因 app.py 把 shared.proactive 注入为窗口的 proactive_watcher,右键开关拿到的是同一个共享实例,用户侧无法绕过。None 的模式返回,无窗处于物理模式时返回 None(本地实测 python -c 复现:修复前 proxy._physics_mode 为 False)。tests/test_single_process_shared.py)——「无人物理模式时哨兵必须是 None」「任一窗进入物理模式时报出该模式」「端到端:无人交互时 tick 必须越过 G1 守卫(前台窗口探测被调用)」;修复前 3 条全红、修复后全绿。spawn=false 路径下配置已 enabled 的主动识屏/DSH 监视器重启后不自启)属 v4.2.0 已存在的缺口,main 上已由 #100 / #102 修掉(PetWindow.__init__ 收尾 sync_optional_services(),见下方同名条目)。本次补上该修复缺的主动识屏侧回归(既有 #99 用例只钉了 agent_link 通道):新增 test_petwindow_proactive_enabled_at_startup_starts_watcher,并实测「把 __init__ 收尾改回 _install_effect_services() 即红」。window.py.base/.ours/.theirs、wp.base/.ours/.theirs(合计约 726 KB,非源码、v4.2.0 中不存在)提交进了 main,本次一并删除。0xc0000142(issue #111)ffmpeg-win-x86_64-v7.1.exe - 应用程序无法正常启动 (0xc0000142)」并阻塞关机流程;桌宠未运行时不弹。0xc0000142 = STATUS_DLL_INIT_FAILED。系统先发 WM_QUERYENDSESSION,随后拆除本次登录会话(窗口站 / 桌面堆 / CSRSS);本进程此前对该消息零感知,动画链照常运转并随时 CreateProcess 新的 ffmpeg 取帧进程(冷首帧预热 / reader 换代 / 元数据探测 / 圈末回收后 fresh spawn —— 日志里 reader 启停每几秒一次),新进程在已拆除的会话里 DLL 初始化失败,系统于是弹窗阻塞关机。pet/webm_clip.py 新增进程级「会话结束」闸门,四条 ffmpeg spawn 路径全部拒绝(start() 返回 False,复用既有「启动被拒」降级契约,调用方零改动;reader 线程 / 首帧解码 / 元数据探测 / exe 探测各自短路,并在 Popen 前二次复查收紧并发窗口);新增 pet/session_watcher.py 用应用级原生事件过滤器捕获 WM_QUERYENDSESSION / WM_ENDSESSION(恒返回 (False, 0),只观测、不 veto 关机),Qt 的 commitDataRequest / aboutToQuit 作次生兜底;会话结束时逐窗冻结(match_shutdown)、停素材库全部已建 clip(含圈末软停驻留的)、日志留痕(收到会话结束通知 / 会话结束:已停止全部 ffmpeg reader)。tests/test_session_end_ffmpeg_guard.py(25 例,含「未置位对照组照常 spawn」证明门是唯一差异、真实 ctypes MSG 投递、tokenize 级 spawn 清单静态护栏);窗口层 match_shutdown / _resume_activity 用例;真机实测向运行中的窗口投递 WM_QUERYENDSESSION 后闸门即时置位,报告者本机复测关机无弹窗;打包产物(onedir + setup.exe)内实跑同一路径复验。Popen 无法撤回、node/pnpm 等非 ffmpeg 派生未纳入)见 docs/ISSUE-111-WINDOWS-SESSION-END-FFMPEG-2026-09-12.md。paintEvent 分支没走特效旋转管线(_sync_mask 一直在转,画面与轮廓还错位)。squash 分支补上与 _sync_mask 一致的旋转路径,仅特效角非零时启用。780 本为贴地弹跳设计,却把空中低速段一起冻结。跟随阈值分档——触界维持 780,空中降为 150 继续跟随。elide/paginate 合并为同一换行助手)、末页孤行重平衡(3+1 → 2+2)、逐页字数自适应停留(page_dwell_ms)、翻页淡入淡出 + 圆点页码。PET_PERF_STATS + >100ms 帧空窗看门狗抓到 26 次卡顿,其中 19 次是动画切换命中冷首帧——GUI 线程同步拉 ffmpeg 解码(冷解码均值 100.7ms / 最差 1.4s,而 60Hz 单帧预算只有 16.7ms)。_roll_next(30/10/40/20 不变形)。GET /user/balance 可能返回多条 balance_infos(如 CNY + USD)且顺序不保证,旧实现直接取 infos[0];USD 排前面时读到 0.00,右键余额在 ¥0.00 与真实值之间来回跳,灵动岛余额档位动画跟着错。_pick_balance_info()——优先取有余额的一条,同分优先 CNY,全为 0 退回首条(保持原行为),非 dict / 非数值条目防御跳过;改动只有 pet/balance.py 一个函数 + 调用点一行,新增 5 条用例。¥ 符号写死」未动(不影响 CNY 账户)。#104:桥接插件归零外部依赖(事故级)
link: 安装,而 link: 不安装被链接包自身的依赖,链接目标又是打包版 _internal 副本(不带 node_modules)。#57 给桥接引入的 @deepseek-ai/dsh-llm 在打包副本上解析失败 → Cordis 插件树初始化整体抛错,dsh web / headless / desktop 全 profile 无法启动(多位用户实机复现)。index.js 手写 user-message envelope(与 dsh createUserMessage 键序/覆盖语义/深冻结逐项对拍),清空 dependencies、删除 pnpm-lock.yaml。build_onedir.ps1 从「必须声明依赖」改为「禁止声明任何依赖」(含 peer/optional);fix_bridge_bundle.py 新增不依赖 node 的 dist 清单零依赖校验;PR 门禁新增 7 个 bridge 契约测试 + verify_import.mjs hermetic 冒烟(无 node_modules 隔离目录 import、动态 import/require 一律禁止、临时目录祖先链污染负向对照)。#105:PR57 系统性收尾(先红后绿)
requiresApproval 嵌在 payload 内写盘、pet 门禁查顶层字段,判定恒 False,而测试用手写顶层形状掩盖了断裂);问题气泡在 mux 断线时关不掉(pet 侧升级重建把旧 call_id 覆盖成 None、bridge 侧帧不带 callId);5 个正常事件漏登记导致每次「终止/自动优化」都伴随「请更新/重装 bridge」假提醒;无 pnpm 时卸载假成功并留下 link: 残留。dsh_state 认桥接真实写的 llm_error(旧表键无生产者,ERROR 态永不可达),删死键。AgentEventRuntime 分发层与 Judge 机制等 20 项死信号/死键/孤儿方法(逐项 grep 零消费证据);设置页新增「行为重复检测」组(开关 + 9 阈值);检测器节流「记账后置」(被概率门抽稀的提醒不再占用 30s 节流槽)。node --test 7 文件全过 + hermetic 冒烟通过;新增/更新约 30 条用例全部先红后绿。新功能:事件汇报概率门(pet/report_gates.py)
activity=0.6、其余 1.0;旧开关/百分比自动迁移;右键菜单保留 0/1 两端快捷入口,细粒度概率走设置页滑块。事件链路语义对齐(Bridge → 传输 → 气泡渲染)
rate_limit 事件 → model_access(语义从「限流」扩展为「模型访问失败」:限流/过载/AI 服务错误);RateLimitTracker → ModelAccessTracker。errorText → errorMessage(tool/result 与 llm/retry 等统一);source → failureType(model_retry_exhausted / tool_failed)。event 字段(agent/status 别名 → 规范名),修掉模型访问失败连续计数丢失;占位符与文案键同步迁移({source}→{failureType}、{errorText}→{errorMessage}、rate_limit.*→model_access.*)。Persona 模板升级
entries 语义描述;预设键序排齐、占位符迁移;修掉自定义模式 {text} 字面量泄漏与双层预设取 global 层的问题。issue #95(DSH 联动装不上插件)
%NVM_HOME%\v*、%NVM_SYMLINK%、%PNPM_HOME%、%APPDATA%\npm、Volta/fnm/scoop/choco/bun/yarn、%ProgramFiles%\nodejs),POSIX 补 PNPM_HOME/$NVM_DIR/fnm/volta/asdf/linuxbrew;nvm 自定义根 ~/nvm(无点号)与 %APPDATA%\nvm 兜底。.cmd/.ps1 包装脚本里的真实 JS 入口、独立 pnpm.exe、DSH_PNPM_BIN/pnpm_bin 配置键);一键启动 DSH 的全局包根目录在 Windows 上补 nvm-windows 等目录。package.json.bak-* + pnpm 重试)、未知桥接事件不再误报「更新/重装 bridge」。其它修复
shiboken6.isValid());全量测试 access violation(全局 processEvents 冲刷 → 定向 DeferredDelete);桥接硬失败误报(恢复后未清零);连续点击动画不从头播放(_soft_parked 未清零);AppShell/共享子系统退出生命周期收口(解释器退出 access violation)。AgentLinkManager 懒创建、监视器真正启动靠 apply_config(),而工程里只有「展开 Agent 联动菜单」与「关闭设置对话框」两条路径会触发——重启后已开启的 Agent 联动(DSH/Claude/Cursor/OpenCode 与 custom_agents)与主动识屏开机不生效,必须手动点一次菜单或设置。PetWindow.__init__ 收尾改为 sync_optional_services()(其内部仍以 _install_effect_services() 收尾,净增 0 行),开机即按配置装配主动识屏 / Agent 联动 / 效果控制器;隐私红线不变(仍受 proactive_screen.enabled 与白名单/上限/冷却约束)。tests/test_feature_gating.py 两条用例(dsh 与 opencode 通道各一),断言管理器已创建且 monitor._running,并以 mgr.shutdown() 收尾。_try_move 仍会建立位移计划——桌宠挂着探头姿态被平移出屏幕边缘(自动掷骰与右键「移动」两条路径都会)。PetWindow._try_move 入口加 _effects_probe_active() 闸门,探头会话激活时直接返回 False(不建立位移计划);回归用例同时验证「退出探头后移动恢复可用」的正向对照。window.py 涨到 4385 行,行数预算按仓库惯例随实测校准(带日期注释)。完整清单(含全部小项与修复)见 docs/RELEASE-v4.2.0.md,GitHub Release 同步。
新功能
harness_autostart、启动链路静默化;macOS Node 解析。-threads 1 + 圈边界回收;预测式首帧预热;碰撞预测限频(CPU 61%→43%)。关键修复
WS_EX_TRANSPARENT)。ensurePolished(),女仆模式 30/111 条台词切字问题消失。AttributeError、dsh_control 每次请求必 TypeError、opencode 假完成、429 双提醒、联动气泡时钟域错误、检测器连环换弹。parent=None 的 manager 被循环 GC 在 worker 线程回收 → 腐化 Qt 事件队列(现 shutdown 过继 QApplication + 停定时器 + 控制 worker 可取消)。性能与内存
-threads 1,消灭每 10s 杀进程重启的 churn;圈边界按 ffmpeg_recycle_minutes(默认 10 分钟)定期回收。单进程多窗与共享解码
PetApp 拆为进程级 AppShell + 每窗 PetInstance;「单进程多开」转正为设置页正式开关(默认关闭,重启生效)。DecodeFanoutHub 替代旧 shm broker,3 窗 1 进程 1 解码器;旧 decode_broker.py(1464 行)退役删除。DSH_PET_SPAWN_FRESH 强制重播种,已有 slot 用户存档一律保留。交互稳定性
WS_EX_TRANSPARENT,不再 setWindowFlag 重建原生窗口,根治第二触发源。结构治理 / 代码健康
modern_settings_dialog.py 从 4811 行拆到 1857 行:控件库/菜单布局编辑器/AI 设置页/主题 QSS 四模块拆出;新增行数预算红线。settings_widgets.py 旧残壳、死 QSS、decode_broker.py 等),并新增孤儿簇守卫测试。decode_fanout 单向依赖、window.py 与 modern_settings_dialog.py 行数预算、窗口私有面冻结。AGENTS.md 新增 CI 成本纪律。验证
QSoundEffect 播放前显式 stop(),解决“首次点击有音效、后续点击无声”的问题(源码版/试听均验证)。<config_dir>/file_eaten_stats.json;不会真实删除、移动或修改文件。QSystemTrayIcon.showMessage 的原生气泡在托盘图标被系统收进“隐藏的图标”区域时可能不弹出(v4.1.0 评估后弃用该通道的直接原因);要真正进入 Windows 通知中心还需 AppUserModelID + 原生通知 API(WinRT),在 PyInstaller 冻结、多实例与绿色版/安装版并存下涉及打包身份与升级路径,验证成本高;.app,绿色/未签名分发下不可靠;Linux 桌面通知依赖各发行版的 DBus/notify 服务,实现不一;agent_link.custom_agents 配置驱动,声明 {key, name, path} 即可零代码接入任意符合统一事件协议的 JSONL Agent;新增 docs/AGENT_LINK_PROTOCOL.md 协议文档。bytesAvailable() 兜底读取,恢复 POSIX 测试覆盖。QLocalServer 的 Unix socket 文件残留,幸存者 listen() 报 AddressInUseError 导致重选死循环;submit_leave 成员表为空属于同批时序问题。处理:先探测活监听者、确认无人应答再清理残留并重试;补 bytesAvailable() 兜底读取;测试服务名缩短规避 macOS socket 路径长度上限(PR #46/#49)。test_rapid_start_stop_no_leaked_running_threads 在 Linux/macOS 偶发断言残留新出现的非预期线程(含 reader 线程)。处理:线程退出是异步的,断言前给 5s 宽限等待;若仍偶发可重跑定位是否负载相关。test_context_menu_transitions_smoothly_to_safe_target 原先固定 qWait(50) 采样动画中间位置,macOS offscreen 子进程定时器调度延迟时会采样到尚未推进的帧(middle.x == 40)。处理:改为轮询等待菜单位置首次变化(上限 2s)后再采样,并等待 duration + margin 验证最终位置;恢复严格下界断言(PR #53/#54)。lunar-python、winrt-Windows.*)后本机跑套件,test_menu_layout.py 一次红 20 条、test_architecture.py/test_config_schema.py 也有红,看着像合并把设置页改坏。根因是第三方库只在叶子模块导入(pet/festival_calendar.py 里的 lunar_python),而 ModernSettingsDialog.__init__ 在导入期就 import 设置页,于是所有构造设置页的用例都在 import 阶段炸;CI 会自动 pip install -r requirements.txt,所以各 PR 自己的 CI 都是绿的。判定:红的是同一族 + 报错是 ModuleNotFoundError + 在合并前的分支上对照组也红 ⇒ 环境问题,不是合并。修复:pip install -r requirements.txt。详见 docs/BUILD-CI-FAILURE-NOTES-2026-08.md 第 2.4 节。requirements.txt 是唯一权威清单)。workflow_dispatch 手动触发 CI 只测试+构建+上传 artifact,不创建/修改 Release,适合合并前/后验证。docs/BUILD-CI-FAILURE-NOTES-2026-08.md。click.wav 升级为完整音效包——内置默认 / 小黄鸭 / 自定义单文件 / 自定义文件夹随机播放;新增音量(0–100%)与试听;播放层统一 QtMultimedia(QSoundEffect 即时重启 + MP3/OGG 解码缓存 + 播放器池兜底),旧配置自动迁移。GetCursorInfo 轮询,系统光标持续隐藏 200ms 自动鼠标穿透、出现即恢复;绝不调用 ShowCursor 干扰其他程序。QSoundEffect 加载完成、点击动画首帧优先预热、音效延迟到下一事件循环,消除首次点击/快速连点的数百 ms 卡顿。DSH_HOME 并过滤 node_modules;补全 web_search/read_page 工具名映射,thinking 状态有独立气泡文案,并支持每个 Agent 自定义 thinking 文案。PYTHONUTF8=1,新增打包产物中文编码自检,杜绝菜单/气泡/动作名再次乱码。WA_TranslucentBackground 的逐像素半透明边缘。web --help 是否支持 --no-open(按命令缓存)——旧版 dsh(如 0.1.0-rc.3)没有该选项,强行传参会启动失败;不支持时不传,由 dsh 自己打开浏览器,桌宠不重复打开。integrations/(含 DSH 桥接插件),修复 macOS/Linux 上「启动 DeepSeek Harness → 一键安装桥接插件」因资源缺失而失败的问题;构建后增加断言检查,漏打包直接报错。--collect-all keyring,确保 Chat 版 API Key 走系统凭据存储可用。--uninstall-cleanup 清理脚本(卸载更快更直接);源码运行 python -m pet --uninstall-cleanup 仍可用。WS_CAPTION)——自动隐藏任务栏下的最大化窗口不再误触发隐藏;已最大化后按 F11 的窗口(应用清掉了标题栏)仍能正确命中隐藏。screenAdded 即时触发 + 5 秒轮询兜底),目标屏上线后自动恢复到保存位置(2 分钟超时放弃);等待期间不把临时落脚坐标/屏名写回配置(防止覆盖副屏保存位置);用户真正开始拖动或点「回到右下角」会立即撤销自动恢复。cordis.yml 的真实目录,过滤 node_modules 等包管理器/误操作残留,避免安装失败触发整体回滚;pet_opacity 配置脏值(手改配置文件出错等)不再导致启动崩溃。[看看屏幕] 前台窗口:… 记录 + 一条回复),可继续追问"你刚才看到什么了";聊天窗未打开时仅气泡显示、不写入。click.wav 放到数据目录 sounds/ 替换);修复关闭音效后仍出声的问题(设置保存后未同步到窗口实例)。/user/balance 接口(用当前 API Key),气泡显示"余额 ¥xx(充值 / 赠送)";30 秒缓存复用,重复查询秒回;桌宠设置可开启自动刷新(分钟级)。实现参考 MeteorNOX/DeepSeek-Balance-Whale-Widget。auto_hide_fullscreen,保存后重启会被重置为默认。已修复(白名单补齐);并修复高 DPI(125%/150% 缩放)下最大化窗口被误判为全屏而误隐藏的问题(物理像素与逻辑坐标统一换算)——现在只在真全屏(视频全屏/游戏/F11)时隐藏,最大化窗口不隐藏。SetWindowPos 原生重设置顶,并每 30 秒自检一次、检测到丢失自动恢复;点击或拖拽桌宠会把它带回置顶最前(不抢键盘焦点);开启鼠标穿透时气泡提示"无法通过点击唤回置顶"。show/activate 会被 AppKit 抑制。现统一延迟到菜单关闭后再呈现窗口(含「AI 对话」窗口),右键菜单与托盘菜单均生效。dsh/npx 的 shebang(/usr/bin/env node)在子进程环境里找不到 node。现启动子进程时注入增强 PATH(Homebrew / nvm / volta / bun / pnpm 等常见目录),并将就绪等待从 45s 放宽到 90s;无 npm 环境不再卡顿 15 秒。cacert.pem 打进发布包;AI 设置新增「跳过 SSL 证书验证」选项,用于本地网关 / 自签名证书 / 代理拦截场景(仅建议在可信环境关闭)。[NSWindow setLevel:] 仅在实际 cocoa 平台执行,非 cocoa(offscreen 测试等)环境不再有段错误风险。python scripts/convert_to_gif.py --force --clean 生成素材)。docs/INDEX.md:全文档入口索引——53 份文档按领域分组,每条一句话 + 何时必读;想给项目做东西先从这里找相关模块的文档。AGENTS.md:工程指南与 CI 成本纪律(PR #76 后硬性规矩)。docs/WINDOW_PY_SPLIT_GUIDE.md:window.py 演进指南、功能驱动拆分流程与架构红线说明。docs/HANDOVER_2026-09.md:2026-09 性能/结构线交付手册(含后续批次更新说明)。docs/ONEDIR_PACKAGING.md:onedir 构建、绿色版 zip 与 Inno Setup 安装包流水线。docs/BUILD_ARTIFACTS-2026-08-22.md:EXE 构建、大小、哈希和启动验证记录。本项目采用 MIT License(见仓库根目录 LICENSE),第三方素材与组件授权声明见 THIRD_PARTY_NOTICES.md。
特别感谢: