dsh-mac-pet 0.0.0-stage → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sundusk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,288 @@
1
- # Temporary Holding Version
1
+ <h1 align="center">DSH Pet</h1>
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <p align="center">
4
+ 以鲸鱼娘的像素形象,把 DeepSeek Harness 的 Agent 状态带到 macOS 桌面<br>
5
+ 随时查看任务、接收提醒、发送消息
6
+ </p>
7
+
8
+ <p align="center">
9
+ <img src="docs/assets/jingyuniang-preview.png" width="360" alt="鲸鱼娘桌宠:应用实际导出的透明背景待机预览">
10
+ </p>
11
+
12
+ <p align="center">
13
+ macOS 14+ · SwiftUI 原生应用 · 需要 DeepSeek Harness(DSH)运行
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="#-安装">⬇️ 安装 DSH Pet</a>
18
+ </p>
19
+
20
+ ## 这是什么?
21
+
22
+ **DSH Pet** 是 DeepSeek Harness 的 macOS 桌面伴侣。它默认以鲸鱼娘的形象显示 Agent 状态:
23
+ 待机、思考、等待授权或回答、完成和失败都有对应动作。你可以把桌宠拖到屏幕任意位置,
24
+ 通过状态气泡和任务卡片了解进度,也能直接从桌面向 Harness 发送消息;浏览器最小化后仍可使用。
25
+
26
+ 鲸鱼娘沿用原始造型与动作,支持状态切换、待机时双击挥手和左右拖拽奔跑。可在设置面板中调整形象、大小与交互方式。
27
+
28
+ ### 项目组成
29
+
30
+ - **DSH Pet.app**:以鲸鱼娘为默认形象的 macOS 桌宠应用,提供状态气泡、任务卡片与消息输入
31
+ - **DSH Pet 桥接插件**(包名 `dsh-mac-pet`):订阅 Agent 会话事件,提供 HTTP/状态 Socket 兼容接口、用户级命令 Socket,以及安装桌宠引导和桌宠会话跳转;无插件设置项
32
+
33
+ 旧名称和数据位置保留必要的兼容处理。安装脚本会清理旧版 `MoodBall.app`。
34
+ 当前源码对应的桌面 App 版本为 **v0.9.3**,桥接插件版本为 **v0.9.2**。已发布的 `DSH-Pet.app.zip` 安装包可从 [GitHub Releases](https://github.com/sundusk/dsh-pet/releases/latest) 下载;安装脚本也会自动获取最新发布的安装包。
35
+
36
+ 一切配置都在 app 的设置面板里完成。新版设置面板包含六个页面:「桌宠外观」「交互与提醒」「快捷键」「宠物管理」「Harness 连接」和「关于与更新」。
37
+
38
+ ### 鲸鱼娘动作
39
+
40
+ 鲸鱼娘会随任务状态切换动作,支持待机、思考、等待操作、完成、失败、挥手,以及左右拖拽奔跑。
41
+
42
+ <details>
43
+ <summary>查看鲸鱼娘原始动作图集</summary>
44
+
45
+ <p align="center">
46
+ <img src="Resources/Pet/Jingyuniang/JingyuniangSprites.png" width="768" alt="应用实际使用的鲸鱼娘动作图集,包含待机、奔跑、交互与任务状态动画">
47
+ </p>
48
+
49
+ </details>
50
+
51
+ ## 🚀 安装
52
+
53
+ ### 先运行 DeepSeek Harness
54
+
55
+ DSH Pet 需要 **macOS 14+** 和正在运行的 DeepSeek Harness。按[官方中文 README](https://github.com/deepseek-ai/deepseek-harness/blob/master/README.zh.md)选择一种 Web 启动方式:
56
+
57
+ 安装 Node.js 后,直接通过 npm 运行:
58
+
59
+ ```bash
60
+ npx @deepseek-ai/dsh web
61
+ ```
62
+
63
+ 或安装 pnpm,从官方源码构建并运行:
64
+
65
+ ```bash
66
+ git clone https://github.com/deepseek-ai/deepseek-harness.git
67
+ cd deepseek-harness
68
+ pnpm install
69
+ pnpm run build
70
+ pnpm dsh web
71
+ ```
72
+
73
+ 两种方式默认在 `http://127.0.0.1:3080` 打开 Web UI。已有 DeepSeek Harness 桌面版的用户也可以继续使用桌面版;其插件安装步骤见下文。加载新版 bridge 后,再从 DSH Pet 的「Harness 连接」页检测连接。
74
+
75
+ ### 安装 DSH Pet(推荐)
76
+
77
+ 如果已在 Harness 的「插件」页面安装并启用 `dsh-mac-pet`,未安装 App 或无法确认安装状态时,可从侧栏的「安装桌宠」进入下载和安装引导。安装 App 后,在 App 的「Harness 连接」页选择目标并检查连接;如果 Harness 提示需要重启,请等当前任务结束后再重启。
78
+
79
+ 也可以使用安装脚本。另开一个终端执行:
80
+
81
+ ```bash
82
+ git clone --depth 1 https://github.com/sundusk/dsh-pet.git
83
+ cd dsh-pet
84
+ bash install.sh
85
+ ```
86
+
87
+ 安装脚本会:
88
+
89
+ 1. 检测当前正在运行的 Harness,并识别桌面版、NPM/NPX 版或源码版
90
+ 2. 源码版在实际源码根目录执行 `pnpm dsh`;NPM/NPX 版执行对应的 `dsh` 或 `npx @deepseek-ai/dsh` CLI
91
+ 3. CLI 版在目标 Harness 的 `web` profile 中检测/安装状态插件;桌面版通过应用内「插件」页面安装
92
+ 4. 下载最新 Release 中的 `DSH Pet.app`;如果当前目录已有 `dist/DSH Pet.app`,则优先使用本地构建产物
93
+ 5. 优先安装到 `/Applications`,无权限时自动回退到 `~/Applications` 并启动;安装后只保留一个 `DSH Pet.app`,旧版 `MoodBall.app` 和重复构建副本会移入废纸篓
94
+
95
+ 安装脚本只需要执行一次。若插件刚安装而当前 Harness 正在运行,脚本仍会继续安装并启动
96
+ `DSH Pet.app`;请在当前任务完成后重启一次 DeepSeek Harness 让插件加载,**无需再次运行安装脚本**。
97
+ 如果 Harness 当前没有运行,DSH Pet 会先显示“未连接”,启动 Harness 后自动连接。
98
+
99
+ 安装器不会自动停止或重启正在运行的 Harness,也不会把状态接口暂时不可达直接判断为“插件未安装”。
100
+ 如果同时存在多个源码版 Harness 且当前都未运行,会要求选择目标;不会静默把插件装进错误的仓库。
101
+
102
+ 源码版也可以显式指定:
103
+
104
+ ```bash
105
+ DSH_SOURCE_ROOT="$HOME/Projects/deepseek-harness" bash install.sh
106
+ ```
107
+
108
+ 安装器会复用当前环境或已识别 Harness 的 `DSH_HOME`,并将最近使用的 Harness 类型、源码路径和 profile
109
+ 记录在 `~/Library/Application Support/DSH Pet/config.json`,供下一次安装使用;首次使用时也会读取旧位置的配置。
110
+
111
+ 如需自行构建,请先安装 Xcode 命令行工具,在本仓库运行 `DSH_PET_SKIP_OPEN=1 bash make-app.sh`,再运行 `bash install.sh`。
112
+
113
+ 桌面版使用独立的 `desktop` profile。请在桌面版「插件」页面搜索并启用 `dsh-mac-pet`;如 Harness 提示需要重启,待当前任务结束后再重启以加载插件。官方内置 CLI 需先完成初始化,并退出桌面版后才可管理该 `desktop` profile;NPM/NPX CLI 不能管理桌面版 profile。安装器检测到桌面版时不会把插件误装到 `web` profile。DSH Pet 使用本地 Socket 接收桌面版状态;桌面版晚启动或重启后会自动重连。
114
+
115
+ ### 手动安装桥接插件
116
+
117
+ 如果只安装了应用、需要单独补装插件,请根据 Harness 的启动方式执行对应命令,然后重启 Harness:
118
+
119
+ ```bash
120
+ # 通过 npx 运行 Harness
121
+ npx @deepseek-ai/dsh plugin --profile web add dsh-mac-pet
122
+
123
+ # 已安装全局 dsh 命令
124
+ dsh plugin --profile web add dsh-mac-pet
125
+
126
+ # 从源码运行 Harness:先进入 deepseek-harness 源码根目录
127
+ pnpm dsh plugin --profile web add dsh-mac-pet
128
+ ```
129
+
130
+ 桌面版仍请使用其「插件」页面;官方内置 CLI 只有在完成初始化并退出桌面版后,才可管理 `desktop` profile;NPM/NPX CLI 只针对 `web` profile。加载新版 bridge 后,再回到 DSH Pet 的「Harness 连接」页重新检测。
131
+
132
+ 如果已安装旧包 `@sundusk/dsh-moodball-status`,请在对应 Harness 中移除旧包,再按上面的方式安装新版并重启 Harness。桌面版请在「插件」页面操作;源码版或 CLI 版可使用相应的 `dsh plugin --profile web remove @sundusk/dsh-moodball-status` 命令。
133
+
134
+ ## ✨ 使用
135
+
136
+ 安装并启动后,**桌面上没有任何窗口**——它是个纯菜单栏应用(不占 Dock、不抢焦点):
137
+ 菜单栏右侧出现状态图标,桌面右下角出现鲸鱼娘(或你选择的其他皮肤)。
138
+
139
+ ### 以后怎么打开?
140
+
141
+ - **访达 → 应用程序(或 ~/Applications)**:找到「DSH Pet」,双击
142
+ - **终端**:`open -a "DSH Pet"`
143
+
144
+ ### 菜单栏图标功能
145
+
146
+ 菜单栏采用 [DeepSeek Harness 官方 FishLogo](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-primitives/src/FishLogo.tsx) 的单色轮廓,自动适配深浅背景。悬停可查看当前状态,展开菜单可查看状态文字与状态色点。素材来源与许可随 App 一同附在 `ThirdPartyNotices.txt` 中。
147
+
148
+ | 菜单项 | 功能 |
149
+ |---|---|
150
+ | 状态文字 | 当前连接状态、状态名与桥接方式(如「已连接 · 工具调用 · 本地桥接」) |
151
+ | 隐藏 / 显示宠物 | 隐藏或显示桌宠及其操作栏 |
152
+ | 设置… | 打开设置面板 |
153
+ | 退出 | 退出 app(不影响 DSH 本体) |
154
+
155
+ ### 鲸鱼娘交互
156
+
157
+ - **拖动**:按住鲸鱼娘拖动,她会朝拖动方向奔跑,并记住停留位置
158
+ - **双击**:待机时鲸鱼娘会挥手
159
+ - **锁定位置**(设置面板 → 交互与提醒)开启后不可拖拽,仍可双击
160
+
161
+ ### 第一阶段:快捷入口
162
+
163
+ - 默认按 **Option+Space**,可在「设置 → 快捷键」重新录入组合键或关闭。快捷键冲突时 DSH Pet 不会抢占其他应用,并会在设置面板提示。
164
+ - 「设置 → 快捷键」的“菜单快捷键”区域可分别修改或关闭输入消息、新建会话、选择工作区、打开 Harness、显示/隐藏宠物和打开设置的菜单快捷键;默认使用不冲突的 `⌘` 组合键。
165
+ - 快捷键会显示并聚焦输入框;输入框已打开时重复按下不会关闭、不清空草稿。按 Esc 收起输入框,中文输入法选字时 Enter 仍只确认候选文字。
166
+ - 菜单栏“隐藏/显示宠物”同时控制桌宠及其操作栏。点击穿透设为“永远点击穿透”时,仍可通过全局快捷键唤起输入框。
167
+
168
+ ### 向 Harness 发送消息
169
+
170
+ 宠物下方的短横条会在鼠标靠近时变成笔记按钮。点击后展开原生输入框:
171
+
172
+ - 先在「Harness 连接」页选择目标,再选择该 Harness 已注册的工作区;DSH Pet 会记住当前目标。
173
+ - Enter 发送,Shift+Enter 换行;中文输入法选字时 Enter 只确认候选文字。
174
+ - 第一条消息创建 DSH Pet 专用会话,后续消息沿用同一会话;查询文件或技能时也可能提前创建空会话,但不会启动模型。执行中、等待授权或等待回答时暂时不能继续发送。
175
+ - “新建会话”只解除当前绑定,不会删除或停止旧 Harness 任务;“打开 Harness”进入当前选定 Harness 的首页。
176
+ - 命令插件不可用时仍可显示旧版状态,但输入功能会明确禁用;草稿不会被静默丢弃。
177
+
178
+ ### 第二阶段:多任务卡片与提醒
179
+
180
+ - 待机时保留宠物下方的短横线;鼠标靠近后展开底部功能栏,从左到右分别是输入消息、当前任务和最近任务。
181
+
182
+ - 所有已注册工作区的普通 Harness 会话都会显示为任务卡片,卡片标注所属工作区;子 Agent 不单列。任务卡片不依赖输入框里选中的工作区,因此直接在 Harness 桌面版或网页版里开始的任务也会出现。多任务默认只显示排序第一项,箭头展开纵向列表,最多直接显示四张,超出部分在卡片区域内滚动。
183
+ - 状态气泡有两种模式:只有 1 个活动任务时保持原来的单气泡;2 个及以上任务时出现气泡卡组。第 1 页是总览(任务总数和最高优先级动作),之后每个任务一页,显示该任务当前的气泡动作(正在思考中、工具调用、等待授权、搞定啦、出错了等);右上角页码直接显示 1、2、3,点击气泡循环切换。任务标题、工作区和具体工具在任务面板中查看。气泡切换只改变宠物显示,不改变输入目标。
184
+ - 双击宠物(或任务气泡)会唤起对应的 Harness,并让客户端直接打开当前气泡的会话;停在总览或没有任务气泡时,仍按原行为打开 Harness。该跳转由桥接插件的无界面浏览器半区完成,需要更新并重新加载桥接插件后生效。
185
+ - 完成/失败/停止只播放 2.5–3 秒结果动画,然后宠物停止结果动作:单任务回到 idle 并隐藏气泡,多任务自动回总览。未读的完成/失败仍保留在任务面板;在任务面板读掉后从气泡卡组移除。
186
+ - 等待授权/回答出现时,多任务自动回到总览并显示等待动作,处理完留在总览。任务页按进入卡组的先后固定,新任务追加到末尾,不按实时优先级重排;最多 5 个任务页,更多任务只在任务面板显示。
187
+ - 排序优先级为等待操作、失败未读、完成未读、运行中、其他最近更新任务。点击卡片查看详情并标记已读;完成/失败任务右侧的状态图标可直接点击“知道了”,立即从当前关注和气泡卡组移除,并保留在最近任务历史中。“继续此会话”会切换到该任务所属工作区并切换输入目标,当前草稿不为空时会先阻止切换。
188
+ - 任务标题、状态图标和更新时间优先来自 Harness;标题服务不可用时使用工作目录和会话 ID 后缀回退。未读位置由 DSH Pet 本地保存,首次加载不会把历史任务批量标成未读,重连后会继续使用已读位置。
189
+ - 任务详情中的“打开此会话”会唤起对应 Harness 客户端并跳到该任务,不改变输入目标或草稿。该功能通过已加载桥接插件的客户端完成,不依赖外部深链;客户端未加载或不可用时会提示。
190
+ - 任务详情可显示最后一轮最终回复的简短预览;没有回复或当前 Harness 不支持时会说明,预览不是模型生成的摘要。
191
+
192
+ ### 截图附件
193
+
194
+ 当前版本的输入框未提供图片粘贴或截图附件入口;应用启动时也不会恢复旧版附件草稿。桥接协议仍保留图片附件能力,但不能从当前输入界面使用。
195
+
196
+ ### 文件和技能选择
197
+
198
+ - 在输入框中输入 `@` 可搜索当前会话工作目录的文件和目录,输入 `$` 可选择当前会话允许用户调用的技能。使用方向键选择、Enter 确认、Esc 关闭;也可点击候选项。
199
+ - 选择文件或目录会插入 Harness 识别的引用,不上传整个目录;不能用该格式表达的路径会提示。技能调用放在消息开头,每条消息可选择一个,与 Harness 命令同名的技能不可选择。
200
+ - 新会话查询时可能先创建空会话;切换会话或连接时,旧查询结果不会用于新目标。相应服务不可用或桥接插件版本较旧时会提示,已有文字草稿会保留。
201
+
202
+ ### 鲸鱼娘外观
203
+
204
+ 设置面板 →「桌宠外观」可调整宠物形象、大小、状态气泡与发光,修改即时生效。
205
+
206
+ 首次安装默认显示鲸鱼娘;之后会记住你选择的宠物。
207
+
208
+ 内置「鲸鱼娘Q版」,可在「桌宠外观」直接选择。素材来自 Codex 同名宠物包,保留原始透明图集与帧序:空闲用待机,思考与工具调用用专注思考,授权与抉择用等待用户,完成用跳跃庆祝,失败用低落动作;双击挥手、左右拖拽奔跑也沿用原动作,共使用 51 帧。审阅与 16 向视线帧保留在图集中,暂未接入交互。
209
+
210
+ 鲸鱼娘的悬浮窗口按精灵和光晕收紧;状态气泡出现时才向上扩展,精灵之外的透明区域不会触发展开操作栏。
211
+
212
+ ### 状态预览与导出
213
+
214
+ 「设置 → 桌宠外观 → 效果预览」可切换状态;点击“放大 / 导出…”可预览鲸鱼娘的全部状态,
215
+ 选择是否包含状态文字,并通过保存面板导出 512×512 透明背景 PNG。预览中的宠物和状态选择不会改变实际桌宠或任务。
216
+
217
+ ### 导入 Codex 宠物
218
+
219
+ 「设置 → 宠物管理」位于快捷键下方。点击“导入宠物”,选择包含 `pet.json` 的文件夹,预览后确认导入,再点击“使用”切换桌宠。已导入宠物也会出现在「桌宠外观」的选择列表中。
220
+
221
+ - 支持 Codex v2 本地宠物包:`spriteVersionNumber: 2`,图集为透明 PNG 或 WebP,尺寸为 1536×2288(8×11,每格 192×208),不超过 20 MiB。
222
+ - 可以从 `~/.codex/pets` 选择已有宠物目录;不需要运行 Codex。宠物的任务动作由当前 Harness 状态驱动。
223
+ - 导入只复制清单与图集到 `~/Library/Application Support/DSH Pet/Pets`,不会执行包内代码,也不会修改原始目录。重复图集不会覆盖已有宠物。
224
+ - 导入不会自动改变正在使用的桌宠;选择会自动保存。删除当前宠物会回退到鲸鱼娘,删除只影响 DSH Pet 副本。
225
+ - “刷新”重新读取 App 保存的副本;修改原始 Codex 宠物不会自动更新已导入副本。暂不支持 ZIP 或 v1 图集。
226
+
227
+ ### 状态气泡与发光的颜色
228
+
229
+ 状态颜色用于鲸鱼娘的提示气泡,以及开启“桌宠发光”后的光晕。
230
+
231
+ | 状态 | 默认颜色 |
232
+ |---|---|
233
+ | 空闲 | 蓝色 |
234
+ | 正在思考中 | 绿色 |
235
+ | 工具调用 | 紫色 |
236
+ | 等待你的授权 | 黄色 |
237
+ | 做出你的抉择 | 粉色 |
238
+ | 搞定啦 | 青色 |
239
+ | 出错了 | 红色 |
240
+ | 停止 / 中断 | 黑色 |
241
+ | 未连接 / 插件未装 | 灰色 |
242
+
243
+ **可在「设置 → 桌宠外观 → 状态配色」自定义颜色。**
244
+
245
+ ### 状态桥接与设置面板
246
+
247
+ 「Harness 连接」页会显示当前可发现的 Harness 实例,并统一选择状态与消息目标。只有一个实例时 DSH Pet 自动使用它;两个实例同时启动时,首次需要用户选择。目标确定后,另一个实例出现不会触发自动切换。
248
+
249
+ 新版 bridge 为每个运行实例提供同源的状态与命令通道,菜单、任务卡片和快捷键等打开入口都会跟随当前选定的 Harness。新版 bridge 必须先在目标 Harness 中加载,App 才能读取实例检测元信息;仅更新 DSH Pet App 不足以完成识别。
250
+
251
+ 旧版 bridge 没有来源身份时,DSH Pet 保留只读状态兼容,连接页显示「来源未知」,默认禁用发送并提示升级插件;旧版状态兼容不保证消息与状态来自同一 Harness。DSH Pet 不会启动、停止、升级或修改 Harness,也不模拟 Web UI。
252
+
253
+ 菜单栏 →「设置…」包含六个页面:「桌宠外观」「交互与提醒」「快捷键」「宠物管理」「Harness 连接」和「关于与更新」。可在其中调整桌宠、交互行为、快捷入口、当前 Harness 目标和版本信息,修改立即生效。
254
+
255
+ 「关于与更新」页只检查 GitHub Releases 是否有新版本,有则提供「前往下载」链接,不自动安装。
256
+
257
+ ### 卸载
258
+
259
+ ```bash
260
+ git clone --depth 1 https://github.com/sundusk/dsh-pet.git
261
+ cd dsh-pet
262
+ bash uninstall.sh
263
+ ```
264
+
265
+ 脚本会退出并移除 `DSH Pet.app` 与旧版 `MoodBall.app`(检查 `~/Applications` 和 `/Applications`),
266
+ 并询问是否同时移除 DSH Pet 桥接插件(移除后重启 dsh web 生效)。
267
+
268
+ ### 常见问题
269
+
270
+ - **鲸鱼娘显示未连接?** 先确认目标 Harness 正在运行,并确认新版 bridge 已在目标 Harness 中加载;仅更新 DSH Pet App 不足以完成实例检测。
271
+ - **「设置 → 插件」里怎么没有 DSH Pet 插件卡片?** 这是正常的——桥接插件没有任何设置项
272
+ (所有配置都在 app 的设置面板里),所以不显示配置卡片。可在「设置 → 插件 → **插件列表**」
273
+ 中查看它(状态为「已挂载」)。
274
+
275
+ ## 🔧 开发
276
+
277
+ ```bash
278
+ # DSH Pet 桥接插件:构建到 lib/
279
+ pnpm install
280
+ pnpm build
281
+
282
+ # app:构建 dist/DSH Pet.app(含鲸鱼娘图集)并启动
283
+ bash make-app.sh
284
+ ```
285
+
286
+ ## 📄 License
287
+
288
+ 本项目采用 [MIT License](LICENSE) 发布。
@@ -0,0 +1,13 @@
1
+ # dsh-pet bundle patch: inserts the host-only plugin row into the
2
+ # web profile roster. Applied as a profile bundle layer (the `dsh.bundle.patch`
3
+ # manifest field); install with
4
+ # `dsh plugin --profile web add dsh-mac-pet`.
5
+ #
6
+ # The row is a bare plugin by package name: the node half (exports ".") tracks
7
+ # agent activity and serves /api/dsh-pet/status plus the session-open queue.
8
+ # The package also declares a tiny browser half (exports "./client") that only
9
+ # forwards a queued Session id to uiWorkspace.openSession; it renders no UI and
10
+ # needs no settings namespace.
11
+ - insert:
12
+ - id: dsh-pet
13
+ name: 'dsh-mac-pet'
package/lib/client.js ADDED
@@ -0,0 +1,154 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-mac-pet",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ /**
7
+ * DSH Pet browser half: poll the host-side session-open queue and navigate the
8
+ * Harness web client to the Session selected by the desktop pet.
9
+ *
10
+ * This file is wrapped into the DSH client-module loader format by
11
+ * scripts/build-client.mjs. The installation panel uses the React instance
12
+ * shared by the Harness shell and the optional slots service.
13
+ */
14
+ const inject = ['uiWorkspace']
15
+
16
+ function apply(ctx) {
17
+ if (typeof fetch !== 'function' || typeof document === 'undefined') return
18
+
19
+ if (typeof ctx.inject === 'function') {
20
+ ctx.inject(['slots', 'layout'], scope => {
21
+ const React = require('react')
22
+ const h = React.createElement
23
+ const panelId = 'dsh-pet-install'
24
+ // The desktop shell spawns its Host locally and exclusively forwards this origin to it.
25
+ const localDesktop = window.location?.protocol === 'dsh-app:' && window.location?.hostname === 'app'
26
+ && typeof window.dshDesktopBoot?.ready === 'function'
27
+ let hostStatus = 'unknown'
28
+ let stopped = false
29
+ let requestId = 0
30
+ const listeners = new Set()
31
+ const subscribe = listener => { listeners.add(listener); return () => listeners.delete(listener) }
32
+ const refreshStatus = async () => {
33
+ const current = ++requestId
34
+ let next = 'unknown'
35
+ try {
36
+ const response = await fetch(new URL('api/dsh-pet/install-status', document.baseURI).href, { cache: 'no-store', headers: { accept: 'application/json' } })
37
+ const payload = response.ok ? await response.json() : null
38
+ if (['installed', 'not-found'].includes(payload?.status)) next = payload.status
39
+ } catch { /* Keep the installation entry available when detection fails. */ }
40
+ if (stopped || current !== requestId || next === hostStatus) return
41
+ hostStatus = next
42
+ for (const listener of listeners) listener()
43
+ }
44
+
45
+ scope.effect(() => {
46
+ void refreshStatus()
47
+ const timer = setInterval(() => { void refreshStatus() }, 15_000)
48
+ return () => { stopped = true; clearInterval(timer) }
49
+ }, 'dsh-pet: app installation detection')
50
+
51
+ function InstallIcon({ size }) {
52
+ return h('svg', { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.7, 'aria-hidden': true },
53
+ h('path', { d: 'M12 3v12m-4-4 4 4 4-4M5 17v3h14v-3', strokeLinecap: 'round', strokeLinejoin: 'round' }))
54
+ }
55
+
56
+ function InstallPage() {
57
+ const status = React.useSyncExternalStore(subscribe, () => hostStatus)
58
+ const hostLabel = status === 'installed' ? '已检测到 DSH Pet' : status === 'not-found' ? '未检测到 DSH Pet' : '无法确认'
59
+ const section = { marginTop: '28px' }
60
+ return h('main', { style: { height: '100%', overflow: 'auto', boxSizing: 'border-box', color: 'var(--dsw-alias-label-primary)', background: 'var(--dsw-alias-bg-base)' } },
61
+ h('div', { style: { maxWidth: '864px', margin: '0 auto', padding: 'calc(28px + var(--dsh-frame-top-clearance, 0px)) clamp(24px, 4vw, 48px) 48px', lineHeight: 1.65, fontSize: '14px' } },
62
+ h('h1', { style: { fontSize: '20px', lineHeight: '28px', fontWeight: 500, margin: 0 } }, '安装 DSH Pet 桌宠'),
63
+ h('p', null, '安装桌面应用后,桌宠可以留在桌面显示 Harness 的任务状态。'),
64
+ h('p', null, '系统要求:macOS 14 或更新版本,Apple 芯片 Mac。'),
65
+ h('a', { href: 'https://github.com/sundusk/dsh-pet/releases/latest/download/DSH-Pet.app.zip', target: '_blank', rel: 'noopener noreferrer', style: { display: 'inline-block', padding: '8px 16px', borderRadius: '8px', background: 'var(--dsw-alias-accent, #4d76ff)', color: '#fff', textDecoration: 'none' } }, '下载 DSH Pet'),
66
+ h('p', null, h('a', { href: 'https://github.com/sundusk/dsh-pet/releases/latest', target: '_blank', rel: 'noopener noreferrer' }, '下载失败?打开 GitHub Release 页面')),
67
+ h('section', { style: section },
68
+ h('h2', { style: { fontSize: '16px' } }, '安装步骤'),
69
+ h('ol', { style: { paddingLeft: '22px' } },
70
+ h('li', null, '下载并解压 DSH-Pet.app.zip。'),
71
+ h('li', null, '将 DSH Pet.app 拖入「应用程序」文件夹,然后打开。'),
72
+ h('li', null, '在桌宠的「设置」→「Harness 连接」中检查连接状态。'),
73
+ h('li', null, '如果 Harness 提示需要重启才能加载插件,请在当前任务完成后重启。')),
74
+ h('p', null, '首次打开时,如果 macOS 阻止运行,请根据系统提示在「系统设置」→「隐私与安全性」中处理。')),
75
+ h('section', { style: section },
76
+ h('h2', { style: { fontSize: '16px' } }, '安装状态'),
77
+ h('p', { role: 'status' }, `${localDesktop ? '本机' : 'Harness 所在设备'}:${hostLabel}。`),
78
+ localDesktop ? null : h('p', null, '无法确认你当前使用的设备是否就是 Harness 所在设备,请在本机检查 DSH Pet 是否已安装。'),
79
+ h('button', { type: 'button', onClick: () => { void refreshStatus() } }, '重新检测'))))
80
+ }
81
+
82
+ scope.slots.inject('main', () => scope.slots.register({ name: 'main', key: panelId }, InstallPage))
83
+ scope.slots.inject('sidebar.panellist', () => {
84
+ let unregister
85
+ const panelInfo = scope.layout?.panelInfo
86
+ const canTrackPanel = typeof panelInfo?.getSnapshot === 'function' && typeof panelInfo?.subscribe === 'function'
87
+ const update = () => {
88
+ const visible = !localDesktop || hostStatus !== 'installed' || !canTrackPanel || panelInfo.getSnapshot().activePanelId === panelId
89
+ if (visible && unregister === undefined) unregister = scope.slots.register({ name: 'sidebar.panellist', id: panelId, order: 90, label: '安装桌宠' }, InstallIcon)
90
+ else if (!visible && unregister !== undefined) { unregister(); unregister = undefined }
91
+ }
92
+ const unlistenStatus = subscribe(update)
93
+ const unlistenPanel = canTrackPanel ? panelInfo.subscribe(update) : () => {}
94
+ update()
95
+ return () => { unlistenStatus(); unlistenPanel(); unregister?.() }
96
+ })
97
+ })
98
+ }
99
+
100
+ let stopped = false
101
+ let timer
102
+
103
+ const endpoint = path => new URL(path, document.baseURI).href
104
+
105
+ const acknowledge = async requestId => {
106
+ await fetch(endpoint('api/dsh-pet/session-open/ack'), {
107
+ method: 'POST',
108
+ headers: { 'content-type': 'application/json' },
109
+ body: JSON.stringify({ requestId }),
110
+ cache: 'no-store',
111
+ })
112
+ }
113
+
114
+ const poll = async () => {
115
+ if (stopped) return
116
+ try {
117
+ const response = await fetch(endpoint('api/dsh-pet/session-open/pending'), {
118
+ cache: 'no-store',
119
+ headers: { accept: 'application/json' },
120
+ })
121
+ if (response.ok) {
122
+ const payload = await response.json()
123
+ const request = payload?.request
124
+ if (request !== null && typeof request === 'object' && typeof request.sessionId === 'string' && typeof request.requestId === 'string') {
125
+ try {
126
+ ctx.uiWorkspace.openSession(request.sessionId)
127
+ await acknowledge(request.requestId)
128
+ } catch (error) {
129
+ console.warn('[dsh-pet] unable to open the selected Harness Session:', error)
130
+ }
131
+ }
132
+ }
133
+ } catch {
134
+ // The host or its protocol carrier can be briefly unavailable while the
135
+ // desktop app starts or reloads; the next poll retries.
136
+ }
137
+ if (!stopped) timer = setTimeout(() => { void poll() }, 400)
138
+ }
139
+
140
+ ctx.effect(() => {
141
+ void poll()
142
+ return () => {
143
+ stopped = true
144
+ if (timer !== undefined) clearTimeout(timer)
145
+ }
146
+ }, 'dsh-pet: session open polling')
147
+ }
148
+
149
+ exports.inject = inject
150
+ exports.apply = apply
151
+
152
+ return module.exports;
153
+ },
154
+ });