@morlay/dsh-desktop-shell 0.0.1-alpha.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 +21 -0
- package/README.md +34 -0
- package/dist/appconfig-B4kozO2F.mjs +43 -0
- package/dist/appconfig.mjs +2 -0
- package/dist/dshhome-C_iyJzc8.mjs +26 -0
- package/dist/dshhome.mjs +2 -0
- package/dist/index.mjs +1849 -0
- package/dist/official-C8RFHQyT.mjs +170 -0
- package/dist/official.mjs +2 -0
- package/dist/preload-app.cjs +182 -0
- package/dist/profile-project-CEAT8PEX.mjs +101 -0
- package/dist/profile-project.mjs +2 -0
- package/dist/seed-Bg2S14hM.mjs +99 -0
- package/dist/seed.mjs +2 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +57 -0
|
@@ -0,0 +1,170 @@
|
|
|
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
|
+
const OFFICIAL_PROFILE_PACKAGES = [
|
|
9
|
+
"@deepseek-ai/cordis-plugin-timer",
|
|
10
|
+
"@deepseek-ai/dsh-agent",
|
|
11
|
+
"@deepseek-ai/dsh-agent-default-model",
|
|
12
|
+
"@deepseek-ai/dsh-agent-loop",
|
|
13
|
+
"@deepseek-ai/dsh-agent-preset-registry",
|
|
14
|
+
"@deepseek-ai/dsh-api-account-controller",
|
|
15
|
+
"@deepseek-ai/dsh-api-gateway",
|
|
16
|
+
"@deepseek-ai/dsh-api-job-controller",
|
|
17
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
18
|
+
"@deepseek-ai/dsh-api-session-controller",
|
|
19
|
+
"@deepseek-ai/dsh-api-settings-controller",
|
|
20
|
+
"@deepseek-ai/dsh-api-terminal-controller",
|
|
21
|
+
"@deepseek-ai/dsh-api-workspace-controller",
|
|
22
|
+
"@deepseek-ai/dsh-api-workspace-files",
|
|
23
|
+
"@deepseek-ai/dsh-attachment-local",
|
|
24
|
+
"@deepseek-ai/dsh-authorization",
|
|
25
|
+
"@deepseek-ai/dsh-bash-sandbox",
|
|
26
|
+
"@deepseek-ai/dsh-client-connection",
|
|
27
|
+
"@deepseek-ai/dsh-client-file-upload",
|
|
28
|
+
"@deepseek-ai/dsh-client-hmr",
|
|
29
|
+
"@deepseek-ai/dsh-client-locale",
|
|
30
|
+
"@deepseek-ai/dsh-client-modules",
|
|
31
|
+
"@deepseek-ai/dsh-client-resources",
|
|
32
|
+
"@deepseek-ai/dsh-client-shortcuts",
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-agent-preset",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-approval",
|
|
35
|
+
"@deepseek-ai/dsh-client-ui-attachment",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-brand-official",
|
|
37
|
+
"@deepseek-ai/dsh-client-ui-chat",
|
|
38
|
+
"@deepseek-ai/dsh-client-ui-commands",
|
|
39
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
40
|
+
"@deepseek-ai/dsh-client-ui-cordis",
|
|
41
|
+
"@deepseek-ai/dsh-client-ui-deliverables",
|
|
42
|
+
"@deepseek-ai/dsh-client-ui-goal",
|
|
43
|
+
"@deepseek-ai/dsh-client-ui-input-trigger",
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-jobs",
|
|
45
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-message-feedback",
|
|
47
|
+
"@deepseek-ai/dsh-client-ui-model-selection",
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-open-in-app",
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-permission-presets",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-plan",
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager",
|
|
52
|
+
"@deepseek-ai/dsh-client-ui-reference",
|
|
53
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-session",
|
|
55
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-settings-account",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-settings-agent-loop",
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-settings-general",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-settings-models",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-settings-plugin-inventory",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-settings-shell",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-settings-subagent",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-settings-web-search",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-shortcuts",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-sidebar-browser",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-sidebar-documentpreview",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-sidebar-files",
|
|
70
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right",
|
|
71
|
+
"@deepseek-ai/dsh-client-ui-sidebar-terminal",
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-skill",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-subagent",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-theme",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-tool",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-trajectory",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-user-questions",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-workflow-run",
|
|
79
|
+
"@deepseek-ai/dsh-client-ui-workspace",
|
|
80
|
+
"@deepseek-ai/dsh-command-feedback",
|
|
81
|
+
"@deepseek-ai/dsh-commands",
|
|
82
|
+
"@deepseek-ai/dsh-compaction-image-offload",
|
|
83
|
+
"@deepseek-ai/dsh-config-editor",
|
|
84
|
+
"@deepseek-ai/dsh-cordis-client-runner",
|
|
85
|
+
"@deepseek-ai/dsh-cordis-host-runner",
|
|
86
|
+
"@deepseek-ai/dsh-credentials-local",
|
|
87
|
+
"@deepseek-ai/dsh-deepseek-account-platform",
|
|
88
|
+
"@deepseek-ai/dsh-deepseek-llm-api-extensions",
|
|
89
|
+
"@deepseek-ai/dsh-file-reference-local",
|
|
90
|
+
"@deepseek-ai/dsh-fs-observation-policy",
|
|
91
|
+
"@deepseek-ai/dsh-fs-sandbox",
|
|
92
|
+
"@deepseek-ai/dsh-goal",
|
|
93
|
+
"@deepseek-ai/dsh-goal-round-driver",
|
|
94
|
+
"@deepseek-ai/dsh-hmr",
|
|
95
|
+
"@deepseek-ai/dsh-host-directory-picker-auto",
|
|
96
|
+
"@deepseek-ai/dsh-host-open-in-app",
|
|
97
|
+
"@deepseek-ai/dsh-host-plugin-inventory",
|
|
98
|
+
"@deepseek-ai/dsh-host-webserver",
|
|
99
|
+
"@deepseek-ai/dsh-jobs-local",
|
|
100
|
+
"@deepseek-ai/dsh-llm",
|
|
101
|
+
"@deepseek-ai/dsh-llm-deepseek-account",
|
|
102
|
+
"@deepseek-ai/dsh-llm-deepseek-api-key",
|
|
103
|
+
"@deepseek-ai/dsh-llm-pi-ai",
|
|
104
|
+
"@deepseek-ai/dsh-llm-retry",
|
|
105
|
+
"@deepseek-ai/dsh-mcp-resources",
|
|
106
|
+
"@deepseek-ai/dsh-message-feedback",
|
|
107
|
+
"@deepseek-ai/dsh-office-to-pdf",
|
|
108
|
+
"@deepseek-ai/dsh-permission-presets",
|
|
109
|
+
"@deepseek-ai/dsh-plugin-manager",
|
|
110
|
+
"@deepseek-ai/dsh-plugin-package-inventory-deepseek",
|
|
111
|
+
"@deepseek-ai/dsh-ptc-runtime-node",
|
|
112
|
+
"@deepseek-ai/dsh-pwsh-sandbox",
|
|
113
|
+
"@deepseek-ai/dsh-repeat-tool-reminder",
|
|
114
|
+
"@deepseek-ai/dsh-sandbox-local",
|
|
115
|
+
"@deepseek-ai/dsh-sandbox-policy",
|
|
116
|
+
"@deepseek-ai/dsh-session",
|
|
117
|
+
"@deepseek-ai/dsh-session-checkpoint-policy",
|
|
118
|
+
"@deepseek-ai/dsh-session-log-deepseek",
|
|
119
|
+
"@deepseek-ai/dsh-session-log-export",
|
|
120
|
+
"@deepseek-ai/dsh-session-persistence-jsonl",
|
|
121
|
+
"@deepseek-ai/dsh-session-projection",
|
|
122
|
+
"@deepseek-ai/dsh-session-projection-cache",
|
|
123
|
+
"@deepseek-ai/dsh-session-query-sqlite",
|
|
124
|
+
"@deepseek-ai/dsh-session-reference",
|
|
125
|
+
"@deepseek-ai/dsh-session-stats",
|
|
126
|
+
"@deepseek-ai/dsh-session-telemetry-otel",
|
|
127
|
+
"@deepseek-ai/dsh-session-title",
|
|
128
|
+
"@deepseek-ai/dsh-session-title-first-prompt-llm",
|
|
129
|
+
"@deepseek-ai/dsh-session-turn-outline",
|
|
130
|
+
"@deepseek-ai/dsh-settings",
|
|
131
|
+
"@deepseek-ai/dsh-shell-env",
|
|
132
|
+
"@deepseek-ai/dsh-skill",
|
|
133
|
+
"@deepseek-ai/dsh-spill-local",
|
|
134
|
+
"@deepseek-ai/dsh-spill-policy",
|
|
135
|
+
"@deepseek-ai/dsh-storage",
|
|
136
|
+
"@deepseek-ai/dsh-storage-domain",
|
|
137
|
+
"@deepseek-ai/dsh-storage-json",
|
|
138
|
+
"@deepseek-ai/dsh-subagent",
|
|
139
|
+
"@deepseek-ai/dsh-subagent-fork-in-process",
|
|
140
|
+
"@deepseek-ai/dsh-subagent-spawn-in-process",
|
|
141
|
+
"@deepseek-ai/dsh-subprocess-local",
|
|
142
|
+
"@deepseek-ai/dsh-system-prompt",
|
|
143
|
+
"@deepseek-ai/dsh-token-meter",
|
|
144
|
+
"@deepseek-ai/dsh-tool-call-timeout-policy",
|
|
145
|
+
"@deepseek-ai/dsh-tool-cordis",
|
|
146
|
+
"@deepseek-ai/dsh-tool-subagent",
|
|
147
|
+
"@deepseek-ai/dsh-tools",
|
|
148
|
+
"@deepseek-ai/dsh-typert-loader",
|
|
149
|
+
"@deepseek-ai/dsh-typert-registry",
|
|
150
|
+
"@deepseek-ai/dsh-user-approval",
|
|
151
|
+
"@deepseek-ai/dsh-user-questions",
|
|
152
|
+
"@deepseek-ai/dsh-web",
|
|
153
|
+
"@deepseek-ai/dsh-web-app",
|
|
154
|
+
"@deepseek-ai/dsh-web-fetch-http",
|
|
155
|
+
"@deepseek-ai/dsh-web-search-deepseek",
|
|
156
|
+
"@deepseek-ai/dsh-workspace",
|
|
157
|
+
"@deepseek-ai/dsh-workspace-changes"
|
|
158
|
+
];
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/official.ts
|
|
161
|
+
/** 工具自带的桌面 host 变体包(不是上游 private 应用):部署里按这个名字落位,壳按 `<pkg>/lib/index.js` 启动。 */
|
|
162
|
+
const DESKTOP_HOST_PACKAGE = "@morlay/dsh-desktop-host";
|
|
163
|
+
const OFFICIAL_RUNTIME_PACKAGES = [
|
|
164
|
+
"@deepseek-ai/dsh",
|
|
165
|
+
DESKTOP_HOST_PACKAGE,
|
|
166
|
+
...OFFICIAL_PROFILE_PACKAGES
|
|
167
|
+
];
|
|
168
|
+
const OFFICIAL_PROFILE_BUNDLES = ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"];
|
|
169
|
+
//#endregion
|
|
170
|
+
export { OFFICIAL_PROFILE_PACKAGES as i, OFFICIAL_PROFILE_BUNDLES as n, OFFICIAL_RUNTIME_PACKAGES as r, DESKTOP_HOST_PACKAGE as t };
|
|
@@ -0,0 +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-C8RFHQyT.mjs";
|
|
2
|
+
export { DESKTOP_HOST_PACKAGE, OFFICIAL_PROFILE_BUNDLES, OFFICIAL_PROFILE_PACKAGES, OFFICIAL_RUNTIME_PACKAGES };
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
let electron = require("electron");
|
|
2
|
+
/** Web UI 写入的主题来源属性。 */
|
|
3
|
+
const THEME_SOURCE_ATTRIBUTE = "data-ds-theme-source";
|
|
4
|
+
function whenRootReady(root, run) {
|
|
5
|
+
if (root.documentElement === null) root.defaultView?.addEventListener("DOMContentLoaded", run, { once: true });
|
|
6
|
+
else run();
|
|
7
|
+
}
|
|
8
|
+
/** 标记页面平台:macOS 布局(侧边栏顶部拖拽条与 logo 下沉)据此启用。 */
|
|
9
|
+
function markDocumentPlatform(platform, root) {
|
|
10
|
+
whenRootReady(root, () => {
|
|
11
|
+
root.documentElement.dataset.platform = platform;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/** Windows 自绘 caption 标记:布局据此留出标题栏高度、把控制按钮位置让开。 */
|
|
15
|
+
function markWindowsTitlebar(platform, root) {
|
|
16
|
+
if (platform !== "win32") return;
|
|
17
|
+
whenRootReady(root, () => {
|
|
18
|
+
root.documentElement.dataset.windowsTitlebar = "";
|
|
19
|
+
root.documentElement.style.setProperty("--dsh-windows-titlebar-height", `${String(40)}px`);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** macOS:把主题来源转发给主进程,窗口 vibrancy 才会跟随应用配色。 */
|
|
23
|
+
function syncNativeTheme(platform, root, send) {
|
|
24
|
+
if (platform !== "darwin") return;
|
|
25
|
+
let sent;
|
|
26
|
+
const publish = () => {
|
|
27
|
+
const value = root.documentElement.getAttribute(THEME_SOURCE_ATTRIBUTE);
|
|
28
|
+
if (value === null || value === sent) return;
|
|
29
|
+
sent = value;
|
|
30
|
+
send(value);
|
|
31
|
+
};
|
|
32
|
+
const observe = () => {
|
|
33
|
+
new MutationObserver(publish).observe(root.documentElement, { attributeFilter: [THEME_SOURCE_ATTRIBUTE] });
|
|
34
|
+
publish();
|
|
35
|
+
};
|
|
36
|
+
if (root.readyState === "loading") root.defaultView?.addEventListener("DOMContentLoaded", observe, { once: true });
|
|
37
|
+
else observe();
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/ipc.ts
|
|
41
|
+
const DESKTOP_IPC = {
|
|
42
|
+
directoryPick: "dsh-desktop:directory-pick",
|
|
43
|
+
nativeThemeSet: "dsh-desktop:native-theme-set",
|
|
44
|
+
/** 桌面流载体:页面把 Gateway 的流请求交给主进程,主进程用标准 Request 喂宿主。 */
|
|
45
|
+
streamOpen: "dsh-desktop:stream-open",
|
|
46
|
+
streamCancel: "dsh-desktop:stream-cancel",
|
|
47
|
+
/** 逻辑流的上行项(客户端 → 宿主),主进程把它们写成宿主请求体的后续行。 */
|
|
48
|
+
streamUplink: "dsh-desktop:stream-uplink",
|
|
49
|
+
/** 上行结束:主进程据此结束宿主请求体。 */
|
|
50
|
+
streamUplinkEnd: "dsh-desktop:stream-uplink-end",
|
|
51
|
+
streamChunk: "dsh-desktop:stream-chunk",
|
|
52
|
+
streamEnd: "dsh-desktop:stream-end",
|
|
53
|
+
streamError: "dsh-desktop:stream-error",
|
|
54
|
+
/** 桌面快捷键:设备偏好的读写 / 录制状态(渲染进程 → 主进程)。 */
|
|
55
|
+
shortcutsGet: "dsh-desktop:shortcuts-get",
|
|
56
|
+
shortcutsEdit: "dsh-desktop:shortcuts-edit",
|
|
57
|
+
shortcutsRecording: "dsh-desktop:shortcuts-recording",
|
|
58
|
+
shortcutsCloseWindow: "dsh-desktop:shortcuts-close-window",
|
|
59
|
+
/** 命中的原生按键手势(主进程 → 渲染进程);渲染侧的 native keyboard 适配器消费。 */
|
|
60
|
+
shortcutsInput: "dsh-desktop:shortcuts-input",
|
|
61
|
+
/** 配置快照变化(主进程 → 渲染进程)。 */
|
|
62
|
+
shortcutsChanged: "dsh-desktop:shortcuts-changed"
|
|
63
|
+
};
|
|
64
|
+
/** preload 从渲染进程 argv 里读回 scheme 的参数名(主进程与 preload 共用一份)。 */
|
|
65
|
+
const DESKTOP_SCHEME_ARGUMENT = "--dsh-desktop-scheme";
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/stream-uplink.ts
|
|
68
|
+
/** 造一条上行通道;`send` / `end` 是主进程方向的投递函数。 */
|
|
69
|
+
function createStreamUplink(send, end) {
|
|
70
|
+
let streamId;
|
|
71
|
+
let closed = false;
|
|
72
|
+
const queued = [];
|
|
73
|
+
return {
|
|
74
|
+
bind(id) {
|
|
75
|
+
if (streamId !== void 0) throw new Error("dsh desktop: stream uplink is already bound");
|
|
76
|
+
streamId = id;
|
|
77
|
+
for (const item of queued) send(id, item);
|
|
78
|
+
queued.length = 0;
|
|
79
|
+
if (closed) end(id);
|
|
80
|
+
},
|
|
81
|
+
push(item) {
|
|
82
|
+
if (closed) return;
|
|
83
|
+
if (streamId === void 0) queued.push(item);
|
|
84
|
+
else send(streamId, item);
|
|
85
|
+
},
|
|
86
|
+
close() {
|
|
87
|
+
if (closed) return;
|
|
88
|
+
closed = true;
|
|
89
|
+
if (streamId !== void 0) end(streamId);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/preload-app.ts
|
|
95
|
+
/** scheme 取自 app 名,只有主进程知道;sandboxed preload 里只能从 argv 读回。 */
|
|
96
|
+
const scheme = process.argv.find((argument) => argument.startsWith(`${DESKTOP_SCHEME_ARGUMENT}=`))?.slice(21);
|
|
97
|
+
if (scheme !== void 0 && scheme !== "" && location.protocol === `${scheme}:` && location.hostname === "app") {
|
|
98
|
+
electron.contextBridge.exposeInMainWorld("__DSH_DIRECTORY_PICKER__", { pick: () => electron.ipcRenderer.invoke(DESKTOP_IPC.directoryPick) });
|
|
99
|
+
electron.contextBridge.exposeInMainWorld("__DSH_DESKTOP_STREAM__", { open(endpoint, payload, handlers) {
|
|
100
|
+
let id;
|
|
101
|
+
let cancelled = false;
|
|
102
|
+
const uplink = createStreamUplink((streamId, item) => {
|
|
103
|
+
electron.ipcRenderer.send(DESKTOP_IPC.streamUplink, streamId, item);
|
|
104
|
+
}, (streamId) => {
|
|
105
|
+
electron.ipcRenderer.send(DESKTOP_IPC.streamUplinkEnd, streamId);
|
|
106
|
+
});
|
|
107
|
+
const onChunk = (_event, value) => {
|
|
108
|
+
if (value.id !== id || value.data === void 0) return;
|
|
109
|
+
handlers.chunk(value.data);
|
|
110
|
+
};
|
|
111
|
+
const onEnd = (_event, value) => {
|
|
112
|
+
if (value.id === id) handlers.end();
|
|
113
|
+
};
|
|
114
|
+
const onError = (_event, value) => {
|
|
115
|
+
if (value.id === id) handlers.fail(value.message ?? "desktop stream failed");
|
|
116
|
+
};
|
|
117
|
+
electron.ipcRenderer.on(DESKTOP_IPC.streamChunk, onChunk);
|
|
118
|
+
electron.ipcRenderer.on(DESKTOP_IPC.streamEnd, onEnd);
|
|
119
|
+
electron.ipcRenderer.on(DESKTOP_IPC.streamError, onError);
|
|
120
|
+
electron.ipcRenderer.invoke(DESKTOP_IPC.streamOpen, endpoint, payload).then((opened) => {
|
|
121
|
+
id = opened;
|
|
122
|
+
if (cancelled) {
|
|
123
|
+
electron.ipcRenderer.send(DESKTOP_IPC.streamCancel, id);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
uplink.bind(id);
|
|
127
|
+
}).catch((error) => {
|
|
128
|
+
handlers.fail(error instanceof Error ? error.message : String(error));
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
cancel() {
|
|
132
|
+
if (cancelled) return;
|
|
133
|
+
cancelled = true;
|
|
134
|
+
electron.ipcRenderer.off(DESKTOP_IPC.streamChunk, onChunk);
|
|
135
|
+
electron.ipcRenderer.off(DESKTOP_IPC.streamEnd, onEnd);
|
|
136
|
+
electron.ipcRenderer.off(DESKTOP_IPC.streamError, onError);
|
|
137
|
+
if (id !== void 0) electron.ipcRenderer.send(DESKTOP_IPC.streamCancel, id);
|
|
138
|
+
},
|
|
139
|
+
send(item) {
|
|
140
|
+
if (!cancelled) uplink.push(item);
|
|
141
|
+
},
|
|
142
|
+
end() {
|
|
143
|
+
if (!cancelled) uplink.close();
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
} });
|
|
147
|
+
}
|
|
148
|
+
markDocumentPlatform(process.platform, document);
|
|
149
|
+
markWindowsTitlebar(process.platform, document);
|
|
150
|
+
electron.contextBridge.exposeInMainWorld("dshDesktop", {
|
|
151
|
+
protocolVersion: 1,
|
|
152
|
+
keyboard: {
|
|
153
|
+
subscribe(listener) {
|
|
154
|
+
const handle = (_event, input) => {
|
|
155
|
+
listener(input);
|
|
156
|
+
};
|
|
157
|
+
electron.ipcRenderer.on(DESKTOP_IPC.shortcutsInput, handle);
|
|
158
|
+
return () => {
|
|
159
|
+
electron.ipcRenderer.off(DESKTOP_IPC.shortcutsInput, handle);
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
closeWindow: (revision) => electron.ipcRenderer.invoke(DESKTOP_IPC.shortcutsCloseWindow, revision)
|
|
163
|
+
},
|
|
164
|
+
shortcuts: {
|
|
165
|
+
get: (definitions) => electron.ipcRenderer.invoke(DESKTOP_IPC.shortcutsGet, definitions),
|
|
166
|
+
edit: (edit, revision) => electron.ipcRenderer.invoke(DESKTOP_IPC.shortcutsEdit, edit, revision),
|
|
167
|
+
subscribe(listener) {
|
|
168
|
+
const handle = (_event, snapshot) => {
|
|
169
|
+
listener(snapshot);
|
|
170
|
+
};
|
|
171
|
+
electron.ipcRenderer.on(DESKTOP_IPC.shortcutsChanged, handle);
|
|
172
|
+
return () => {
|
|
173
|
+
electron.ipcRenderer.off(DESKTOP_IPC.shortcutsChanged, handle);
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
recording: (active) => electron.ipcRenderer.invoke(DESKTOP_IPC.shortcutsRecording, active)
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
syncNativeTheme(process.platform, document, (source) => {
|
|
180
|
+
electron.ipcRenderer.send(DESKTOP_IPC.nativeThemeSet, source);
|
|
181
|
+
});
|
|
182
|
+
//#endregion
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
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
|
+
const PROFILE_WORKSPACE_NAME = "pnpm-workspace.yaml";
|
|
13
|
+
/** Seed-written report naming the runtime packages the profile resolves through `overrides`. */
|
|
14
|
+
const PROFILE_RUNTIME_REPORT_NAME = "desktop-runtime-packages.json";
|
|
15
|
+
/** Profile directory holding the `file:` sources of the profile's own bundles. */
|
|
16
|
+
const PROFILE_VENDOR_DIR_NAME = "vendor";
|
|
17
|
+
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
|
+
async function runtimeOverrides(profileDir, runtimeDir) {
|
|
26
|
+
const entries = JSON.parse(await readFile(join(profileDir, PROFILE_RUNTIME_REPORT_NAME), "utf8")).runtimePackages;
|
|
27
|
+
if (!Array.isArray(entries) || !entries.every(isRuntimePackage)) throw new Error(`dsh desktop: ${PROFILE_RUNTIME_REPORT_NAME} has an invalid runtimePackages list`);
|
|
28
|
+
return entries.map((entry) => ({
|
|
29
|
+
name: entry.name,
|
|
30
|
+
target: join(runtimeDir, ...entry.path.split("/"))
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
/** One reported runtime package: its dependency name and its runtime-relative directory. */
|
|
34
|
+
function isRuntimePackage(value) {
|
|
35
|
+
if (typeof value !== "object" || value === null) return false;
|
|
36
|
+
const candidate = value;
|
|
37
|
+
return typeof candidate.name === "string" && candidate.name !== "" && typeof candidate.path === "string" && candidate.path !== "";
|
|
38
|
+
}
|
|
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
|
+
function workspaceWithOverrides(text, overrides) {
|
|
48
|
+
if (overrides.length === 0) return text;
|
|
49
|
+
const kept = [];
|
|
50
|
+
let skipping = false;
|
|
51
|
+
for (const line of text.replaceAll("\r\n", "\n").split("\n")) {
|
|
52
|
+
const key = TOP_LEVEL_KEY.exec(line)?.[1];
|
|
53
|
+
if (key !== void 0) skipping = key === "overrides";
|
|
54
|
+
if (!skipping) kept.push(line);
|
|
55
|
+
}
|
|
56
|
+
while (kept.length > 0 && (kept[kept.length - 1] ?? "").trim() === "") kept.pop();
|
|
57
|
+
const block = ["overrides:", ...overrides.map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(`link:${entry.target}`)}`)];
|
|
58
|
+
return `${[...kept, ...block].join("\n")}\n`;
|
|
59
|
+
}
|
|
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
|
+
function installProfile(options) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const child = (options.spawn ?? spawn)(options.node, [
|
|
68
|
+
options.pnpmEntry,
|
|
69
|
+
"install",
|
|
70
|
+
"--prod",
|
|
71
|
+
"--ignore-scripts",
|
|
72
|
+
"--offline"
|
|
73
|
+
], {
|
|
74
|
+
cwd: options.profileDir,
|
|
75
|
+
stdio: [
|
|
76
|
+
"ignore",
|
|
77
|
+
"pipe",
|
|
78
|
+
"pipe"
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
let stderr = "";
|
|
82
|
+
child.stderr?.setEncoding("utf8");
|
|
83
|
+
child.stderr?.on("data", (chunk) => {
|
|
84
|
+
stderr = (stderr + chunk).slice(-4096);
|
|
85
|
+
});
|
|
86
|
+
child.stdout?.on("data", () => void 0);
|
|
87
|
+
child.once("error", (error) => {
|
|
88
|
+
reject(error);
|
|
89
|
+
});
|
|
90
|
+
child.once("close", (code) => {
|
|
91
|
+
if (code === 0) {
|
|
92
|
+
resolve();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const diagnostic = stderr.trim();
|
|
96
|
+
reject(/* @__PURE__ */ new Error(`dsh desktop: profile install exited with ${String(code)}${diagnostic === "" ? "" : `: ${diagnostic}`}`));
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { runtimeOverrides as a, installProfile as i, PROFILE_VENDOR_DIR_NAME as n, workspaceWithOverrides as o, PROFILE_WORKSPACE_NAME as r, PROFILE_RUNTIME_REPORT_NAME as t };
|
|
@@ -0,0 +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-CEAT8PEX.mjs";
|
|
2
|
+
export { PROFILE_RUNTIME_REPORT_NAME, PROFILE_VENDOR_DIR_NAME, PROFILE_WORKSPACE_NAME, installProfile, runtimeOverrides, workspaceWithOverrides };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { n as PROFILE_PATCH_NAME, t as PROFILE_NAME } from "./appconfig-B4kozO2F.mjs";
|
|
2
|
+
import { access, chmod, copyFile, lstat, mkdir, readFile, readdir, readlink, rm, symlink, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
//#region src/seed.ts
|
|
5
|
+
const SEED_DIR_NAME = "dsh-home";
|
|
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
|
+
const SEED_RUNTIME_DIR_NAME = "runtime";
|
|
9
|
+
const SEED_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
10
|
+
".nub-store",
|
|
11
|
+
".store",
|
|
12
|
+
".nub"
|
|
13
|
+
]);
|
|
14
|
+
async function pathExists(path) {
|
|
15
|
+
try {
|
|
16
|
+
await access(path);
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function readSeedHash(path) {
|
|
23
|
+
try {
|
|
24
|
+
return await readFile(path, "utf8");
|
|
25
|
+
} catch {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function isDirectory(path) {
|
|
30
|
+
try {
|
|
31
|
+
return (await lstat(path)).isDirectory();
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function ensureSeedProfile(seedDir, home) {
|
|
37
|
+
const seedProfile = join(seedDir, "profiles", PROFILE_NAME);
|
|
38
|
+
if (!await isDirectory(seedProfile)) return false;
|
|
39
|
+
const profileDir = join(home, "profiles", PROFILE_NAME);
|
|
40
|
+
const seedHash = await readSeedHash(join(seedProfile, SEED_HASH_NAME));
|
|
41
|
+
if (seedHash !== "") try {
|
|
42
|
+
if (!(await lstat(profileDir)).isSymbolicLink() && await readSeedHash(join(profileDir, ".seed-hash")) === seedHash) return false;
|
|
43
|
+
} catch {}
|
|
44
|
+
const patchPath = join(profileDir, PROFILE_PATCH_NAME);
|
|
45
|
+
const userPatch = await readFile(patchPath, "utf8").catch(() => void 0);
|
|
46
|
+
try {
|
|
47
|
+
const stat = await lstat(profileDir);
|
|
48
|
+
if (stat.isSymbolicLink()) await unlink(profileDir);
|
|
49
|
+
else if (stat.isDirectory()) await rm(profileDir, { recursive: true });
|
|
50
|
+
else await unlink(profileDir);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error.code !== "ENOENT") throw error;
|
|
53
|
+
}
|
|
54
|
+
await copySeed(seedProfile, profileDir);
|
|
55
|
+
if (userPatch !== void 0) {
|
|
56
|
+
await mkdir(dirname(patchPath), { recursive: true });
|
|
57
|
+
await writeFile(patchPath, userPatch);
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
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
|
+
async function copySeed(source, target) {
|
|
66
|
+
await mkdir(target, { recursive: true });
|
|
67
|
+
const visit = async (directory, prefix) => {
|
|
68
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
69
|
+
const path = join(directory, entry.name);
|
|
70
|
+
const relativePath = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
71
|
+
const destination = join(target, ...relativePath.split("/"));
|
|
72
|
+
if (entry.isSymbolicLink()) {
|
|
73
|
+
if (await pathExists(destination)) continue;
|
|
74
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
75
|
+
await symlink(await readlink(path), destination, process.platform === "win32" ? "junction" : "dir");
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
let stat;
|
|
79
|
+
try {
|
|
80
|
+
stat = await lstat(path);
|
|
81
|
+
} catch {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (stat.isDirectory()) {
|
|
85
|
+
if (SEED_SKIP_DIRS.has(entry.name)) continue;
|
|
86
|
+
await mkdir(destination, { recursive: true });
|
|
87
|
+
await visit(path, relativePath);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!stat.isFile() || await pathExists(destination)) continue;
|
|
91
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
92
|
+
await copyFile(path, destination);
|
|
93
|
+
await chmod(destination, stat.mode & 511);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
await visit(source, "");
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { ensureSeedProfile as i, SEED_HASH_NAME as n, SEED_RUNTIME_DIR_NAME as r, SEED_DIR_NAME as t };
|
package/dist/seed.mjs
ADDED
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@morlay/dsh-desktop-shell",
|
|
3
|
+
"version": "0.0.1-alpha.2",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"desktop",
|
|
7
|
+
"dsh",
|
|
8
|
+
"electron",
|
|
9
|
+
"shell"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "morlay",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/morlay/dsh-plugin.git"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"locale/*.json",
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.mjs",
|
|
23
|
+
"module": "./dist/index.mjs",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./dist/index.mjs",
|
|
26
|
+
"./appconfig": "./dist/appconfig.mjs",
|
|
27
|
+
"./dshhome": "./dist/dshhome.mjs",
|
|
28
|
+
"./official": "./dist/official.mjs",
|
|
29
|
+
"./profile-project": "./dist/profile-project.mjs",
|
|
30
|
+
"./seed": "./dist/seed.mjs",
|
|
31
|
+
"./package.json": "./package.json",
|
|
32
|
+
"./locale/*.json": "./locale/*.json"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"electron": "^44.3.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@deepseek-ai/dsh": "0.1.7-rc.2",
|
|
39
|
+
"@deepseek-ai/dsh-app-boot": "0.1.7-rc.2",
|
|
40
|
+
"@deepseek-ai/dsh-atomic-write": "0.1.7-rc.2",
|
|
41
|
+
"@deepseek-ai/dsh-client-shortcuts": "0.1.7-rc.2",
|
|
42
|
+
"@local/devkit": "0.1.0",
|
|
43
|
+
"@morlay/dsh-desktop-host": "0.0.1-alpha.9",
|
|
44
|
+
"@types/node": "^26.5.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@deepseek-ai/dsh-atomic-write": "^0.1.7-rc.2",
|
|
48
|
+
"@deepseek-ai/dsh-client-shortcuts": "^0.1.7-rc.2"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=24.11.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "pnpm exec tsdown",
|
|
55
|
+
"gen:official-packages": "tsx scripts/gen-official-packages.ts"
|
|
56
|
+
}
|
|
57
|
+
}
|