dsh-agentic-pet 0.3.1 → 0.4.0
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/README.md +56 -50
- package/README.zh.md +56 -50
- package/carrier/lib/main.js +10 -6
- package/client/pet-settings.js +44 -10
- package/lib/index.js +60 -13
- package/lib/{service-Vj-lLpIv.js → service-D-b8UzM2.js} +619 -69
- package/lib/tools.js +1 -1
- package/lib/types/index.d.ts +2 -2
- package/lib/types/inner-voice.d.ts +14 -0
- package/lib/types/petdata.d.ts +44 -0
- package/lib/types/registry.d.ts +2 -1
- package/package.json +2 -10
- package/presets/pet-inner-voice.patch.yml +3 -3
- package/presets/pet.patch.yml +3 -3
package/README.md
CHANGED
|
@@ -1,50 +1,56 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "The dsh desktop pet: an agentic pet plugin that serves the pet renderer over the web composition, drives it from in-process dsh events, and answers approvals and questions from the desktop."
|
|
3
|
-
kind: "package-bundle"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# dsh-agentic-pet
|
|
7
|
-
|
|
8
|
-
English | [中文](README.zh.md)
|
|
9
|
-
|
|
10
|
-
## Summary
|
|
11
|
-
|
|
12
|
-
`dsh-agentic-pet` is a desktop-pet plugin for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) ecosystem: the pet lives on your desktop as a transparent always-on-top window, follows your agent sessions (idle / thinking / working / error), races the web GUI to answer approvals and user questions (first answer wins), collects a per-pet touch-affection ledger, and exposes expressive tools (`pet_say`, `pet_do`, `pet_digest`, `pet_interactions`, `pet_state`) so the model can speak and act through the pet. Multiple pet implementations can run side by side — a directory registry (`~/.dsh/pet-http.d/`) discovers every live pet and routes state, interactions, and ledgers per pet.
|
|
13
|
-
|
|
14
|
-
The pet itself is a dumb display with zero dsh knowledge. Everything that understands the runtime — event convergence, interaction waterfalls, multi-pet routing, the model tools — lives in the plugin's driver, and the pet only implements a small local HTTP control interface. Any pet implementation that speaks the same HTTP contract can be adopted without changing this plugin.
|
|
15
|
-
|
|
16
|
-
## What's in this repo
|
|
17
|
-
|
|
18
|
-
| Path | What it is |
|
|
19
|
-
|---|---|
|
|
20
|
-
| `src/` | The plugin (Cordis bundle): `/pet` route, event driver, registry, `PetService` seam, model tools, settings-page routes |
|
|
21
|
-
| `client/` | The desktop sidebar settings page (client half) |
|
|
22
|
-
| `carrier/` | The Electron carrier **build artifacts** shipped with the package (`lib/`, `dist-renderer/`) |
|
|
23
|
-
| `carrier-src/` | The carrier **sources** (mirrored from the dev upstream; media assets live in [dsh-pet-assets](https://github.com/beyondgjc/dsh-pet-assets)) |
|
|
24
|
-
| `vendor/typert-plugin/` | Vendored typert decorator plugin used by the packaging config |
|
|
25
|
-
| `tests/` | Vitest specs (pure-function coverage of the registry routing and the `/status` field-matrix normalization) |
|
|
26
|
-
|
|
27
|
-
## Install
|
|
28
|
-
|
|
29
|
-
From the DeepSeek Harness desktop app's plugin page, or:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
dsh plugin --profile <name> add dsh-agentic-pet
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
The animation media ship as a separate package, `dsh-agentic-pet-assets`, and are pulled in automatically as a dependency.
|
|
36
|
-
|
|
37
|
-
## Development
|
|
38
|
-
|
|
39
|
-
The TypeScript build still runs inside the dsh monorepo workspace (the bundled `@deepseek-ai/dsh-*` deps are not published to npm). The flow:
|
|
40
|
-
|
|
41
|
-
1. In the monorepo: `pnpm run build` (plugin `lib/`) and `pnpm --filter @deepseek-ai/dsh-pet run build` (carrier `lib/` + `dist-renderer/`).
|
|
42
|
-
2. Here: `scripts\sync-from-monorepo.cmd` — mirrors sources and build outputs into this repo.
|
|
43
|
-
3. Commit here, then `npm publish` (assets package first; see [dsh-pet-assets/PUBLISH.md](../dsh-pet-assets/PUBLISH.md) for the release manual).
|
|
44
|
-
|
|
45
|
-
Tests: `npx vitest run tests/registry-service.spec.ts` runs self-contained (needs only `cordis` and `vitest`); the full suite including `tests/pet-app.spec.ts` runs in the monorepo workspace.
|
|
46
|
-
|
|
47
|
-
## Documentation
|
|
48
|
-
|
|
49
|
-
- Control-plane contract (the frozen consumer API every pet implementation speaks): see the multi-pet guide in the project documentation.
|
|
50
|
-
- Known limitations and design notes live in the repo wiki and the project's engineering docs.
|
|
1
|
+
---
|
|
2
|
+
description: "The dsh desktop pet: an agentic pet plugin that serves the pet renderer over the web composition, drives it from in-process dsh events, and answers approvals and questions from the desktop."
|
|
3
|
+
kind: "package-bundle"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dsh-agentic-pet
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
`dsh-agentic-pet` is a desktop-pet plugin for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) ecosystem: the pet lives on your desktop as a transparent always-on-top window, follows your agent sessions (idle / thinking / working / error), races the web GUI to answer approvals and user questions (first answer wins), collects a per-pet touch-affection ledger, and exposes expressive tools (`pet_say`, `pet_do`, `pet_digest`, `pet_interactions`, `pet_state`) so the model can speak and act through the pet. Multiple pet implementations can run side by side — a directory registry (`~/.dsh/pet-http.d/`) discovers every live pet and routes state, interactions, and ledgers per pet.
|
|
13
|
+
|
|
14
|
+
The pet itself is a dumb display with zero dsh knowledge. Everything that understands the runtime — event convergence, interaction waterfalls, multi-pet routing, the model tools — lives in the plugin's driver, and the pet only implements a small local HTTP control interface. Any pet implementation that speaks the same HTTP contract can be adopted without changing this plugin.
|
|
15
|
+
|
|
16
|
+
## What's in this repo
|
|
17
|
+
|
|
18
|
+
| Path | What it is |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `src/` | The plugin (Cordis bundle): `/pet` route, event driver, registry, `PetService` seam, model tools, settings-page routes |
|
|
21
|
+
| `client/` | The desktop sidebar settings page (client half) |
|
|
22
|
+
| `carrier/` | The Electron carrier **build artifacts** shipped with the package (`lib/`, `dist-renderer/`) |
|
|
23
|
+
| `carrier-src/` | The carrier **sources** (mirrored from the dev upstream; media assets live in [dsh-pet-assets](https://github.com/beyondgjc/dsh-pet-assets)) |
|
|
24
|
+
| `vendor/typert-plugin/` | Vendored typert decorator plugin used by the packaging config |
|
|
25
|
+
| `tests/` | Vitest specs (pure-function coverage of the registry routing and the `/status` field-matrix normalization) |
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
From the DeepSeek Harness desktop app's plugin page, or:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
dsh plugin --profile <name> add dsh-agentic-pet
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The animation media ship as a separate package, `dsh-agentic-pet-assets`, and are pulled in automatically as a dependency.
|
|
36
|
+
|
|
37
|
+
## Development
|
|
38
|
+
|
|
39
|
+
The TypeScript build still runs inside the dsh monorepo workspace (the bundled `@deepseek-ai/dsh-*` deps are not published to npm). The flow:
|
|
40
|
+
|
|
41
|
+
1. In the monorepo: `pnpm run build` (plugin `lib/`) and `pnpm --filter @deepseek-ai/dsh-pet run build` (carrier `lib/` + `dist-renderer/`).
|
|
42
|
+
2. Here: `scripts\sync-from-monorepo.cmd` — mirrors sources and build outputs into this repo.
|
|
43
|
+
3. Commit here, then `npm publish` (assets package first; see [dsh-pet-assets/PUBLISH.md](../dsh-pet-assets/PUBLISH.md) for the release manual).
|
|
44
|
+
|
|
45
|
+
Tests: `npx vitest run tests/registry-service.spec.ts` runs self-contained (needs only `cordis` and `vitest`); the full suite including `tests/pet-app.spec.ts` runs in the monorepo workspace.
|
|
46
|
+
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
- Control-plane contract (the frozen consumer API every pet implementation speaks): see the multi-pet guide in the project documentation.
|
|
50
|
+
- Known limitations and design notes live in the repo wiki and the project's engineering docs.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## Credits & Licensing
|
|
54
|
+
|
|
55
|
+
- This project is a derivative of [PC2005-cloud/dsh-pet](https://github.com/PC2005-cloud/dsh-pet) — the upstream dsh-pet plugin by PC2005-cloud. Plugin code is licensed MIT.
|
|
56
|
+
- The character artwork (the "大肥鱼" whale-girl character under `carrier/assets` and `dsh-agentic-pet-assets`) is licensed **CC BY-NC-SA 4.0** (attribution · non-commercial · share-alike) by its original author — [original statement](https://www.bilibili.com/opus/1247156596114456581). Non-commercial use only; artwork derivatives must be shared under the same license.
|
package/README.zh.md
CHANGED
|
@@ -1,50 +1,56 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "dsh 桌面宠物:经由 web 组合层伺服宠物页面、由进程内 dsh 事件驱动、可从桌面应答审批与提问的 agentic 宠物插件。"
|
|
3
|
-
kind: "package-bundle"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# dsh-agentic-pet
|
|
7
|
-
|
|
8
|
-
[English](README.md) | 中文
|
|
9
|
-
|
|
10
|
-
## 简介
|
|
11
|
-
|
|
12
|
-
`dsh-agentic-pet` 是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 生态的桌面宠物插件:宠物以透明置顶窗常驻桌面,跟随 agent 会话状态(idle / thinking / working / error),与 web 界面竞速应答审批与提问(先答者赢),维护每宠独立的触摸好感账本,并向模型暴露表达工具(`pet_say`、`pet_do`、`pet_digest`、`pet_interactions`、`pet_state`)——模型可以借宠物说话、做动作、感知被触摸。多只宠物实现可以并存:目录注册表(`~/.dsh/pet-http.d/`)发现全部在线宠物,状态、交互、账本按宠路由。
|
|
13
|
-
|
|
14
|
-
宠物本身是零 DSH 知识的纯显示端。理解运行时的一切——事件收敛、交互瀑布、多宠路由、模型工具——都在插件的 driver 里,宠物只实现一个小型本地 HTTP 控制接口。任何讲同一 HTTP 契约的宠物实现都能被采纳,插件零改动。
|
|
15
|
-
|
|
16
|
-
## 仓库结构
|
|
17
|
-
|
|
18
|
-
| 路径 | 内容 |
|
|
19
|
-
|---|---|
|
|
20
|
-
| `src/` | 插件(Cordis bundle):`/pet` 路由、事件 driver、注册表、`PetService` 接缝、模型工具、设置页路由 |
|
|
21
|
-
| `client/` | 桌面侧边栏设置页(client 半边) |
|
|
22
|
-
| `carrier/` | 随包分发的 Electron 载体**构建产物**(`lib/`、`dist-renderer/`) |
|
|
23
|
-
| `carrier-src/` | 载体**源码**(自开发上游镜像;素材在 [dsh-pet-assets](https://github.com/beyondgjc/dsh-pet-assets)) |
|
|
24
|
-
| `vendor/typert-plugin/` | 打包配置使用的 typert 装饰器插件(vendored) |
|
|
25
|
-
| `tests/` | Vitest 用例(注册表路由与 `/status` 字段矩阵的纯函数覆盖) |
|
|
26
|
-
|
|
27
|
-
## 安装
|
|
28
|
-
|
|
29
|
-
在 DeepSeek Harness 桌面端插件页安装,或:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
dsh plugin --profile <name> add dsh-agentic-pet
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
动画素材以独立包 `dsh-agentic-pet-assets` 分发,作为依赖自动带入。
|
|
36
|
-
|
|
37
|
-
## 开发
|
|
38
|
-
|
|
39
|
-
TypeScript 构建仍在 dsh monorepo workspace 内进行(被打包的 `@deepseek-ai/dsh-*` 依赖未发布 npm)。流程:
|
|
40
|
-
|
|
41
|
-
1. monorepo 内:`pnpm run build`(插件 `lib/`)与 `pnpm --filter @deepseek-ai/dsh-pet run build`(载体 `lib/` + `dist-renderer/`)。
|
|
42
|
-
2. 本仓库:`scripts\sync-from-monorepo.cmd`——把源码与构建产物镜像进本仓库。
|
|
43
|
-
3. 本仓库提交,然后 `npm publish`(先素材包后插件包;发布手册见 [dsh-pet-assets/PUBLISH.md](../dsh-pet-assets/PUBLISH.md))。
|
|
44
|
-
|
|
45
|
-
测试:`npx vitest run tests/registry-service.spec.ts` 自持可跑(只需 `cordis` 与 `vitest`);含 `tests/pet-app.spec.ts` 的全量测试在 monorepo workspace 内跑。
|
|
46
|
-
|
|
47
|
-
## 文档
|
|
48
|
-
|
|
49
|
-
- 控制面契约(所有宠物实现共同讲述的冻结消费方 API)见项目文档的多宠指南。
|
|
50
|
-
- 已知边界与设计笔记见仓库 wiki 与项目工程文档。
|
|
1
|
+
---
|
|
2
|
+
description: "dsh 桌面宠物:经由 web 组合层伺服宠物页面、由进程内 dsh 事件驱动、可从桌面应答审批与提问的 agentic 宠物插件。"
|
|
3
|
+
kind: "package-bundle"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dsh-agentic-pet
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 简介
|
|
11
|
+
|
|
12
|
+
`dsh-agentic-pet` 是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 生态的桌面宠物插件:宠物以透明置顶窗常驻桌面,跟随 agent 会话状态(idle / thinking / working / error),与 web 界面竞速应答审批与提问(先答者赢),维护每宠独立的触摸好感账本,并向模型暴露表达工具(`pet_say`、`pet_do`、`pet_digest`、`pet_interactions`、`pet_state`)——模型可以借宠物说话、做动作、感知被触摸。多只宠物实现可以并存:目录注册表(`~/.dsh/pet-http.d/`)发现全部在线宠物,状态、交互、账本按宠路由。
|
|
13
|
+
|
|
14
|
+
宠物本身是零 DSH 知识的纯显示端。理解运行时的一切——事件收敛、交互瀑布、多宠路由、模型工具——都在插件的 driver 里,宠物只实现一个小型本地 HTTP 控制接口。任何讲同一 HTTP 契约的宠物实现都能被采纳,插件零改动。
|
|
15
|
+
|
|
16
|
+
## 仓库结构
|
|
17
|
+
|
|
18
|
+
| 路径 | 内容 |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `src/` | 插件(Cordis bundle):`/pet` 路由、事件 driver、注册表、`PetService` 接缝、模型工具、设置页路由 |
|
|
21
|
+
| `client/` | 桌面侧边栏设置页(client 半边) |
|
|
22
|
+
| `carrier/` | 随包分发的 Electron 载体**构建产物**(`lib/`、`dist-renderer/`) |
|
|
23
|
+
| `carrier-src/` | 载体**源码**(自开发上游镜像;素材在 [dsh-pet-assets](https://github.com/beyondgjc/dsh-pet-assets)) |
|
|
24
|
+
| `vendor/typert-plugin/` | 打包配置使用的 typert 装饰器插件(vendored) |
|
|
25
|
+
| `tests/` | Vitest 用例(注册表路由与 `/status` 字段矩阵的纯函数覆盖) |
|
|
26
|
+
|
|
27
|
+
## 安装
|
|
28
|
+
|
|
29
|
+
在 DeepSeek Harness 桌面端插件页安装,或:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
dsh plugin --profile <name> add dsh-agentic-pet
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
动画素材以独立包 `dsh-agentic-pet-assets` 分发,作为依赖自动带入。
|
|
36
|
+
|
|
37
|
+
## 开发
|
|
38
|
+
|
|
39
|
+
TypeScript 构建仍在 dsh monorepo workspace 内进行(被打包的 `@deepseek-ai/dsh-*` 依赖未发布 npm)。流程:
|
|
40
|
+
|
|
41
|
+
1. monorepo 内:`pnpm run build`(插件 `lib/`)与 `pnpm --filter @deepseek-ai/dsh-pet run build`(载体 `lib/` + `dist-renderer/`)。
|
|
42
|
+
2. 本仓库:`scripts\sync-from-monorepo.cmd`——把源码与构建产物镜像进本仓库。
|
|
43
|
+
3. 本仓库提交,然后 `npm publish`(先素材包后插件包;发布手册见 [dsh-pet-assets/PUBLISH.md](../dsh-pet-assets/PUBLISH.md))。
|
|
44
|
+
|
|
45
|
+
测试:`npx vitest run tests/registry-service.spec.ts` 自持可跑(只需 `cordis` 与 `vitest`);含 `tests/pet-app.spec.ts` 的全量测试在 monorepo workspace 内跑。
|
|
46
|
+
|
|
47
|
+
## 文档
|
|
48
|
+
|
|
49
|
+
- 控制面契约(所有宠物实现共同讲述的冻结消费方 API)见项目文档的多宠指南。
|
|
50
|
+
- 已知边界与设计笔记见仓库 wiki 与项目工程文档。
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## 致谢与授权
|
|
54
|
+
|
|
55
|
+
- 本项目基于 [PC2005-cloud/dsh-pet](https://github.com/PC2005-cloud/dsh-pet) 衍生(上游 dsh-pet 插件,作者 PC2005-cloud)。插件代码以 MIT 许可发布。
|
|
56
|
+
- 角色形象("大肥鱼"鲸鱼娘,见 `carrier/assets` 与 `dsh-agentic-pet-assets`)的形象授权为 **CC BY-NC-SA 4.0**(署名 · 非商业 · 相同方式共享),[原作者声明](https://www.bilibili.com/opus/1247156596114456581)。仅限非商业使用;形象衍生须以相同方式共享。
|
package/carrier/lib/main.js
CHANGED
|
@@ -287,7 +287,7 @@ function spawnHostChild() {
|
|
|
287
287
|
* The pet-surface locator: pet-app bundles inside a running runtime publish
|
|
288
288
|
* their authenticated root here (the desktop shell keeps its URL in process
|
|
289
289
|
* memory only). A published URL wins over spawning a dedicated profile, so the
|
|
290
|
-
* pet follows whichever live runtime serves /
|
|
290
|
+
* pet follows whichever live runtime serves /whale598/ — the user's desktop when it
|
|
291
291
|
* runs, otherwise the last publisher still alive.
|
|
292
292
|
*/
|
|
293
293
|
async function readLocatorUrl() {
|
|
@@ -311,14 +311,16 @@ async function readLocatorUrl() {
|
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
313
|
/**
|
|
314
|
-
* The pet surface URL for an authenticated web root: path `/
|
|
314
|
+
* The pet surface URL for an authenticated web root: path `/whale598/` with the
|
|
315
315
|
* token **dropped** — authorizeIndex mints the root cookie from the token and
|
|
316
316
|
* rejects any token-carrying request on a subpath, so the pet page itself is
|
|
317
317
|
* entered cookie-only after the root mint.
|
|
318
318
|
*/
|
|
319
319
|
function petSurfaceUrl(authenticated) {
|
|
320
320
|
const parsed = new URL(authenticated);
|
|
321
|
-
|
|
321
|
+
// MUST stay in lockstep with the plugin's PET_PATH_PREFIX (/whale598) —
|
|
322
|
+
// separate builds, so the literal is duplicated here by design.
|
|
323
|
+
parsed.pathname = '/whale598/';
|
|
322
324
|
parsed.search = '';
|
|
323
325
|
parsed.hash = '';
|
|
324
326
|
return parsed.toString();
|
|
@@ -465,7 +467,7 @@ async function main() {
|
|
|
465
467
|
});
|
|
466
468
|
// Connection's token exchange is root-path-only (authorizeIndex admits
|
|
467
469
|
// `/?token=…`, never a subpath): load the authenticated root once to mint
|
|
468
|
-
// the browser cookie, then enter /
|
|
470
|
+
// the browser cookie, then enter /whale598/ with it. The root load starts the
|
|
469
471
|
// Web GUI; the second navigation aborts it, so the web app is at most a
|
|
470
472
|
// flash.
|
|
471
473
|
await window.loadURL(authenticated).catch(() => { });
|
|
@@ -979,7 +981,9 @@ async function main() {
|
|
|
979
981
|
// swap blindness). The legacy single file keeps being written during the
|
|
980
982
|
// transition so pre-registry readers still see this carrier.
|
|
981
983
|
const manifest = `${JSON.stringify({ port, pid: process.pid, since: Date.now(), pet: 'electron' })}\n`;
|
|
982
|
-
|
|
984
|
+
// Namespaced under whale598/ (collision avoidance); the driver scans
|
|
985
|
+
// the legacy pet-http.d/ as a read fallback for older pet builds.
|
|
986
|
+
const registryDir = join(DSH_HOME, 'whale598', 'pet-http.d');
|
|
983
987
|
try {
|
|
984
988
|
mkdirSync(registryDir, { recursive: true });
|
|
985
989
|
}
|
|
@@ -1021,7 +1025,7 @@ async function main() {
|
|
|
1021
1025
|
// find a dead port in pet-http.d/ — stale entries were the "旧端点残留"
|
|
1022
1026
|
// incident class. Best effort: a killed carrier leaves the file, and the
|
|
1023
1027
|
// readers' pid+probe validation is the backstop.
|
|
1024
|
-
for (const file of ['pet-http.d/electron.json', 'pet-http-electron.json']) {
|
|
1028
|
+
for (const file of ['whale598/pet-http.d/electron.json', 'pet-http.d/electron.json', 'pet-http-electron.json']) {
|
|
1025
1029
|
void rm(join(DSH_HOME, file), { force: true }).catch(() => { });
|
|
1026
1030
|
}
|
|
1027
1031
|
if (handle === undefined)
|
package/client/pet-settings.js
CHANGED
|
@@ -96,6 +96,34 @@ window.__ModuleLoader__.load({
|
|
|
96
96
|
var pets = (data && data.pets) || []
|
|
97
97
|
if (!(pets && pets.length) && data && data.pet) pets = [Object.assign({ id: 'electron' }, data.pet)]
|
|
98
98
|
var moodOf = function (a) { return a >= 80 ? '开心' : a >= 40 ? '满足' : '平静' }
|
|
99
|
+
// 显示名:用户自定义(settings.names)优先,缺省回落内置名;
|
|
100
|
+
// 宠物区改名后好感度区自动跟随(同一状态、同一渲染趟)。
|
|
101
|
+
var defaultName = function (id) { return id === 'electron' ? '鲸鱼娘' : id === 'pure' ? 'Pure 宠物' : id }
|
|
102
|
+
var BOND_TIERS = [[0, "陌生"], [5, "认识"], [15, "熟了"], [40, "亲近"], [90, "离不开"], [180, "只有你"]]
|
|
103
|
+
var bondTier = function (days) {
|
|
104
|
+
var name = "陌生"
|
|
105
|
+
BOND_TIERS.forEach(function (t) { if (days >= t[0]) name = t[1] })
|
|
106
|
+
return name
|
|
107
|
+
}
|
|
108
|
+
var petName = function (id) {
|
|
109
|
+
var custom = settings.names && settings.names[id]
|
|
110
|
+
return custom ? String(custom) : defaultName(id)
|
|
111
|
+
}
|
|
112
|
+
// 提交一次改名(失焦/回车调用):与当前值比较,无变化零开销;
|
|
113
|
+
// 空值删键(回落默认)。一次编辑 = 至多一次 update = 至多一条 [改名]。
|
|
114
|
+
var commitName = function (id, raw) {
|
|
115
|
+
var v = (raw || '').trim()
|
|
116
|
+
var next = Object.assign({}, (settings.names || {}))
|
|
117
|
+
var current = next[id]
|
|
118
|
+
if (v !== '' && v !== defaultName(id)) {
|
|
119
|
+
if (current === v) return
|
|
120
|
+
next[id] = v
|
|
121
|
+
} else {
|
|
122
|
+
if (current === undefined) return
|
|
123
|
+
delete next[id]
|
|
124
|
+
}
|
|
125
|
+
update({ names: next })
|
|
126
|
+
}
|
|
99
127
|
|
|
100
128
|
function update(patch, doneMessage) { act(UPDATE, { patch: patch }, doneMessage) }
|
|
101
129
|
|
|
@@ -111,9 +139,13 @@ window.__ModuleLoader__.load({
|
|
|
111
139
|
h(Section, { title: '宠物' },
|
|
112
140
|
pets.length
|
|
113
141
|
? pets.map(function (p) {
|
|
114
|
-
var name = p.id === 'electron' ? 'TS 宠物' : p.id === 'pure' ? 'Pure 宠物' : String(p.id)
|
|
115
142
|
return h('div', { key: p.id, style: row },
|
|
116
|
-
|
|
143
|
+
// Uncontrolled + commit-on-blur/Enter: typing is LOCAL until the
|
|
144
|
+
// edit completes — a per-keystroke update would fire a [改名]
|
|
145
|
+
// turn per character.清空 + 失焦 = 恢复默认名。
|
|
146
|
+
h('input', { style: Object.assign({}, inputStyle, { width: 120, fontWeight: 700 }), defaultValue: petName(p.id), title: '改名后点击空白处或回车生效;清空恢复默认',
|
|
147
|
+
onBlur: function (e) { commitName(p.id, e.target.value) },
|
|
148
|
+
onKeyDown: function (e) { if (e.key === 'Enter') { commitName(p.id, e.target.value); e.target.blur() } } }),
|
|
117
149
|
p.online
|
|
118
150
|
? h('span', null, '在线',
|
|
119
151
|
h('span', { style: muted }, 'pid ' + String(p.pid ?? '?')),
|
|
@@ -121,7 +153,7 @@ window.__ModuleLoader__.load({
|
|
|
121
153
|
p.animation ? h('span', { style: muted }, String(p.animation)) : null)
|
|
122
154
|
: h('span', { style: muted }, '离线'))
|
|
123
155
|
})
|
|
124
|
-
: h('div', null, '没有在线的宠物', h(Hint, null, '启动 DeepSeek Harness
|
|
156
|
+
: h('div', null, '没有在线的宠物', h(Hint, null, '启动 DeepSeek Harness 后鲸鱼娘会自动上桌;Pure 宠物需自行启动。'))),
|
|
125
157
|
|
|
126
158
|
// ── 好感度(每宠独立账本) ──
|
|
127
159
|
h(Section, { title: '好感度' },
|
|
@@ -133,18 +165,20 @@ window.__ModuleLoader__.load({
|
|
|
133
165
|
if (!ids.length) return h('div', { style: muted }, '还没有互动记录')
|
|
134
166
|
return ids.map(function (id) {
|
|
135
167
|
var book = (affection && affection[id]) || { affection: 0, total: 0 }
|
|
136
|
-
var name = id
|
|
168
|
+
var name = petName(id)
|
|
137
169
|
return h('div', { key: id, style: row },
|
|
138
170
|
h('span', { style: { fontWeight: 700 } }, name),
|
|
139
171
|
h('span', { style: { fontSize: 18, fontWeight: 700 } }, String(book.affection ?? 0)),
|
|
140
|
-
h('span', { style: muted }, '/ 100 · ' + moodOf(book.affection ?? 0)),
|
|
172
|
+
h('span', { style: muted }, '/ 100 · ' + moodOf(book.affection ?? 0) + ' · 羁绊:' + bondTier(book.bond ? (book.bond.value ?? book.bond.days ?? 0) : 0) + '(' + String(book.bond ? (book.bond.value ?? book.bond.days ?? 0) : 0) + ' 天)'),
|
|
141
173
|
h('span', { style: muted }, '累计 ' + String(book.total ?? 0) + ' 次'))
|
|
142
174
|
})
|
|
143
175
|
})(),
|
|
144
176
|
h('div', { style: row }, h(SButton, { variant: 'danger', disabled: busy, onClick: function () {
|
|
145
177
|
if (window.confirm('清空所有好感度账本?此操作不可撤销。')) act('/api/pet-settings/reset-affection', {}, '账本已清空')
|
|
146
178
|
} }, '重置账本'),
|
|
147
|
-
h('span', { style: muted }, '
|
|
179
|
+
h('span', { style: muted }, '每 10 次触摸该宠物会自主庆祝一次'))),
|
|
180
|
+
|
|
181
|
+
|
|
148
182
|
|
|
149
183
|
// ── 内心戏(宠物自主说话)──
|
|
150
184
|
h(Section, { title: h('span', null, '自主表达 ', h('span', { style: { fontSize: 11, fontWeight: 400, color: 'rgba(127,127,127,.9)' } }, '模型驱动')) },
|
|
@@ -190,11 +224,11 @@ window.__ModuleLoader__.load({
|
|
|
190
224
|
marginLeft: side === 'right' ? -1 : 0 }
|
|
191
225
|
}
|
|
192
226
|
return h('div', { style: { display: 'inline-flex' } },
|
|
193
|
-
h('button', { style: seg(iv.preset
|
|
194
|
-
if (iv.preset !== '
|
|
227
|
+
h('button', { style: seg(iv.preset === 'whale598', 'left'), onClick: function () {
|
|
228
|
+
if (iv.preset !== 'whale598') update({ innerVoice: { preset: 'whale598' } })
|
|
195
229
|
} }, '精简(仅表达)'),
|
|
196
|
-
h('button', { style: seg(iv.preset === '
|
|
197
|
-
if (iv.preset !== '
|
|
230
|
+
h('button', { style: seg(iv.preset === 'whale598-standard', 'right'), onClick: function () {
|
|
231
|
+
if (iv.preset !== 'whale598-standard') update({ innerVoice: { preset: 'whale598-standard' } })
|
|
198
232
|
} }, '普通(含工作工具)'))
|
|
199
233
|
})()),
|
|
200
234
|
h('div', { style: row }, '指定会话(高级)',
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { i as installPetDriver, n as installPetService } from "./service-
|
|
1
|
+
import { c as readAffectionRaw, d as toolCallsFile, f as writeAffectionRaw, i as installPetDriver, l as readJsonl, n as installPetService, p as writePetName, s as eventsFile, u as readPetName } from "./service-D-b8UzM2.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { dirname, extname, join, normalize, resolve, sep } from "node:path";
|
|
@@ -20,6 +20,7 @@ import { fileURLToPath } from "node:url";
|
|
|
20
20
|
* dispatch — the webserver throws on duplicate path registration.
|
|
21
21
|
* @module dsh-agentic-pet/settings-page
|
|
22
22
|
*/
|
|
23
|
+
/** The touch ledger the driver owns; reset writes the driver's empty shape. */
|
|
23
24
|
const DEFAULT_SETTINGS = {
|
|
24
25
|
proactivity: {
|
|
25
26
|
enabled: false,
|
|
@@ -30,7 +31,7 @@ const DEFAULT_SETTINGS = {
|
|
|
30
31
|
},
|
|
31
32
|
innerVoice: {
|
|
32
33
|
enabled: true,
|
|
33
|
-
preset: "
|
|
34
|
+
preset: "whale598",
|
|
34
35
|
wakeIntervalMinutes: 2,
|
|
35
36
|
speakProbability: .5,
|
|
36
37
|
touchReplyProbability: .3,
|
|
@@ -39,7 +40,6 @@ const DEFAULT_SETTINGS = {
|
|
|
39
40
|
},
|
|
40
41
|
debug: { cdp: false }
|
|
41
42
|
};
|
|
42
|
-
/** The touch ledger the driver owns; reset writes the driver's empty shape. */
|
|
43
43
|
const EMPTY_LEDGER = {
|
|
44
44
|
affection: 0,
|
|
45
45
|
total: 0,
|
|
@@ -169,7 +169,6 @@ function openPathInFileManager(path) {
|
|
|
169
169
|
*/
|
|
170
170
|
function installSettingsPage(ctx, home, pet) {
|
|
171
171
|
const settingsPath = join(home, "pet-settings.json");
|
|
172
|
-
const ledgerPath = join(home, "pet-affection.json");
|
|
173
172
|
const windowPath = join(home, "pet-window.json");
|
|
174
173
|
/** Summarize EVERY live pet (id, online flag, normalized /status). */
|
|
175
174
|
const petStatus = async () => {
|
|
@@ -224,18 +223,49 @@ function installSettingsPage(ctx, home, pet) {
|
|
|
224
223
|
petStatus()
|
|
225
224
|
]);
|
|
226
225
|
const affection = {};
|
|
227
|
-
const legacyBook = await readFile(ledgerPath, "utf8").then((raw) => JSON.parse(raw), () => null);
|
|
228
226
|
const bookIds = new Set(status.pets.map((pet) => String(pet.id)));
|
|
229
227
|
bookIds.add("electron");
|
|
230
228
|
for (const id of bookIds) {
|
|
231
|
-
const book = await
|
|
229
|
+
const book = await readAffectionRaw(home, id);
|
|
232
230
|
if (book !== null) affection[id] = book;
|
|
233
231
|
}
|
|
232
|
+
const names = {};
|
|
233
|
+
for (const id of bookIds) {
|
|
234
|
+
const label = await readPetName(home, id);
|
|
235
|
+
if (label !== void 0) names[id] = label;
|
|
236
|
+
}
|
|
237
|
+
const composed = {
|
|
238
|
+
...settings,
|
|
239
|
+
names
|
|
240
|
+
};
|
|
241
|
+
const history = [];
|
|
242
|
+
const touchTotals = {};
|
|
243
|
+
for (const id of bookIds) {
|
|
244
|
+
const events = await readJsonl(eventsFile(home, id), 600);
|
|
245
|
+
const totals = {
|
|
246
|
+
total: 0,
|
|
247
|
+
byPart: {}
|
|
248
|
+
};
|
|
249
|
+
for (const event of events) {
|
|
250
|
+
if (event.type !== "touch") continue;
|
|
251
|
+
const part = typeof event.part === "string" ? event.part : "miss";
|
|
252
|
+
totals.total += 1;
|
|
253
|
+
totals.byPart[part] = (totals.byPart[part] ?? 0) + 1;
|
|
254
|
+
}
|
|
255
|
+
if (totals.total > 0) touchTotals[id] = totals;
|
|
256
|
+
for (const call of await readJsonl(toolCallsFile(home, id), 12)) history.push({
|
|
257
|
+
...call,
|
|
258
|
+
petId: id
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
history.sort((a, b) => Number(b.at ?? 0) - Number(a.at ?? 0));
|
|
234
262
|
writeJson(res, 200, {
|
|
235
|
-
settings,
|
|
263
|
+
settings: composed,
|
|
236
264
|
affection,
|
|
237
265
|
windowPosition,
|
|
238
|
-
pets: status.pets
|
|
266
|
+
pets: status.pets,
|
|
267
|
+
history: history.slice(0, 12),
|
|
268
|
+
touchTotals
|
|
239
269
|
});
|
|
240
270
|
});
|
|
241
271
|
route("POST", "/api/pet-settings/update", async (req, res) => {
|
|
@@ -245,6 +275,11 @@ function installSettingsPage(ctx, home, pet) {
|
|
|
245
275
|
writeJson(res, 400, { error: "body must be {patch:{...}}" });
|
|
246
276
|
return;
|
|
247
277
|
}
|
|
278
|
+
const patchNames = patch.names;
|
|
279
|
+
if (typeof patchNames === "object" && patchNames !== null) {
|
|
280
|
+
for (const [petId, label] of Object.entries(patchNames)) await writePetName(home, petId, typeof label === "string" && label.trim() !== "" ? label : null);
|
|
281
|
+
delete patch.names;
|
|
282
|
+
}
|
|
248
283
|
const next = mergeSettings(await readSettings(), patch);
|
|
249
284
|
await writeFile(settingsPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
250
285
|
ctx.emit("pet-settings/changed");
|
|
@@ -254,7 +289,7 @@ function installSettingsPage(ctx, home, pet) {
|
|
|
254
289
|
});
|
|
255
290
|
});
|
|
256
291
|
route("POST", "/api/pet-settings/reset-affection", async (_req, res) => {
|
|
257
|
-
await
|
|
292
|
+
await writeAffectionRaw(home, "electron", EMPTY_LEDGER);
|
|
258
293
|
writeJson(res, 200, {
|
|
259
294
|
ok: true,
|
|
260
295
|
affection: EMPTY_LEDGER
|
|
@@ -311,7 +346,7 @@ const inject = [
|
|
|
311
346
|
"workspaceController"
|
|
312
347
|
];
|
|
313
348
|
/** The URL path prefix the pet renderer is served under. No trailing slash. */
|
|
314
|
-
const PET_PATH_PREFIX = "/
|
|
349
|
+
const PET_PATH_PREFIX = "/whale598";
|
|
315
350
|
/**
|
|
316
351
|
* Locator file naming this runtime's authenticated root for the pet carrier.
|
|
317
352
|
* The desktop shell keeps its authenticated URL in process memory only, so a
|
|
@@ -367,7 +402,7 @@ function bytesBody(bytes) {
|
|
|
367
402
|
/**
|
|
368
403
|
* Answer one GET or HEAD `/pet` request. The URL path is the full server
|
|
369
404
|
* pathname (`/pet` prefix included); it must be exactly the prefix or stay
|
|
370
|
-
* under it. The index entry (`/
|
|
405
|
+
* under it. The index entry (`/whale598`, `/whale598/`, `/whale598/index.html`) passes
|
|
371
406
|
* Connection's browser authentication; every other file is public with a weak
|
|
372
407
|
* ETag and `cache-control: no-cache`, answering a matching `If-None-Match`
|
|
373
408
|
* with 304. Traversal outside the dist root is 403, missing paths are 404, a
|
|
@@ -379,7 +414,8 @@ function bytesBody(bytes) {
|
|
|
379
414
|
*/
|
|
380
415
|
async function servePetStatic(request, distRoot, distIndex, authorizeIndex) {
|
|
381
416
|
const pathname = decodeURIComponent(new URL(request.url).pathname);
|
|
382
|
-
|
|
417
|
+
if (!(pathname === "/whale598" || pathname.startsWith(`/whale598/`))) return new Response(null, { status: 404 });
|
|
418
|
+
const target = resolve(normalize(join(distRoot, pathname === "/whale598" || pathname === `/whale598/` ? "/" : pathname.slice(9))));
|
|
383
419
|
if (target !== distRoot && !target.startsWith(distRoot + sep)) return new Response(null, { status: 403 });
|
|
384
420
|
let body;
|
|
385
421
|
let headers;
|
|
@@ -535,6 +571,17 @@ function apply(ctx) {
|
|
|
535
571
|
const candidate = String(require("electron"));
|
|
536
572
|
if (existsSync(candidate)) return candidate;
|
|
537
573
|
} catch {}
|
|
574
|
+
try {
|
|
575
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
576
|
+
for (let i = 0; i < 8; i += 1) {
|
|
577
|
+
const pkgDir = join(dir, "node_modules", "electron");
|
|
578
|
+
const binary = join(pkgDir, "dist", readFileSync(join(pkgDir, "path.txt"), "utf8").trim());
|
|
579
|
+
if (existsSync(binary)) return binary;
|
|
580
|
+
const parent = dirname(dir);
|
|
581
|
+
if (parent === dir) break;
|
|
582
|
+
dir = parent;
|
|
583
|
+
}
|
|
584
|
+
} catch {}
|
|
538
585
|
return process.versions.electron !== void 0 ? process.execPath : void 0;
|
|
539
586
|
};
|
|
540
587
|
const spawnPetCarrier = (url) => {
|