@morlay/dsh-desktop-shell 0.2.0 → 0.2.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/README.md +4 -26
- package/dist/{appconfig-B4kozO2F.mjs → appconfig-cvjOFNqE.mjs} +0 -5
- package/dist/appconfig.mjs +1 -1
- package/dist/{dshhome-C_iyJzc8.mjs → dshhome-Dp89FOr-.mjs} +0 -4
- package/dist/dshhome.mjs +1 -1
- package/dist/index.mjs +5 -63
- package/dist/{official-C8RFHQyT.mjs → official-Cz9Mry1n.mjs} +4 -7
- package/dist/official.mjs +1 -1
- package/dist/preload-app.cjs +0 -13
- package/dist/{profile-project-CEAT8PEX.mjs → profile-project-BgQILDbu.mjs} +0 -30
- package/dist/profile-project.mjs +1 -1
- package/dist/{seed-Bg2S14hM.mjs → seed-DEd-kuvi.mjs} +1 -6
- package/dist/seed.mjs +1 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -1,34 +1,12 @@
|
|
|
1
1
|
# @morlay/dsh-desktop-shell
|
|
2
2
|
|
|
3
|
-
dsh 桌面应用的 **Electron 壳**:服务应用自己的 `<name>://` 协议,经字节管道(FD 3/4)把 desktop profile 交给
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
dsh 桌面应用的 **Electron 壳**:服务应用自己的 `<name>://` 协议,经字节管道(FD 3/4)把 desktop profile 交给 host 子进程 [`@morlay/dsh-desktop-host`](../dsh-desktop-host/README.md) 启动,并在首次启动 / 种子指纹变化时把 profile 种进 `DSH_HOME`。
|
|
4
|
+
|
|
5
|
+
## 用法
|
|
6
6
|
|
|
7
7
|
它**不单独跑**——启动与打包它的是 [`@morlay/dsh-desktopify`](../dsh-desktopify/README.md):
|
|
8
8
|
|
|
9
9
|
- `dsh-desktopify dev` 以**本包目录**为 Electron app 启动(Electron 按本包清单的 `main` 找到 `dist/index.mjs`);
|
|
10
10
|
- `dsh-desktopify bundle` 把本包的 `dist/` 复制成最小壳 app 目录再交给 electron-builder。
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
## 出口
|
|
15
|
-
|
|
16
|
-
| 出口 | 用途 |
|
|
17
|
-
| ------------------- | ------------------------------------------------------------------- |
|
|
18
|
-
| `.` | 壳入口(`dist/index.mjs`,Electron 主进程),由 `main` 与它一起指到 |
|
|
19
|
-
| `./appconfig` | `appconfig.json` 读写与 profile patch 文件名 |
|
|
20
|
-
| `./dshhome` | `dshHome` 语义解析(`xdg` / `env` / 路径) |
|
|
21
|
-
| `./official` | 官方包清单与 host 变体包名(壳与工具的同一份事实) |
|
|
22
|
-
| `./profile-project` | profile 工程(`pnpm-workspace.yaml`、overrides、清单) |
|
|
23
|
-
| `./seed` | 种子布局与指纹 |
|
|
24
|
-
|
|
25
|
-
后五个是工具侧复用同一份实现的入口——种子、官方包清单、home 解析都只有这一份,壳与 CLI 都引它。
|
|
26
|
-
|
|
27
|
-
## 依赖边界
|
|
28
|
-
|
|
29
|
-
壳产物整份进 `app.asar`,那里只有壳自己的字节(`@local/*` 与 `@morlay/dsh-desktop-host` 构建期内联),上游包
|
|
30
|
-
一律留在产物外。因此**壳入口可达的模块只能 import node 内建、`electron` 与那两个内联面**——上游裸引用能过类型
|
|
31
|
-
检查、能打出产物,但装进 asar 后解析不到,只在真机启动时炸。守卫:
|
|
32
|
-
[`shell-import-boundary.spec.ts`](./src/__tests__/shell-import-boundary.spec.ts)。
|
|
33
|
-
|
|
34
|
-
preload 必须是 CJS(sandboxed preload 的约束),所以它单独一遍构建;壳主进程与它产在同一次 `pnpm build`。
|
|
12
|
+
出口面、依赖边界与拆分理由见[设计 壳独立成包](./.agents/designs/20260924-壳独立成包.md);传输、启动链与平台标记见[设计 桌面无端口传输与窗口对齐](../dsh-desktopify/.agents/designs/20260920-桌面无端口传输与窗口对齐.md)。
|
|
@@ -2,11 +2,6 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
//#region src/appconfig.ts
|
|
4
4
|
const PROFILE_NAME = "desktop";
|
|
5
|
-
/**
|
|
6
|
-
* profile 自己的 patch 文档名(上游 `PROFILE_PATCH_FILENAME` 的同值)。
|
|
7
|
-
* 常量放在这里而不是 import 上游:`seed.ts` 会被打进壳产物(`app.asar`),
|
|
8
|
-
* 那里解析不到 `@deepseek-ai/*`——上游裸引用会让应用启动即 `ERR_MODULE_NOT_FOUND`。
|
|
9
|
-
*/
|
|
10
5
|
const PROFILE_PATCH_NAME = "cordis.patch.yml";
|
|
11
6
|
function isRecord(value) {
|
|
12
7
|
return typeof value === "object" && value !== null;
|
package/dist/appconfig.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as writeAppConfig, n as PROFILE_PATCH_NAME, r as loadAppConfig, t as PROFILE_NAME } from "./appconfig-
|
|
1
|
+
import { i as writeAppConfig, n as PROFILE_PATCH_NAME, r as loadAppConfig, t as PROFILE_NAME } from "./appconfig-cvjOFNqE.mjs";
|
|
2
2
|
export { PROFILE_NAME, PROFILE_PATCH_NAME, loadAppConfig, writeAppConfig };
|
|
@@ -7,10 +7,6 @@ function xdgDataHome() {
|
|
|
7
7
|
if (configured !== void 0 && configured.trim() !== "") return resolve(configured);
|
|
8
8
|
return join(homedir(), ".local", "share");
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
* 解析 `dshHome` 的三态:`xdg`(平台数据目录 + app 名)、`env`(交给环境,这里没有具体路径)、
|
|
12
|
-
* 绝对路径。打包壳与 dev CLI(`--home`)共用这一份,措辞与校验只写在这里。
|
|
13
|
-
*/
|
|
14
10
|
function resolveConfiguredHome(name, spec) {
|
|
15
11
|
if (spec === "env") return void 0;
|
|
16
12
|
if (spec === "xdg") return join(xdgDataHome(), name);
|
package/dist/dshhome.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as resolveDshHome, r as xdgDataHome, t as resolveConfiguredHome } from "./dshhome-
|
|
1
|
+
import { n as resolveDshHome, r as xdgDataHome, t as resolveConfiguredHome } from "./dshhome-Dp89FOr-.mjs";
|
|
2
2
|
export { resolveConfiguredHome, resolveDshHome, xdgDataHome };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { r as loadAppConfig } from "./appconfig-
|
|
2
|
-
import { n as resolveDshHome } from "./dshhome-
|
|
3
|
-
import { t as DESKTOP_HOST_PACKAGE } from "./official-
|
|
4
|
-
import { a as runtimeOverrides, i as installProfile, o as workspaceWithOverrides, r as PROFILE_WORKSPACE_NAME } from "./profile-project-
|
|
5
|
-
import { i as ensureSeedProfile, r as SEED_RUNTIME_DIR_NAME } from "./seed-
|
|
1
|
+
import { r as loadAppConfig } from "./appconfig-cvjOFNqE.mjs";
|
|
2
|
+
import { n as resolveDshHome } from "./dshhome-Dp89FOr-.mjs";
|
|
3
|
+
import { t as DESKTOP_HOST_PACKAGE } from "./official-Cz9Mry1n.mjs";
|
|
4
|
+
import { a as runtimeOverrides, i as installProfile, o as workspaceWithOverrides, r as PROFILE_WORKSPACE_NAME } from "./profile-project-BgQILDbu.mjs";
|
|
5
|
+
import { i as ensureSeedProfile, r as SEED_RUNTIME_DIR_NAME } from "./seed-DEd-kuvi.mjs";
|
|
6
6
|
import { access, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
7
7
|
import { basename, dirname, join, resolve } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
@@ -15,32 +15,21 @@ import { randomBytes } from "node:crypto";
|
|
|
15
15
|
const DESKTOP_IPC = {
|
|
16
16
|
directoryPick: "dsh-desktop:directory-pick",
|
|
17
17
|
nativeThemeSet: "dsh-desktop:native-theme-set",
|
|
18
|
-
/** 桌面流载体:页面把 Gateway 的流请求交给主进程,主进程用标准 Request 喂宿主。 */
|
|
19
18
|
streamOpen: "dsh-desktop:stream-open",
|
|
20
19
|
streamCancel: "dsh-desktop:stream-cancel",
|
|
21
|
-
/** 逻辑流的上行项(客户端 → 宿主),主进程把它们写成宿主请求体的后续行。 */
|
|
22
20
|
streamUplink: "dsh-desktop:stream-uplink",
|
|
23
|
-
/** 上行结束:主进程据此结束宿主请求体。 */
|
|
24
21
|
streamUplinkEnd: "dsh-desktop:stream-uplink-end",
|
|
25
22
|
streamChunk: "dsh-desktop:stream-chunk",
|
|
26
23
|
streamEnd: "dsh-desktop:stream-end",
|
|
27
24
|
streamError: "dsh-desktop:stream-error",
|
|
28
|
-
/** 桌面快捷键:设备偏好的读写 / 录制状态(渲染进程 → 主进程)。 */
|
|
29
25
|
shortcutsGet: "dsh-desktop:shortcuts-get",
|
|
30
26
|
shortcutsEdit: "dsh-desktop:shortcuts-edit",
|
|
31
27
|
shortcutsRecording: "dsh-desktop:shortcuts-recording",
|
|
32
28
|
shortcutsCloseWindow: "dsh-desktop:shortcuts-close-window",
|
|
33
|
-
/** 命中的原生按键手势(主进程 → 渲染进程);渲染侧的 native keyboard 适配器消费。 */
|
|
34
29
|
shortcutsInput: "dsh-desktop:shortcuts-input",
|
|
35
|
-
/** 配置快照变化(主进程 → 渲染进程)。 */
|
|
36
30
|
shortcutsChanged: "dsh-desktop:shortcuts-changed"
|
|
37
31
|
};
|
|
38
|
-
/** preload 从渲染进程 argv 里读回 scheme 的参数名(主进程与 preload 共用一份)。 */
|
|
39
32
|
const DESKTOP_SCHEME_ARGUMENT = "--dsh-desktop-scheme";
|
|
40
|
-
/**
|
|
41
|
-
* 应用页面的自定义协议:取 app 工作区 package.json 的 name 派生,避免与官方桌面应用
|
|
42
|
-
* 共用同一个 scheme。去 scope 前缀、转小写、非法字符换成 `-`;派生不出合法 scheme 就报错。
|
|
43
|
-
*/
|
|
44
33
|
function desktopScheme(name) {
|
|
45
34
|
const bare = name.slice(name.lastIndexOf("/") + 1).toLowerCase().replaceAll(/[^a-z0-9+.-]/gu, "-");
|
|
46
35
|
if (!/^[a-z][a-z0-9+.-]*$/u.test(bare)) throw new Error(`dsh desktop: application name "${name}" does not yield a usable URL scheme (${bare})`);
|
|
@@ -72,23 +61,16 @@ function installDesktopDirectoryPicker(getWindow, scheme) {
|
|
|
72
61
|
return result;
|
|
73
62
|
});
|
|
74
63
|
}
|
|
75
|
-
/** 桌面流的宿主路径:页面/主进程都用它(两侧共享一份,避免路径写两处)。 */
|
|
76
64
|
const DESKTOP_STREAM_PATH = "/.dsh/remote-stream";
|
|
77
|
-
/**
|
|
78
|
-
* 桌面流请求体的首行:定下这条逻辑流的 endpoint 与 payload,后续行都是上行项
|
|
79
|
-
* (客户端 → 宿主;`$events` 这类宿主自己的流被上游释放,不会读到它们)。
|
|
80
|
-
*/
|
|
81
65
|
function encodeDesktopStreamOpen(endpoint, payload) {
|
|
82
66
|
return `${JSON.stringify({
|
|
83
67
|
endpoint,
|
|
84
68
|
payload
|
|
85
69
|
})}\n`;
|
|
86
70
|
}
|
|
87
|
-
/** 桌面流请求体里的一道上行项。 */
|
|
88
71
|
function encodeDesktopStreamItem(item) {
|
|
89
72
|
return `${JSON.stringify(item)}\n`;
|
|
90
73
|
}
|
|
91
|
-
/** 单个 data 帧携带的最大原始字节数。 */
|
|
92
74
|
const DESKTOP_PIPE_CHUNK_BYTES = 65536;
|
|
93
75
|
const FRAME_MAGIC = 1146308659;
|
|
94
76
|
const FRAME_HEADER_BYTES = 13;
|
|
@@ -125,19 +107,15 @@ function encodeFrame(type, streamId, payload, dataType, direction) {
|
|
|
125
107
|
function jsonPayload(type, streamId, value, dataType, direction) {
|
|
126
108
|
return encodeFrame(type, streamId, Buffer.from(JSON.stringify(value), "utf8"), dataType, direction);
|
|
127
109
|
}
|
|
128
|
-
/** 编码开启一个请求流的元数据帧(壳侧)。 */
|
|
129
110
|
function encodeDesktopRequestStart(streamId, request) {
|
|
130
111
|
return jsonPayload(REQUEST_FRAME_START, streamId, request, REQUEST_FRAME_DATA, "request");
|
|
131
112
|
}
|
|
132
|
-
/** 编码一个受限的请求体原始分片(壳侧)。 */
|
|
133
113
|
function encodeDesktopRequestData(streamId, data) {
|
|
134
114
|
return encodeFrame(REQUEST_FRAME_DATA, streamId, Buffer.from(data), REQUEST_FRAME_DATA, "request");
|
|
135
115
|
}
|
|
136
|
-
/** 编码请求体正常结束(壳侧)。 */
|
|
137
116
|
function encodeDesktopRequestEnd(streamId) {
|
|
138
117
|
return encodeFrame(REQUEST_FRAME_END, streamId, Buffer.alloc(0), REQUEST_FRAME_DATA, "request");
|
|
139
118
|
}
|
|
140
|
-
/** 编码取消一个请求及其响应(壳侧)。 */
|
|
141
119
|
function encodeDesktopRequestCancel(streamId) {
|
|
142
120
|
return encodeFrame(REQUEST_FRAME_CANCEL, streamId, Buffer.alloc(0), REQUEST_FRAME_DATA, "request");
|
|
143
121
|
}
|
|
@@ -163,10 +141,8 @@ function parseJson(payload, subject) {
|
|
|
163
141
|
function assertEmpty(payload, subject) {
|
|
164
142
|
if (payload.byteLength !== 0) throw new Error(`dsh desktop: ${subject} frame carried a payload`);
|
|
165
143
|
}
|
|
166
|
-
/** 增量解码壳侧从响应管道收到的响应帧。 */
|
|
167
144
|
var DesktopHostResponseDecoder = class {
|
|
168
145
|
buffer = Buffer.alloc(0);
|
|
169
|
-
/** 追加字节并返回所有已完整的响应帧。 */
|
|
170
146
|
push(chunk) {
|
|
171
147
|
this.buffer = this.buffer.byteLength === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
|
|
172
148
|
const frames = [];
|
|
@@ -176,7 +152,6 @@ var DesktopHostResponseDecoder = class {
|
|
|
176
152
|
frames.push(frame);
|
|
177
153
|
}
|
|
178
154
|
}
|
|
179
|
-
/** EOF 落在帧中间时报错。 */
|
|
180
155
|
finish() {
|
|
181
156
|
if (this.buffer.byteLength !== 0) throw new Error("dsh desktop: response pipe ended inside a frame");
|
|
182
157
|
}
|
|
@@ -227,7 +202,6 @@ var DesktopHostResponseDecoder = class {
|
|
|
227
202
|
return { message: value.message };
|
|
228
203
|
}
|
|
229
204
|
};
|
|
230
|
-
/** IPC 事件校验(壳侧)。 */
|
|
231
205
|
function isDesktopHostEvent(value) {
|
|
232
206
|
if (!isRecord(value)) return false;
|
|
233
207
|
if (value.type === "ready") return value.protocolVersion === 1;
|
|
@@ -253,7 +227,6 @@ async function exitsWithin(exit, milliseconds) {
|
|
|
253
227
|
if (timer !== void 0) clearTimeout(timer);
|
|
254
228
|
}
|
|
255
229
|
}
|
|
256
|
-
/** 桌面 host 子进程:字节管道上的请求/响应载体(FD 3/4)+ Node IPC 生命周期。 */
|
|
257
230
|
var DesktopHostProcess = class {
|
|
258
231
|
node;
|
|
259
232
|
runtimeDir;
|
|
@@ -285,7 +258,6 @@ var DesktopHostProcess = class {
|
|
|
285
258
|
this.inspectPort = inspectPort;
|
|
286
259
|
this.options = options;
|
|
287
260
|
}
|
|
288
|
-
/** 启动子进程一次;`ready` 之后才解析。 */
|
|
289
261
|
async start() {
|
|
290
262
|
if (this.child !== void 0) return this.readyPromise;
|
|
291
263
|
const entry = join(this.runtimeDir, "node_modules", ...DESKTOP_HOST_PACKAGE.split("/"), "lib", "index.js");
|
|
@@ -377,7 +349,6 @@ var DesktopHostProcess = class {
|
|
|
377
349
|
});
|
|
378
350
|
return this.readyPromise;
|
|
379
351
|
}
|
|
380
|
-
/** 把一个应用协议请求(`<scheme>://app/*`)交给子进程,响应体边收边出。 */
|
|
381
352
|
async fetch(request) {
|
|
382
353
|
await this.start();
|
|
383
354
|
const child = this.child;
|
|
@@ -419,7 +390,6 @@ var DesktopHostProcess = class {
|
|
|
419
390
|
});
|
|
420
391
|
});
|
|
421
392
|
}
|
|
422
|
-
/** 请求优雅退出,再等子进程收尾。 */
|
|
423
393
|
async stop() {
|
|
424
394
|
const child = this.child;
|
|
425
395
|
if (child === void 0) return;
|
|
@@ -1206,14 +1176,6 @@ async function writeFileAtomic(filename, content, options) {
|
|
|
1206
1176
|
}
|
|
1207
1177
|
//#endregion
|
|
1208
1178
|
//#region src/keybindings.ts
|
|
1209
|
-
/** 设备本地的快捷键偏好(Electron userData 下):原子替换、单写者协调。 */
|
|
1210
|
-
/**
|
|
1211
|
-
* 打开设备配置(原子替换)。
|
|
1212
|
-
* @param userData - Electron 的 userData 目录,不接受渲染进程给的路径。
|
|
1213
|
-
* @param platform - 本机输入平台。
|
|
1214
|
-
* @param publish - 提交后更新原生绑定索引与受信页面。
|
|
1215
|
-
* @returns 单写者事务协调器。
|
|
1216
|
-
*/
|
|
1217
1179
|
function desktopKeybindings(userData, platform, publish) {
|
|
1218
1180
|
const path = join(userData, "keybindings.json");
|
|
1219
1181
|
return new ShortcutPersistence({
|
|
@@ -1233,21 +1195,6 @@ function desktopKeybindings(userData, platform, publish) {
|
|
|
1233
1195
|
}
|
|
1234
1196
|
//#endregion
|
|
1235
1197
|
//#region src/shortcuts.ts
|
|
1236
|
-
/**
|
|
1237
|
-
* 桌面快捷键的原生侧:设备偏好持久化 + 原生按键转发 + 关闭窗口动作。
|
|
1238
|
-
*
|
|
1239
|
-
* 上游 `@deepseek-ai/dsh-client-shortcuts` 在 `data-platform` 存在时按 desktop 运行时起服务,
|
|
1240
|
-
* 并**强制要求** `window.dshDesktop.keyboard`(缺了就构造失败)——这里就是那份契约的实现:
|
|
1241
|
-
* 主进程按已绑定键过滤物理按键,把命中的手势经 IPC 交给渲染进程派发。
|
|
1242
|
-
*/
|
|
1243
|
-
/**
|
|
1244
|
-
* 装上桌面快捷键的原生侧。
|
|
1245
|
-
* @param getWindow - 当前主窗口。
|
|
1246
|
-
* @param userData - Electron 的 userData 目录。
|
|
1247
|
-
* @param platform - 本机输入平台。
|
|
1248
|
-
* @param scheme - 产品页面的自定义协议(校验 IPC 来源)。
|
|
1249
|
-
* @returns 窗口接入与释放操作。
|
|
1250
|
-
*/
|
|
1251
1198
|
function installDesktopShortcuts(getWindow, userData, platform, scheme) {
|
|
1252
1199
|
let definitions = [];
|
|
1253
1200
|
let recording = false;
|
|
@@ -1454,7 +1401,6 @@ function shellWrappedSpawn(command, args, options) {
|
|
|
1454
1401
|
//#endregion
|
|
1455
1402
|
//#region src/index.ts
|
|
1456
1403
|
let focusPrimaryWindow = () => {};
|
|
1457
|
-
/** appconfig.json 的位置:打包形态只看 resources,dev 形态由 CLI 指到构建目录。 */
|
|
1458
1404
|
function appConfigDir() {
|
|
1459
1405
|
const configured = process.env.DSH_DESKTOP_APPCONFIG_DIR;
|
|
1460
1406
|
return app.isPackaged || configured === void 0 || configured === "" ? process.resourcesPath : configured;
|
|
@@ -1467,7 +1413,6 @@ function startupFailure(error) {
|
|
|
1467
1413
|
throw error instanceof Error ? error : new Error(message);
|
|
1468
1414
|
}
|
|
1469
1415
|
const appConfig = await loadAppConfig(appConfigDir()).catch((error) => startupFailure(error));
|
|
1470
|
-
/** 自定义协议取自 app 名:与官方桌面应用(`dsh-app`)错开。 */
|
|
1471
1416
|
const SCHEME = (() => {
|
|
1472
1417
|
try {
|
|
1473
1418
|
return desktopScheme(appConfig.name);
|
|
@@ -1488,7 +1433,6 @@ protocol.registerSchemesAsPrivileged([{
|
|
|
1488
1433
|
}]);
|
|
1489
1434
|
const WEB_FRONTEND_PACKAGE = "@deepseek-ai/dsh-web-frontend";
|
|
1490
1435
|
const APPLICATION_URL = `${SCHEME}://app/`;
|
|
1491
|
-
/** Windows 自绘标题栏高度(DIP),与 preload 写入的 CSS 变量一致。 */
|
|
1492
1436
|
const WINDOWS_TITLEBAR_HEIGHT = 40;
|
|
1493
1437
|
async function pathExists(path) {
|
|
1494
1438
|
try {
|
|
@@ -1528,7 +1472,6 @@ function developmentProject() {
|
|
|
1528
1472
|
if (app.isPackaged) throw new Error("dsh desktop: development project override is unavailable in packaged applications");
|
|
1529
1473
|
return resolve(configured);
|
|
1530
1474
|
}
|
|
1531
|
-
/** 窗口形态:macOS 走 sidebar vibrancy + hiddenInset(交通灯落在侧边栏内),Windows 自绘 caption。 */
|
|
1532
1475
|
function windowFrame() {
|
|
1533
1476
|
if (process.platform === "darwin") return {
|
|
1534
1477
|
titleBarStyle: "hiddenInset",
|
|
@@ -1666,7 +1609,6 @@ async function main() {
|
|
|
1666
1609
|
let nextStreamId = 1;
|
|
1667
1610
|
const streamEncoder = new TextEncoder();
|
|
1668
1611
|
const activeStreams = /* @__PURE__ */ new Map();
|
|
1669
|
-
/** 结束宿主请求体:已取消/已结束的流上 close 会抛,这里只当收尾。 */
|
|
1670
1612
|
const closeStreamUplink = (stream) => {
|
|
1671
1613
|
const uplink = stream.uplink;
|
|
1672
1614
|
if (uplink === void 0) return;
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
//#region src/official-packages.generated.ts
|
|
2
|
-
/**
|
|
3
|
-
* GENERATED by scripts/gen-official-packages.ts — do not edit by hand.
|
|
4
|
-
* 桌面组合加载的官方插件包:dsh-base 与 dsh-web-app bundle 的 cordis.patch.yml
|
|
5
|
-
* 非禁用条目引用的 @deepseek-ai/* 包(子路径行归到包名)。上游升级后重跑:
|
|
6
|
-
* pnpm --filter @morlay/dsh-desktop-shell run gen:official-packages
|
|
7
|
-
*/
|
|
8
2
|
const OFFICIAL_PROFILE_PACKAGES = [
|
|
9
3
|
"@deepseek-ai/cordis-plugin-timer",
|
|
10
4
|
"@deepseek-ai/dsh-agent",
|
|
@@ -28,6 +22,7 @@ const OFFICIAL_PROFILE_PACKAGES = [
|
|
|
28
22
|
"@deepseek-ai/dsh-client-hmr",
|
|
29
23
|
"@deepseek-ai/dsh-client-locale",
|
|
30
24
|
"@deepseek-ai/dsh-client-modules",
|
|
25
|
+
"@deepseek-ai/dsh-client-product-analytics",
|
|
31
26
|
"@deepseek-ai/dsh-client-resources",
|
|
32
27
|
"@deepseek-ai/dsh-client-shortcuts",
|
|
33
28
|
"@deepseek-ai/dsh-client-ui-agent-preset",
|
|
@@ -59,6 +54,7 @@ const OFFICIAL_PROFILE_PACKAGES = [
|
|
|
59
54
|
"@deepseek-ai/dsh-client-ui-settings-models",
|
|
60
55
|
"@deepseek-ai/dsh-client-ui-settings-plugin-inventory",
|
|
61
56
|
"@deepseek-ai/dsh-client-ui-settings-plugins",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-settings-session-log",
|
|
62
58
|
"@deepseek-ai/dsh-client-ui-settings-shell",
|
|
63
59
|
"@deepseek-ai/dsh-client-ui-settings-subagent",
|
|
64
60
|
"@deepseek-ai/dsh-client-ui-settings-web-search",
|
|
@@ -95,6 +91,7 @@ const OFFICIAL_PROFILE_PACKAGES = [
|
|
|
95
91
|
"@deepseek-ai/dsh-host-directory-picker-auto",
|
|
96
92
|
"@deepseek-ai/dsh-host-open-in-app",
|
|
97
93
|
"@deepseek-ai/dsh-host-plugin-inventory",
|
|
94
|
+
"@deepseek-ai/dsh-host-product-telemetry-otel",
|
|
98
95
|
"@deepseek-ai/dsh-host-webserver",
|
|
99
96
|
"@deepseek-ai/dsh-jobs-local",
|
|
100
97
|
"@deepseek-ai/dsh-llm",
|
|
@@ -105,6 +102,7 @@ const OFFICIAL_PROFILE_PACKAGES = [
|
|
|
105
102
|
"@deepseek-ai/dsh-mcp-resources",
|
|
106
103
|
"@deepseek-ai/dsh-message-feedback",
|
|
107
104
|
"@deepseek-ai/dsh-office-to-pdf",
|
|
105
|
+
"@deepseek-ai/dsh-otel",
|
|
108
106
|
"@deepseek-ai/dsh-permission-presets",
|
|
109
107
|
"@deepseek-ai/dsh-plugin-manager",
|
|
110
108
|
"@deepseek-ai/dsh-plugin-package-inventory-deepseek",
|
|
@@ -158,7 +156,6 @@ const OFFICIAL_PROFILE_PACKAGES = [
|
|
|
158
156
|
];
|
|
159
157
|
//#endregion
|
|
160
158
|
//#region src/official.ts
|
|
161
|
-
/** 工具自带的桌面 host 变体包(不是上游 private 应用):部署里按这个名字落位,壳按 `<pkg>/lib/index.js` 启动。 */
|
|
162
159
|
const DESKTOP_HOST_PACKAGE = "@morlay/dsh-desktop-host";
|
|
163
160
|
const OFFICIAL_RUNTIME_PACKAGES = [
|
|
164
161
|
"@deepseek-ai/dsh",
|
package/dist/official.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as OFFICIAL_PROFILE_PACKAGES, n as OFFICIAL_PROFILE_BUNDLES, r as OFFICIAL_RUNTIME_PACKAGES, t as DESKTOP_HOST_PACKAGE } from "./official-
|
|
1
|
+
import { i as OFFICIAL_PROFILE_PACKAGES, n as OFFICIAL_PROFILE_BUNDLES, r as OFFICIAL_RUNTIME_PACKAGES, t as DESKTOP_HOST_PACKAGE } from "./official-Cz9Mry1n.mjs";
|
|
2
2
|
export { DESKTOP_HOST_PACKAGE, OFFICIAL_PROFILE_BUNDLES, OFFICIAL_PROFILE_PACKAGES, OFFICIAL_RUNTIME_PACKAGES };
|
package/dist/preload-app.cjs
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
let electron = require("electron");
|
|
2
|
-
/** Web UI 写入的主题来源属性。 */
|
|
3
2
|
const THEME_SOURCE_ATTRIBUTE = "data-ds-theme-source";
|
|
4
3
|
function whenRootReady(root, run) {
|
|
5
4
|
if (root.documentElement === null) root.defaultView?.addEventListener("DOMContentLoaded", run, { once: true });
|
|
6
5
|
else run();
|
|
7
6
|
}
|
|
8
|
-
/** 标记页面平台:macOS 布局(侧边栏顶部拖拽条与 logo 下沉)据此启用。 */
|
|
9
7
|
function markDocumentPlatform(platform, root) {
|
|
10
8
|
whenRootReady(root, () => {
|
|
11
9
|
root.documentElement.dataset.platform = platform;
|
|
12
10
|
});
|
|
13
11
|
}
|
|
14
|
-
/** Windows 自绘 caption 标记:布局据此留出标题栏高度、把控制按钮位置让开。 */
|
|
15
12
|
function markWindowsTitlebar(platform, root) {
|
|
16
13
|
if (platform !== "win32") return;
|
|
17
14
|
whenRootReady(root, () => {
|
|
@@ -19,7 +16,6 @@ function markWindowsTitlebar(platform, root) {
|
|
|
19
16
|
root.documentElement.style.setProperty("--dsh-windows-titlebar-height", `${String(40)}px`);
|
|
20
17
|
});
|
|
21
18
|
}
|
|
22
|
-
/** macOS:把主题来源转发给主进程,窗口 vibrancy 才会跟随应用配色。 */
|
|
23
19
|
function syncNativeTheme(platform, root, send) {
|
|
24
20
|
if (platform !== "darwin") return;
|
|
25
21
|
let sent;
|
|
@@ -41,31 +37,23 @@ function syncNativeTheme(platform, root, send) {
|
|
|
41
37
|
const DESKTOP_IPC = {
|
|
42
38
|
directoryPick: "dsh-desktop:directory-pick",
|
|
43
39
|
nativeThemeSet: "dsh-desktop:native-theme-set",
|
|
44
|
-
/** 桌面流载体:页面把 Gateway 的流请求交给主进程,主进程用标准 Request 喂宿主。 */
|
|
45
40
|
streamOpen: "dsh-desktop:stream-open",
|
|
46
41
|
streamCancel: "dsh-desktop:stream-cancel",
|
|
47
|
-
/** 逻辑流的上行项(客户端 → 宿主),主进程把它们写成宿主请求体的后续行。 */
|
|
48
42
|
streamUplink: "dsh-desktop:stream-uplink",
|
|
49
|
-
/** 上行结束:主进程据此结束宿主请求体。 */
|
|
50
43
|
streamUplinkEnd: "dsh-desktop:stream-uplink-end",
|
|
51
44
|
streamChunk: "dsh-desktop:stream-chunk",
|
|
52
45
|
streamEnd: "dsh-desktop:stream-end",
|
|
53
46
|
streamError: "dsh-desktop:stream-error",
|
|
54
|
-
/** 桌面快捷键:设备偏好的读写 / 录制状态(渲染进程 → 主进程)。 */
|
|
55
47
|
shortcutsGet: "dsh-desktop:shortcuts-get",
|
|
56
48
|
shortcutsEdit: "dsh-desktop:shortcuts-edit",
|
|
57
49
|
shortcutsRecording: "dsh-desktop:shortcuts-recording",
|
|
58
50
|
shortcutsCloseWindow: "dsh-desktop:shortcuts-close-window",
|
|
59
|
-
/** 命中的原生按键手势(主进程 → 渲染进程);渲染侧的 native keyboard 适配器消费。 */
|
|
60
51
|
shortcutsInput: "dsh-desktop:shortcuts-input",
|
|
61
|
-
/** 配置快照变化(主进程 → 渲染进程)。 */
|
|
62
52
|
shortcutsChanged: "dsh-desktop:shortcuts-changed"
|
|
63
53
|
};
|
|
64
|
-
/** preload 从渲染进程 argv 里读回 scheme 的参数名(主进程与 preload 共用一份)。 */
|
|
65
54
|
const DESKTOP_SCHEME_ARGUMENT = "--dsh-desktop-scheme";
|
|
66
55
|
//#endregion
|
|
67
56
|
//#region src/stream-uplink.ts
|
|
68
|
-
/** 造一条上行通道;`send` / `end` 是主进程方向的投递函数。 */
|
|
69
57
|
function createStreamUplink(send, end) {
|
|
70
58
|
let streamId;
|
|
71
59
|
let closed = false;
|
|
@@ -92,7 +80,6 @@ function createStreamUplink(send, end) {
|
|
|
92
80
|
}
|
|
93
81
|
//#endregion
|
|
94
82
|
//#region src/preload-app.ts
|
|
95
|
-
/** scheme 取自 app 名,只有主进程知道;sandboxed preload 里只能从 argv 读回。 */
|
|
96
83
|
const scheme = process.argv.find((argument) => argument.startsWith(`${DESKTOP_SCHEME_ARGUMENT}=`))?.slice(21);
|
|
97
84
|
if (scheme !== void 0 && scheme !== "" && location.protocol === `${scheme}:` && location.hostname === "app") {
|
|
98
85
|
electron.contextBridge.exposeInMainWorld("__DSH_DIRECTORY_PICKER__", { pick: () => electron.ipcRenderer.invoke(DESKTOP_IPC.directoryPick) });
|
|
@@ -2,26 +2,10 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
//#region src/profile-project.ts
|
|
5
|
-
/** Desktop profile project: the files the seed ships and the package operations the launcher runs on them.
|
|
6
|
-
*
|
|
7
|
-
* 种子里只带 app 自己的 bundle(`file:` 指向随包 `vendor/` 副本);官方包与 dsh 由不可变的
|
|
8
|
-
* runtime 提供,profile 里对这些包的依赖通过 `overrides` 以 `link:` 指回 runtime,因此安装
|
|
9
|
-
* 不需要 registry,也不复制运行时闭包。
|
|
10
|
-
*/
|
|
11
|
-
/** Profile pnpm settings file the launcher appends run-time `overrides` to. */
|
|
12
5
|
const PROFILE_WORKSPACE_NAME = "pnpm-workspace.yaml";
|
|
13
|
-
/** Seed-written report naming the runtime packages the profile resolves through `overrides`. */
|
|
14
6
|
const PROFILE_RUNTIME_REPORT_NAME = "desktop-runtime-packages.json";
|
|
15
|
-
/** Profile directory holding the `file:` sources of the profile's own bundles. */
|
|
16
7
|
const PROFILE_VENDOR_DIR_NAME = "vendor";
|
|
17
8
|
const TOP_LEVEL_KEY = /^([A-Za-z_][A-Za-z0-9_-]*):(?:[ \t]|$)/u;
|
|
18
|
-
/**
|
|
19
|
-
* Read the seed's runtime package report and point each entry at the installed runtime.
|
|
20
|
-
* @param profileDir - profile directory holding the seed-written report.
|
|
21
|
-
* @param runtimeDir - runtime root the report's paths are relative to.
|
|
22
|
-
* @returns one `link:` override per reported package, in report order.
|
|
23
|
-
* @throws when the report is missing, malformed, or holds an invalid entry.
|
|
24
|
-
*/
|
|
25
9
|
async function runtimeOverrides(profileDir, runtimeDir) {
|
|
26
10
|
const entries = JSON.parse(await readFile(join(profileDir, PROFILE_RUNTIME_REPORT_NAME), "utf8")).runtimePackages;
|
|
27
11
|
if (!Array.isArray(entries) || !entries.every(isRuntimePackage)) throw new Error(`dsh desktop: ${PROFILE_RUNTIME_REPORT_NAME} has an invalid runtimePackages list`);
|
|
@@ -30,20 +14,11 @@ async function runtimeOverrides(profileDir, runtimeDir) {
|
|
|
30
14
|
target: join(runtimeDir, ...entry.path.split("/"))
|
|
31
15
|
}));
|
|
32
16
|
}
|
|
33
|
-
/** One reported runtime package: its dependency name and its runtime-relative directory. */
|
|
34
17
|
function isRuntimePackage(value) {
|
|
35
18
|
if (typeof value !== "object" || value === null) return false;
|
|
36
19
|
const candidate = value;
|
|
37
20
|
return typeof candidate.name === "string" && candidate.name !== "" && typeof candidate.path === "string" && candidate.path !== "";
|
|
38
21
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Write the runtime overrides into profile pnpm settings, replacing an earlier generation.
|
|
41
|
-
* An empty override list leaves the file untouched, so a hand-edited block survives launches
|
|
42
|
-
* whose seed reported no runtime package.
|
|
43
|
-
* @param text - current profile pnpm settings.
|
|
44
|
-
* @param overrides - runtime packages to link.
|
|
45
|
-
* @returns the settings text to write.
|
|
46
|
-
*/
|
|
47
22
|
function workspaceWithOverrides(text, overrides) {
|
|
48
23
|
if (overrides.length === 0) return text;
|
|
49
24
|
const kept = [];
|
|
@@ -57,11 +32,6 @@ function workspaceWithOverrides(text, overrides) {
|
|
|
57
32
|
const block = ["overrides:", ...overrides.map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(`link:${entry.target}`)}`)];
|
|
58
33
|
return `${[...kept, ...block].join("\n")}\n`;
|
|
59
34
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Install the profile's own bundles offline from the seed's `file:` sources.
|
|
62
|
-
* @param options - bundled executables, profile directory, and process launcher.
|
|
63
|
-
* @throws when pnpm cannot start or exits non-zero, with its diagnostics tail.
|
|
64
|
-
*/
|
|
65
35
|
function installProfile(options) {
|
|
66
36
|
return new Promise((resolve, reject) => {
|
|
67
37
|
const child = (options.spawn ?? spawn)(options.node, [
|
package/dist/profile-project.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as runtimeOverrides, i as installProfile, n as PROFILE_VENDOR_DIR_NAME, o as workspaceWithOverrides, r as PROFILE_WORKSPACE_NAME, t as PROFILE_RUNTIME_REPORT_NAME } from "./profile-project-
|
|
1
|
+
import { a as runtimeOverrides, i as installProfile, n as PROFILE_VENDOR_DIR_NAME, o as workspaceWithOverrides, r as PROFILE_WORKSPACE_NAME, t as PROFILE_RUNTIME_REPORT_NAME } from "./profile-project-BgQILDbu.mjs";
|
|
2
2
|
export { PROFILE_RUNTIME_REPORT_NAME, PROFILE_VENDOR_DIR_NAME, PROFILE_WORKSPACE_NAME, installProfile, runtimeOverrides, workspaceWithOverrides };
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { n as PROFILE_PATCH_NAME, t as PROFILE_NAME } from "./appconfig-
|
|
1
|
+
import { n as PROFILE_PATCH_NAME, t as PROFILE_NAME } from "./appconfig-cvjOFNqE.mjs";
|
|
2
2
|
import { access, chmod, copyFile, lstat, mkdir, readFile, readdir, readlink, rm, symlink, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
//#region src/seed.ts
|
|
5
5
|
const SEED_DIR_NAME = "dsh-home";
|
|
6
6
|
const SEED_HASH_NAME = ".seed-hash";
|
|
7
|
-
/** Seed-relative directory holding the immutable runtime: the host's dsh installation and the Web frontend. */
|
|
8
7
|
const SEED_RUNTIME_DIR_NAME = "runtime";
|
|
9
8
|
const SEED_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
10
9
|
".nub-store",
|
|
@@ -58,10 +57,6 @@ async function ensureSeedProfile(seedDir, home) {
|
|
|
58
57
|
}
|
|
59
58
|
return true;
|
|
60
59
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Plant one profile subtree. The seed's runtime lives beside the profile and stays in the
|
|
63
|
-
* application's read-only resources, so only `profiles/<name>` is copied into the home.
|
|
64
|
-
*/
|
|
65
60
|
async function copySeed(source, target) {
|
|
66
61
|
await mkdir(target, { recursive: true });
|
|
67
62
|
const visit = async (directory, prefix) => {
|
package/dist/seed.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as ensureSeedProfile, n as SEED_HASH_NAME, r as SEED_RUNTIME_DIR_NAME, t as SEED_DIR_NAME } from "./seed-
|
|
1
|
+
import { i as ensureSeedProfile, n as SEED_HASH_NAME, r as SEED_RUNTIME_DIR_NAME, t as SEED_DIR_NAME } from "./seed-DEd-kuvi.mjs";
|
|
2
2
|
export { SEED_DIR_NAME, SEED_HASH_NAME, SEED_RUNTIME_DIR_NAME, ensureSeedProfile };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morlay/dsh-desktop-shell",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Electron shell for dsh desktop apps: serves the app's own <name>:// scheme, boots the desktop profile through the @morlay/dsh-desktop-host child over byte pipes, and installs the profile seed on first launch. The CLI that packages a workspace around it is @morlay/dsh-desktopify.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"desktop",
|
|
@@ -35,17 +35,17 @@
|
|
|
35
35
|
"electron": "^44.3.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@deepseek-ai/dsh": "0.
|
|
39
|
-
"@deepseek-ai/dsh-app-boot": "0.
|
|
40
|
-
"@deepseek-ai/dsh-atomic-write": "0.
|
|
41
|
-
"@deepseek-ai/dsh-client-shortcuts": "0.
|
|
38
|
+
"@deepseek-ai/dsh": "0.2.0-rc.1",
|
|
39
|
+
"@deepseek-ai/dsh-app-boot": "0.2.0-rc.1",
|
|
40
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.0-rc.1",
|
|
41
|
+
"@deepseek-ai/dsh-client-shortcuts": "0.2.0-rc.1",
|
|
42
42
|
"@local/devkit": "0.1.0",
|
|
43
|
-
"@morlay/dsh-desktop-host": "0.2.
|
|
43
|
+
"@morlay/dsh-desktop-host": "0.2.2",
|
|
44
44
|
"@types/node": "^26.5.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@deepseek-ai/dsh-atomic-write": "^0.
|
|
48
|
-
"@deepseek-ai/dsh-client-shortcuts": "^0.
|
|
47
|
+
"@deepseek-ai/dsh-atomic-write": "^0.2.0-rc.1",
|
|
48
|
+
"@deepseek-ai/dsh-client-shortcuts": "^0.2.0-rc.1"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=24.11.0"
|