@morlay/dsh-desktop-shell 0.2.5 → 0.2.7

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.
Files changed (51) hide show
  1. package/README.md +4 -4
  2. package/dist/{appconfig-BZ9wPROk.mjs → appconfig-CSZh3JG7.mjs} +2 -2
  3. package/dist/appconfig.d.mts +21 -0
  4. package/dist/appconfig.mjs +1 -2
  5. package/dist/{dshhome-Dp89FOr-.mjs → dshhome-DAJ9dkn2.mjs} +2 -2
  6. package/dist/dshhome.d.mts +6 -0
  7. package/dist/dshhome.mjs +1 -2
  8. package/dist/index.d.mts +6 -0
  9. package/dist/index.mjs +1050 -139
  10. package/dist/inline-packages.d.mts +14 -0
  11. package/dist/inline-packages.mjs +21 -0
  12. package/dist/official-BmgBsdhq.mjs +5 -0
  13. package/dist/official.d.mts +4 -0
  14. package/dist/official.mjs +1 -2
  15. package/dist/preload-app.cjs +5 -1
  16. package/dist/profile-project.d.mts +20 -0
  17. package/dist/profile-project.mjs +1 -2
  18. package/dist/{seed-BF71UAuI.mjs → seed-Bxr0BsCk.mjs} +52 -5
  19. package/dist/seed.d.mts +21 -0
  20. package/dist/seed.mjs +1 -2
  21. package/package.json +44 -22
  22. package/src/__tests__/developer-endpoint.spec.ts +17 -0
  23. package/src/__tests__/document-marks.spec.ts +42 -0
  24. package/src/__tests__/dshhome.spec.ts +108 -0
  25. package/src/__tests__/host-process.spec.ts +205 -0
  26. package/src/__tests__/ipc.spec.ts +39 -0
  27. package/src/__tests__/profile-project.spec.ts +204 -0
  28. package/src/__tests__/seed-profile.spec.ts +189 -0
  29. package/src/__tests__/shell-env.spec.ts +131 -0
  30. package/src/__tests__/shell-import-boundary.spec.ts +69 -0
  31. package/src/__tests__/stream-uplink.spec.ts +49 -0
  32. package/src/appconfig.ts +79 -0
  33. package/src/developer-endpoint.ts +30 -0
  34. package/src/developer-menu.ts +198 -0
  35. package/src/directory-picker.ts +37 -0
  36. package/src/document-marks.ts +58 -0
  37. package/src/dshhome.ts +29 -0
  38. package/src/electron.ts +33 -0
  39. package/src/host-process.ts +533 -0
  40. package/src/index.ts +716 -0
  41. package/src/inline-packages.ts +18 -0
  42. package/src/ipc.ts +58 -0
  43. package/src/keybindings.ts +35 -0
  44. package/src/official.ts +8 -0
  45. package/src/preload-app.ts +147 -0
  46. package/src/profile-project.ts +138 -0
  47. package/src/seed.ts +183 -0
  48. package/src/shell-env.ts +31 -0
  49. package/src/shortcuts.ts +236 -0
  50. package/src/stream-uplink.ts +41 -0
  51. package/dist/official-Cz9Mry1n.mjs +0 -167
package/dist/index.mjs CHANGED
@@ -1,16 +1,42 @@
1
- import { r as loadAppConfig } from "./appconfig-BZ9wPROk.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-BF71UAuI.mjs";
6
- import { access, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
1
+ import { t as DESKTOP_HOST_PACKAGE } from "./official-BmgBsdhq.mjs";
2
+ import { r as loadAppConfig, t as PROFILE_NAME } from "./appconfig-CSZh3JG7.mjs";
3
+ import { n as resolveDshHome } from "./dshhome-DAJ9dkn2.mjs";
4
+ import { a as ensureSeedProfile, i as SEED_RUNTIME_DIR_NAME } from "./seed-Bxr0BsCk.mjs";
5
+ import { createRequire } from "node:module";
6
+ import { access, appendFile, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
7
7
  import { basename, dirname, join, resolve } from "node:path";
8
- import { fileURLToPath } from "node:url";
9
- import { BrowserWindow, app, dialog, ipcMain, nativeTheme, protocol } from "electron";
8
+ import { format } from "node:util";
9
+ import "rolldown";
10
10
  import { spawn } from "node:child_process";
11
11
  import { once } from "node:events";
12
- import { Readable, Writable } from "node:stream";
13
12
  import { randomBytes } from "node:crypto";
13
+ //#region src/electron.ts
14
+ /**
15
+ * `electron` 模块的**唯一入口**(壳自己的其它文件都从这里取)。
16
+ *
17
+ * 壳产物是 **ESM**(`.mjs`,Electron 28+ 支持 ESM 入口),而 `electron` 在 ESM 下**只有默认导出**:具名导入
18
+ * 会抛 `SyntaxError: The requested module 'electron' does not provide an export named 'BrowserWindow'`,
19
+ * 主进程在 `load` 阶段就没了。更麻烦的是**打包器会把"默认导入 + 解构 / 取属性"优化回具名导入**——那样又回到
20
+ * 同一个错误。
21
+ *
22
+ * 所以这里**走运行期 `require`**:壳就是 Electron 主进程,`require("electron")` 正是它给这个模块的原生形态,
23
+ * 打包器碰不到它。类型仍从 `electron` 取(类型导入会被擦除,不影响运行)。
24
+ */
25
+ const electron = createRequire(import.meta.url)("electron");
26
+ const app = electron.app;
27
+ const BrowserWindow = electron.BrowserWindow;
28
+ const dialog = electron.dialog;
29
+ const ipcMain = electron.ipcMain;
30
+ const nativeTheme = electron.nativeTheme;
31
+ const protocol = electron.protocol;
32
+ const Menu = electron.Menu;
33
+ const MenuItem = electron.MenuItem;
34
+ //#endregion
35
+ //#region ../dsh-desktop-host/src/preload-source.ts
36
+ /** 壳取 preload 的路径(`GET` 它得到可执行的 CJS 字节)。 */
37
+ const PRELOAD_SOURCE_PATH = "/@dsh/preload.cjs";
38
+ join("dist", "preload.cjs");
39
+ //#endregion
14
40
  //#region src/ipc.ts
15
41
  const DESKTOP_IPC = {
16
42
  directoryPick: "dsh-desktop:directory-pick",
@@ -61,6 +87,179 @@ function installDesktopDirectoryPicker(getWindow, scheme) {
61
87
  return result;
62
88
  });
63
89
  }
90
+ //#endregion
91
+ //#region ../dsh-desktop-host/src/paths.ts
92
+ /** 应用 origin 下托管调试窗口前端产物的前缀。 */
93
+ const DEVTOOLS_ASSETS_PREFIX = "/node-devtools";
94
+ //#endregion
95
+ //#region src/developer-endpoint.ts
96
+ /** DevTools 前端在托管前缀下的入口路径(与上游底部面板用的是同一份产物)。 */
97
+ const DEVTOOLS_ENTRY = "devtools_app.html";
98
+ /**
99
+ * 把 host 回报的调试端点拼成应用 origin 下的 DevTools 入口。
100
+ * @param scheme - 应用自定义协议(壳派生自 app 名)。
101
+ * @param endpoint - host 回报的 WebSocket 端点(`ws://127.0.0.1:<port>/<uuid>`)。
102
+ * @returns 可在窗口里加载的入口 URL。
103
+ * @throws 端点不是 WebSocket 端点,或它的值不能原样进查询串。
104
+ */
105
+ function nodeInspectorDevtoolsUrl(scheme, endpoint) {
106
+ const target = new URL(endpoint);
107
+ if (target.protocol !== "ws:" && target.protocol !== "wss:") throw new Error(`dsh desktop: the inspector endpoint is not a WebSocket url: ${endpoint}`);
108
+ if (target.search !== "") throw new Error(`dsh desktop: the inspector endpoint must not carry a query: ${endpoint}`);
109
+ const socket = `${target.host}${target.pathname}`;
110
+ if (!/^[\u0021-\u007e]+$/u.test(socket) || socket.includes("&") || socket.includes("#")) throw new Error(`dsh desktop: the inspector endpoint is not usable in a query: ${endpoint}`);
111
+ return `${scheme}://app${DEVTOOLS_ASSETS_PREFIX}/${DEVTOOLS_ENTRY}?ws=${socket}&panel=console&disableLocaleInfoBar=true`;
112
+ }
113
+ //#endregion
114
+ //#region src/developer-menu.ts
115
+ function reportFailure(options, title, detail) {
116
+ console.error(`[dsh-shell] ${title}: ${detail}`);
117
+ const parent = options.mainWindow();
118
+ if (parent === void 0 || parent.isDestroyed()) dialog.showErrorBox(title, detail);
119
+ else dialog.showMessageBox(parent, {
120
+ type: "error",
121
+ message: title,
122
+ detail
123
+ });
124
+ }
125
+ function insertIntoViewMenu(menu, toggle) {
126
+ const view = menu?.items.find((entry) => entry.role === "viewMenu")?.submenu;
127
+ if (view === void 0) return false;
128
+ const devtools = view.items.findIndex((entry) => entry.role === "toggleDevTools");
129
+ const at = devtools === -1 ? view.items.length : devtools + 1;
130
+ view.insert(at, new MenuItem({ type: "separator" }));
131
+ view.insert(at + 1, new MenuItem(toggle));
132
+ return true;
133
+ }
134
+ function buildFallbackMenu(toggle) {
135
+ const template = [
136
+ ...process.platform === "darwin" ? [{ role: "appMenu" }] : [],
137
+ { role: "fileMenu" },
138
+ { role: "editMenu" },
139
+ {
140
+ label: "View",
141
+ submenu: [
142
+ { role: "reload" },
143
+ { role: "forceReload" },
144
+ { role: "toggleDevTools" },
145
+ { type: "separator" },
146
+ toggle,
147
+ { type: "separator" },
148
+ { role: "resetZoom" },
149
+ { role: "zoomIn" },
150
+ { role: "zoomOut" },
151
+ { type: "separator" },
152
+ { role: "togglefullscreen" }
153
+ ]
154
+ },
155
+ { role: "windowMenu" }
156
+ ];
157
+ return Menu.buildFromTemplate(template);
158
+ }
159
+ /**
160
+ * 把「后端调试(Node Inspector)」挂进应用菜单的 View 里,并管理它那个 DevTools 窗口。
161
+ * 勾选 ⇔ 端点开着 ⇔ 窗口在:关窗口、取消勾选、host 回报端点没了,三条都收敛到同一状态。
162
+ * @param options - 协议、app 名与 host 取值函数。
163
+ * @returns 退订与收尾。
164
+ */
165
+ function installDeveloperMenu(options) {
166
+ let window;
167
+ let closingWindow = false;
168
+ let releaseInspected;
169
+ let toggleItem;
170
+ const setChecked = (checked) => {
171
+ if (toggleItem !== void 0) toggleItem.checked = checked;
172
+ };
173
+ function closeWindow() {
174
+ const current = window;
175
+ window = void 0;
176
+ if (current === void 0 || current.isDestroyed()) return;
177
+ closingWindow = true;
178
+ current.close();
179
+ }
180
+ function closeInspectorHost() {
181
+ releaseInspected?.();
182
+ releaseInspected = void 0;
183
+ closeWindow();
184
+ try {
185
+ options.host()?.endInspect();
186
+ } catch (error) {
187
+ console.error("[dsh-shell] host inspector shutdown failed", error);
188
+ }
189
+ }
190
+ function openWindow(endpoint) {
191
+ const current = window;
192
+ if (current !== void 0 && !current.isDestroyed()) {
193
+ current.focus();
194
+ return;
195
+ }
196
+ let target;
197
+ try {
198
+ target = nodeInspectorDevtoolsUrl(options.scheme, endpoint);
199
+ } catch (error) {
200
+ setChecked(false);
201
+ releaseInspected?.();
202
+ releaseInspected = void 0;
203
+ reportFailure(options, "后端调试", error instanceof Error ? error.message : String(error));
204
+ return;
205
+ }
206
+ const created = new BrowserWindow({
207
+ width: 1280,
208
+ height: 820,
209
+ title: `${options.appName} · Node Inspector`,
210
+ backgroundColor: "#202124"
211
+ });
212
+ window = created;
213
+ created.on("closed", () => {
214
+ if (window === created) window = void 0;
215
+ const closingOurselves = closingWindow;
216
+ closingWindow = false;
217
+ if (closingOurselves) return;
218
+ closeInspectorHost();
219
+ setChecked(false);
220
+ });
221
+ created.loadURL(target);
222
+ }
223
+ const toggle = {
224
+ id: "desktop-host-inspector",
225
+ label: "后端调试(Node Inspector)",
226
+ type: "checkbox",
227
+ checked: false,
228
+ click: (menu) => {
229
+ toggleItem = menu;
230
+ if (!menu.checked) {
231
+ closeInspectorHost();
232
+ return;
233
+ }
234
+ const host = options.host();
235
+ if (host === void 0) {
236
+ setChecked(false);
237
+ reportFailure(options, "后端调试", "后端进程还没就绪,稍后再试。");
238
+ return;
239
+ }
240
+ releaseInspected?.();
241
+ releaseInspected = host.onInspected((state) => {
242
+ if (state.url !== null) {
243
+ openWindow(state.url);
244
+ return;
245
+ }
246
+ setChecked(false);
247
+ closeWindow();
248
+ if (state.message !== void 0) reportFailure(options, "后端调试端点没有开起来", state.message);
249
+ });
250
+ try {
251
+ host.inspect(0);
252
+ } catch (error) {
253
+ releaseInspected?.();
254
+ releaseInspected = void 0;
255
+ setChecked(false);
256
+ reportFailure(options, "后端调试", error instanceof Error ? error.message : String(error));
257
+ }
258
+ }
259
+ };
260
+ if (!insertIntoViewMenu(Menu.getApplicationMenu(), toggle)) Menu.setApplicationMenu(buildFallbackMenu(toggle));
261
+ return { dispose: closeInspectorHost };
262
+ }
64
263
  const DESKTOP_STREAM_PATH = "/.dsh/remote-stream";
65
264
  function encodeDesktopStreamOpen(endpoint, payload) {
66
265
  return `${JSON.stringify({
@@ -206,6 +405,7 @@ function isDesktopHostEvent(value) {
206
405
  if (!isRecord(value)) return false;
207
406
  if (value.type === "ready") return value.protocolVersion === 1;
208
407
  if (value.type === "fatal") return typeof value.message === "string" && (value.diagnostic === void 0 || typeof value.diagnostic === "string");
408
+ if (value.type === "inspected") return (value.url === null || typeof value.url === "string") && (value.message === void 0 || typeof value.message === "string");
209
409
  return false;
210
410
  }
211
411
  //#endregion
@@ -230,7 +430,7 @@ async function exitsWithin(exit, milliseconds) {
230
430
  var DesktopHostProcess = class {
231
431
  node;
232
432
  runtimeDir;
233
- projectDir;
433
+ profileDir;
234
434
  inspectPort;
235
435
  options;
236
436
  child;
@@ -241,6 +441,7 @@ var DesktopHostProcess = class {
241
441
  nextStreamId = 1;
242
442
  pending = /* @__PURE__ */ new Map();
243
443
  blockedResponses = /* @__PURE__ */ new Set();
444
+ inspectedListeners = /* @__PURE__ */ new Set();
244
445
  readyResolve;
245
446
  readyReject;
246
447
  readyPromise = new Promise((resolve, reject) => {
@@ -251,16 +452,16 @@ var DesktopHostProcess = class {
251
452
  stderr = "";
252
453
  failureReported = false;
253
454
  stopping = false;
254
- constructor(node, runtimeDir, projectDir, inspectPort, options = {}) {
455
+ constructor(node, runtimeDir, profileDir, inspectPort, options) {
255
456
  this.node = node;
256
457
  this.runtimeDir = runtimeDir;
257
- this.projectDir = projectDir;
458
+ this.profileDir = profileDir;
258
459
  this.inspectPort = inspectPort;
259
460
  this.options = options;
260
461
  }
261
462
  async start() {
262
463
  if (this.child !== void 0) return this.readyPromise;
263
- const entry = join(this.runtimeDir, "node_modules", ...DESKTOP_HOST_PACKAGE.split("/"), "lib", "index.js");
464
+ const entry = this.options.entry;
264
465
  const packageManager = this.options.packageManager;
265
466
  const args = [
266
467
  "--expose-internals",
@@ -268,58 +469,46 @@ var DesktopHostProcess = class {
268
469
  ...this.inspectPort === void 0 ? [] : [`--inspect=127.0.0.1:${String(this.inspectPort)}`],
269
470
  ...this.options.nodeArgs ?? [],
270
471
  entry,
472
+ "--profile-dir",
473
+ this.profileDir,
474
+ "--runtime-dir",
271
475
  this.runtimeDir,
272
- this.projectDir,
273
- ...packageManager === void 0 ? [] : [packageManager.pnpm, packageManager.nodeBin]
476
+ ...packageManager === void 0 ? [] : [
477
+ "--pnpm",
478
+ packageManager.pnpm,
479
+ "--node-bin",
480
+ packageManager.nodeBin
481
+ ]
274
482
  ];
275
483
  const env = {
276
484
  ...Object.fromEntries(Object.entries(process.env).filter(([name]) => name !== "NODE_OPTIONS" && !name.startsWith("DSH_DESKTOP_") && !/^(?:npm|pnpm|corepack)_/iu.test(name))),
277
485
  ...this.options.extraEnv
278
486
  };
279
487
  const child = (this.options.spawn ?? spawn)(this.node, args, {
280
- cwd: this.projectDir,
488
+ cwd: this.profileDir,
281
489
  env,
282
490
  stdio: [
283
491
  "ignore",
284
492
  "pipe",
285
493
  "pipe",
286
- "pipe",
287
- "pipe",
288
494
  "ipc"
289
495
  ]
290
496
  });
291
- const requestPipe = child.stdio[3];
292
- const responsePipe = child.stdio[4];
293
- if (!(requestPipe instanceof Writable) || !(responsePipe instanceof Readable)) {
294
- child.kill("SIGTERM");
295
- throw new Error("dsh desktop host did not expose the required byte pipes and IPC channel");
296
- }
297
497
  this.child = child;
298
- this.requestPipe = requestPipe;
299
- this.responsePipe = responsePipe;
498
+ const persist = (chunk) => {
499
+ if (this.options.logFile === void 0) return;
500
+ appendFile(this.options.logFile, chunk).catch(() => void 0);
501
+ };
300
502
  child.stderr?.setEncoding("utf8");
301
503
  child.stderr?.on("data", (chunk) => {
302
504
  this.stderr = (this.stderr + chunk).slice(-65536);
303
505
  process.stderr.write(`[dsh-host] ${chunk}`);
506
+ persist(chunk);
304
507
  });
305
- child.stdout?.pipe(process.stdout);
306
- responsePipe.on("data", (chunk) => {
307
- this.acceptResponseBytes(chunk);
308
- });
309
- responsePipe.once("end", () => {
310
- try {
311
- this.responseDecoder.finish();
312
- } catch (error) {
313
- this.fail(errorOf(error, "dsh desktop host response pipe failed"));
314
- return;
315
- }
316
- this.fail(/* @__PURE__ */ new Error("dsh desktop host response pipe ended"));
317
- });
318
- requestPipe.once("error", (error) => {
319
- this.fail(error);
320
- });
321
- responsePipe.once("error", (error) => {
322
- this.fail(error);
508
+ child.stdout?.setEncoding("utf8");
509
+ child.stdout?.on("data", (chunk) => {
510
+ process.stdout.write(chunk);
511
+ persist(chunk);
323
512
  });
324
513
  child.on("message", (message) => {
325
514
  if (!isDesktopHostEvent(message)) {
@@ -332,7 +521,14 @@ var DesktopHostProcess = class {
332
521
  this.fail(new Error(message.message));
333
522
  return;
334
523
  }
335
- this.readyResolve({ protocolVersion: message.protocolVersion });
524
+ if (message.type === "inspected") {
525
+ for (const listener of this.inspectedListeners) listener(message);
526
+ return;
527
+ }
528
+ this.readyResolve({
529
+ protocolVersion: message.protocolVersion,
530
+ origin: message.origin
531
+ });
336
532
  });
337
533
  child.once("error", (error) => {
338
534
  this.fail(error);
@@ -388,6 +584,21 @@ var DesktopHostProcess = class {
388
584
  });
389
585
  });
390
586
  }
587
+ inspect(port = 0) {
588
+ this.send({
589
+ type: "inspect",
590
+ port
591
+ });
592
+ }
593
+ endInspect() {
594
+ this.send({ type: "inspect-off" });
595
+ }
596
+ onInspected(listener) {
597
+ this.inspectedListeners.add(listener);
598
+ return () => {
599
+ this.inspectedListeners.delete(listener);
600
+ };
601
+ }
391
602
  async stop() {
392
603
  const child = this.child;
393
604
  if (child === void 0) return;
@@ -592,33 +803,7 @@ var DesktopHostProcess = class {
592
803
  }
593
804
  };
594
805
  //#endregion
595
- //#region ../../../vendor/deepseek-harness/packages/util/values/lib/index.js
596
- /** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
597
- /**
598
- * Mark an unreachable closed-union branch.
599
- * @param value - impossible value; an unhandled typed variant fails at the call site.
600
- * @param context - optional switch-site label included in the failure message.
601
- * @returns never; a runtime value that escaped its type always throws.
602
- */
603
- function assertNever(value, context) {
604
- const rendered = JSON.stringify(value) ?? String(value);
605
- throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
606
- }
607
- //#endregion
608
- //#region ../../../vendor/deepseek-harness/packages/util/crypto/lib/index.js
609
- /**
610
- * Random v4 UUID, minted from `crypto.getRandomValues`.
611
- * @returns the UUID string.
612
- */
613
- function randomUUID() {
614
- const bytes = globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
615
- const hex = Array.from(bytes, (byte, index) => {
616
- return (index === 6 ? byte & 15 | 64 : index === 8 ? byte & 63 | 128 : byte).toString(16).padStart(2, "0");
617
- }).join("");
618
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
619
- }
620
- //#endregion
621
- //#region ../../../vendor/deepseek-harness/packages/client/shortcuts/lib/protocol.js
806
+ //#region ../../../vendor/deepseek-harness/packages/client/shortcuts/src/binding.ts
622
807
  const keyNames = {
623
808
  Slash: "/",
624
809
  Comma: ",",
@@ -704,6 +889,644 @@ function isWebBindingAllowed(binding, platform) {
704
889
  }
705
890
  ].some((candidate) => bindingKey(binding) === bindingKey(normalizeBinding(candidate, platform)));
706
891
  }
892
+ //#endregion
893
+ //#region ../../../vendor/deepseek-harness/packages/util/values/src/partial-json.ts
894
+ /**
895
+ * Lazily scanned view of one JSON object's top-level fields, built from text
896
+ * that may still be streaming or from an already parsed object. Nothing is
897
+ * scanned until a reader asks; the view remembers every question it answered
898
+ * and reports changed answers when the owner refreshes for publication.
899
+ * Used for model tool-call arguments: a row reads the fields it
900
+ * cares about at whatever granularity it displays, at every stage of the call.
901
+ * @module @deepseek-ai/dsh-util-values/src/partial-json
902
+ */
903
+ const SIMPLE_ESCAPES = {
904
+ "\"": "\"",
905
+ "\\": "\\",
906
+ "/": "/",
907
+ b: "\b",
908
+ f: "\f",
909
+ n: "\n",
910
+ r: "\r",
911
+ t: " "
912
+ };
913
+ const CONTENT_ESCAPE = /[\\\u0000-\u001f]/u;
914
+ function isWhitespace(c) {
915
+ return c === " " || c === "\n" || c === "\r" || c === " ";
916
+ }
917
+ function isHex(c) {
918
+ return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
919
+ }
920
+ (class PartialArguments {
921
+ /** The view of a call with no arguments available. */
922
+ static EMPTY = PartialArguments.fromObject({});
923
+ /**
924
+ * View finished argument text without scanning it until a reader asks.
925
+ * @param text - the complete argument JSON text.
926
+ * @returns a sealed view.
927
+ */
928
+ static fromText(text) {
929
+ const view = new PartialArguments();
930
+ view.append(text);
931
+ view.sealed = true;
932
+ return view;
933
+ }
934
+ /**
935
+ * View an already parsed argument payload, such as a PTC dispatch object.
936
+ * @param value - the parsed argument value.
937
+ * @returns a sealed view; a non-object payload has no fields.
938
+ */
939
+ static fromObject(value) {
940
+ const view = new PartialArguments();
941
+ view.object = typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
942
+ view.sealed = true;
943
+ return view;
944
+ }
945
+ /**
946
+ * The source: text so far or a parsed object, plus whether it can still grow.
947
+ * These are the only enumerable fields, so two views over the same source
948
+ * compare equal structurally however far each has been read.
949
+ */
950
+ chunks = [];
951
+ object;
952
+ sealed = false;
953
+ #ends = [];
954
+ #size = 0;
955
+ #consumed = 0;
956
+ #mode = "root";
957
+ #escape = false;
958
+ #keyStart = 0;
959
+ #keyEscaped = false;
960
+ #key = "";
961
+ #current = null;
962
+ #nestedEnds = [];
963
+ #nestedInString = false;
964
+ #invalidAt;
965
+ #invalidValue = false;
966
+ #entries = /* @__PURE__ */ new Map();
967
+ #order = [];
968
+ #reads = /* @__PURE__ */ new Map();
969
+ /** Whether this view rejects further appends; does not scan text or register reads. */
970
+ get isSealed() {
971
+ return this.sealed;
972
+ }
973
+ /** Whether indexing or a content read found invalid JSON; unread value contents are not validated. */
974
+ get invalid() {
975
+ this.scan();
976
+ return this.#mode === "invalid" || this.#invalidValue;
977
+ }
978
+ /**
979
+ * Retain streamed argument text without scanning or comparing observed answers.
980
+ * @param fragment - the text following every fragment appended before.
981
+ */
982
+ append(fragment) {
983
+ if (this.sealed) throw new Error("PartialArguments: cannot append to a sealed view");
984
+ if (fragment.length === 0) return;
985
+ this.chunks.push(fragment);
986
+ this.#size += fragment.length;
987
+ this.#ends.push(this.#size);
988
+ }
989
+ /**
990
+ * Reconcile a streamed prefix with authoritative complete text without joining the fragments.
991
+ * @param text - the final argument text, which replaces missing or conflicting deltas.
992
+ * @returns this view sealed with its caches retained when every character matches; otherwise a new sealed view.
993
+ */
994
+ settle(text) {
995
+ if (this.object !== void 0 || text.length !== this.#size) return PartialArguments.fromText(text);
996
+ let offset = 0;
997
+ for (const chunk of this.chunks) {
998
+ if (!text.startsWith(chunk, offset)) return PartialArguments.fromText(text);
999
+ offset += chunk.length;
1000
+ }
1001
+ this.chunks = text.length === 0 ? [] : [text];
1002
+ this.#ends = text.length === 0 ? [] : [text.length];
1003
+ this.sealed = true;
1004
+ return this;
1005
+ }
1006
+ /**
1007
+ * Compare observed answers and advance their publication baseline. Unread views remain unscanned.
1008
+ * @returns whether any observed answer changed since its first read or the preceding refresh.
1009
+ */
1010
+ refresh() {
1011
+ if (this.#reads.size === 0) return false;
1012
+ this.scan();
1013
+ let changed = false;
1014
+ let completions = false;
1015
+ for (const read of this.#reads.values()) {
1016
+ if (read.completion) {
1017
+ completions = true;
1018
+ continue;
1019
+ }
1020
+ changed = this.refreshRead(read) || changed;
1021
+ }
1022
+ if (completions) {
1023
+ for (const read of this.#reads.values()) if (read.completion) changed = this.refreshRead(read) || changed;
1024
+ }
1025
+ if (this.sealed) this.#reads.clear();
1026
+ return changed;
1027
+ }
1028
+ refreshRead(read) {
1029
+ const now = read.answer();
1030
+ if (Object.is(now, read.last)) return false;
1031
+ read.last = now;
1032
+ return true;
1033
+ }
1034
+ /**
1035
+ * Check whether no further fields can arrive.
1036
+ * @returns whether the outer object closed, indexing failed, or the view is sealed; unread values are not validated.
1037
+ */
1038
+ closed() {
1039
+ return this.remember("closed", "", () => this.closedNow());
1040
+ }
1041
+ /**
1042
+ * List discovered fields in first-appearance order.
1043
+ * @returns top-level keys seen so far, in first-appearance order.
1044
+ */
1045
+ keys() {
1046
+ return this.remember("keys", "", () => this.keysNow(), (keys) => keys.length);
1047
+ }
1048
+ /**
1049
+ * Check whether a top-level field has appeared.
1050
+ * @param key - argument name.
1051
+ * @returns whether the field has appeared (a string opened or another value began).
1052
+ */
1053
+ has(key) {
1054
+ return this.remember("has", key, () => this.hasNow(key));
1055
+ }
1056
+ /**
1057
+ * Check whether a field's closing delimiter has arrived, without validating its contents.
1058
+ * @param key - argument name.
1059
+ * @returns whether its delimiter arrived and no content reader has reported an error for this value.
1060
+ */
1061
+ complete(key) {
1062
+ return this.remember("complete", key, () => this.completeNow(key));
1063
+ }
1064
+ /**
1065
+ * Read string length without materializing its text.
1066
+ * @param key - argument name.
1067
+ * @param options - change granularity for a streaming string.
1068
+ * @returns decoded UTF-16 length of the string field so far; undefined when absent or not a string.
1069
+ */
1070
+ stringLength(key, options) {
1071
+ const step = Math.max(1, Math.floor(options?.step ?? 1));
1072
+ const offset = options?.offset ?? 0;
1073
+ return this.remember(`length:${step}:${offset}`, key, () => this.lengthNow(key), (length) => length === void 0 ? void 0 : Math.ceil((length + offset) / step));
1074
+ }
1075
+ /**
1076
+ * Check a string against a decoded UTF-16 length limit without materializing it.
1077
+ * @param key - argument name.
1078
+ * @param maxLength - decoded UTF-16 limit, floored to at least zero.
1079
+ * @returns whether the string is longer than the limit; false when absent or not a string.
1080
+ */
1081
+ stringExceeds(key, maxLength) {
1082
+ const limit = Math.max(0, Math.floor(maxLength));
1083
+ return this.remember(`exceeds:${limit}`, key, () => (this.lengthNow(key, limit + 1) ?? 0) > limit);
1084
+ }
1085
+ /**
1086
+ * Read a decoded string, including a streaming prefix.
1087
+ * @param key - argument name.
1088
+ * @returns the string field's decoded text so far; undefined when absent or not a string.
1089
+ */
1090
+ text(key) {
1091
+ return this.remember("text", key, () => this.textNow(key));
1092
+ }
1093
+ /**
1094
+ * Read at most the first decoded UTF-16 units of a string.
1095
+ * @param key - argument name.
1096
+ * @param maxLength - maximum decoded UTF-16 length, floored to at least one.
1097
+ * @returns the bounded string prefix; undefined when absent or not a string.
1098
+ */
1099
+ textPrefix(key, maxLength) {
1100
+ const limit = Math.max(1, Math.floor(maxLength));
1101
+ return this.remember(`prefix:${limit}`, key, () => this.textPrefixNow(key, limit));
1102
+ }
1103
+ /**
1104
+ * Read a completed non-string argument.
1105
+ * @param key - argument name.
1106
+ * @returns the parsed non-string value once it closed; undefined while open, absent, or a string.
1107
+ */
1108
+ value(key) {
1109
+ return this.remember("value", key, () => this.valueNow(key));
1110
+ }
1111
+ /** Answer a question and, on a streaming view, remember it for change detection. */
1112
+ remember(kind, key, read, comparison) {
1113
+ this.scan();
1114
+ const result = read();
1115
+ if (!this.sealed) {
1116
+ const id = `${kind}/${key}`;
1117
+ if (!this.#reads.has(id)) this.#reads.set(id, {
1118
+ completion: kind === "complete",
1119
+ answer: comparison === void 0 ? read : () => comparison(read()),
1120
+ last: comparison === void 0 ? result : comparison(result)
1121
+ });
1122
+ }
1123
+ return result;
1124
+ }
1125
+ closedNow() {
1126
+ return this.sealed || this.#mode === "closed" || this.#mode === "invalid";
1127
+ }
1128
+ keysNow() {
1129
+ return this.object === void 0 ? this.#order : Object.keys(this.object);
1130
+ }
1131
+ hasNow(key) {
1132
+ return this.object === void 0 ? this.#entries.has(key) : Object.hasOwn(this.object, key);
1133
+ }
1134
+ completeNow(key) {
1135
+ if (this.object !== void 0) return Object.hasOwn(this.object, key);
1136
+ const entry = this.#entries.get(key);
1137
+ return entry !== void 0 && entry.end >= 0 && (entry.kind === "string" ? entry.invalidAt === void 0 : !entry.invalid);
1138
+ }
1139
+ lengthNow(key, limit = Number.POSITIVE_INFINITY) {
1140
+ if (this.object !== void 0) {
1141
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
1142
+ return typeof field === "string" ? field.length : void 0;
1143
+ }
1144
+ const entry = this.#entries.get(key);
1145
+ if (entry?.kind !== "string") return void 0;
1146
+ if (entry.text !== void 0 && entry.text.at === entry.end) return entry.text.length;
1147
+ const read = entry.length ??= {
1148
+ at: entry.start,
1149
+ length: 0,
1150
+ text: ""
1151
+ };
1152
+ this.readString(entry, read, limit, false);
1153
+ return read.length;
1154
+ }
1155
+ textNow(key) {
1156
+ if (this.object !== void 0) {
1157
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
1158
+ return typeof field === "string" ? field : void 0;
1159
+ }
1160
+ const entry = this.#entries.get(key);
1161
+ if (entry?.kind !== "string") return void 0;
1162
+ if (entry.text === void 0 && entry.end >= 0 && entry.needsDecoding && entry.invalidAt === void 0) {
1163
+ let text;
1164
+ try {
1165
+ text = JSON.parse(`"${this.slice(entry.start, entry.end)}"`);
1166
+ } catch (_error) {}
1167
+ if (text !== void 0) entry.text = {
1168
+ at: entry.end,
1169
+ length: text.length,
1170
+ text
1171
+ };
1172
+ }
1173
+ const read = entry.text ??= {
1174
+ at: entry.start,
1175
+ length: 0,
1176
+ text: ""
1177
+ };
1178
+ this.readString(entry, read, Number.POSITIVE_INFINITY, true);
1179
+ return read.text;
1180
+ }
1181
+ textPrefixNow(key, maxLength) {
1182
+ if (this.object !== void 0) {
1183
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
1184
+ return typeof field === "string" ? field.slice(0, maxLength) : void 0;
1185
+ }
1186
+ const entry = this.#entries.get(key);
1187
+ if (entry?.kind !== "string") return void 0;
1188
+ const prefixes = entry.prefixes ??= /* @__PURE__ */ new Map();
1189
+ let read = prefixes.get(maxLength);
1190
+ if (read === void 0) {
1191
+ read = {
1192
+ at: entry.start,
1193
+ length: 0,
1194
+ text: ""
1195
+ };
1196
+ prefixes.set(maxLength, read);
1197
+ }
1198
+ this.readString(entry, read, maxLength, true);
1199
+ return read.text;
1200
+ }
1201
+ valueNow(key) {
1202
+ if (this.object !== void 0) {
1203
+ if (!Object.hasOwn(this.object, key)) return void 0;
1204
+ const field = this.object[key];
1205
+ return typeof field === "string" ? void 0 : field;
1206
+ }
1207
+ const entry = this.#entries.get(key);
1208
+ if (entry?.kind !== "value" || entry.end < 0 || entry.invalid) return void 0;
1209
+ if (entry.parsed === void 0) try {
1210
+ entry.parsed = JSON.parse(this.slice(entry.start, entry.end));
1211
+ } catch (_error) {
1212
+ entry.invalid = true;
1213
+ this.#invalidValue = true;
1214
+ }
1215
+ return entry.parsed;
1216
+ }
1217
+ chunkAt(at) {
1218
+ let low = 0;
1219
+ let high = this.#ends.length;
1220
+ while (low < high) {
1221
+ const mid = low + high >>> 1;
1222
+ if (this.#ends[mid] <= at) low = mid + 1;
1223
+ else high = mid;
1224
+ }
1225
+ return low;
1226
+ }
1227
+ /** Materialize only a requested range, never the cumulative source. */
1228
+ slice(start, end) {
1229
+ if (start >= end) return "";
1230
+ const first = this.chunkAt(start);
1231
+ const last = this.chunkAt(end - 1);
1232
+ const base = first === 0 ? 0 : this.#ends[first - 1];
1233
+ if (first === last) return this.chunks[first].slice(start - base, end - base);
1234
+ const parts = [this.chunks[first].slice(start - base)];
1235
+ for (let i = first + 1; i < last; i++) parts.push(this.chunks[i]);
1236
+ parts.push(this.chunks[last].slice(0, end - this.#ends[last - 1]));
1237
+ return parts.join("");
1238
+ }
1239
+ readString(entry, read, limit, materialize) {
1240
+ const end = Math.min(entry.end < 0 ? this.#consumed : entry.end, entry.invalidAt ?? Number.POSITIVE_INFINITY, this.#invalidAt ?? Number.POSITIVE_INFINITY);
1241
+ if (!entry.needsDecoding) {
1242
+ const length = Math.min(end - read.at, limit - read.length);
1243
+ if (length <= 0) return;
1244
+ if (materialize) read.text += this.slice(read.at, read.at + length);
1245
+ read.at += length;
1246
+ read.length += length;
1247
+ return;
1248
+ }
1249
+ let chunkIndex = this.chunkAt(read.at);
1250
+ while (read.at < end && read.length < limit) {
1251
+ const base = chunkIndex === 0 ? 0 : this.#ends[chunkIndex - 1];
1252
+ const chunk = this.chunks[chunkIndex];
1253
+ const remaining = chunk.slice(read.at - base, Math.min(chunk.length, end - base));
1254
+ const boundary = remaining.search(CONTENT_ESCAPE);
1255
+ const length = Math.min(boundary < 0 ? remaining.length : boundary, limit - read.length);
1256
+ if (length > 0) {
1257
+ if (materialize) read.text += remaining.slice(0, length);
1258
+ read.at += length;
1259
+ read.length += length;
1260
+ if (read.at === base + chunk.length) chunkIndex++;
1261
+ continue;
1262
+ }
1263
+ const type = remaining.length > 1 ? remaining[1] : read.at + 1 < end ? this.chunks[chunkIndex + 1][0] : void 0;
1264
+ let decoded;
1265
+ let width = 2;
1266
+ if (remaining[0] === "\\" && type === void 0 && entry.end < 0) return;
1267
+ if (remaining[0] === "\\" && type === "u") {
1268
+ const hex = this.slice(read.at + 2, Math.min(end, read.at + 6));
1269
+ let valid = true;
1270
+ for (let i = 0; i < hex.length; i++) if (!isHex(hex[i])) valid = false;
1271
+ if (valid) {
1272
+ if (hex.length < 4 && entry.end < 0) return;
1273
+ if (hex.length === 4) decoded = String.fromCharCode(Number.parseInt(hex, 16));
1274
+ }
1275
+ width = 6;
1276
+ } else if (remaining[0] === "\\" && type !== void 0) decoded = SIMPLE_ESCAPES[type];
1277
+ if (decoded === void 0) {
1278
+ entry.invalidAt = read.at;
1279
+ this.#invalidValue = true;
1280
+ return;
1281
+ }
1282
+ if (materialize) read.text += decoded;
1283
+ read.length++;
1284
+ read.at += width;
1285
+ while (chunkIndex < this.chunks.length && read.at >= this.#ends[chunkIndex]) chunkIndex++;
1286
+ }
1287
+ }
1288
+ /** Locate new field ranges without decoding or parsing their contents. */
1289
+ scan() {
1290
+ if (this.object !== void 0 || this.#consumed === this.#size) return;
1291
+ for (let i = this.chunkAt(this.#consumed); i < this.chunks.length && this.#invalidAt === void 0; i++) {
1292
+ const pending = this.chunks[i];
1293
+ const base = i === 0 ? 0 : this.#ends[i - 1];
1294
+ for (let index = this.#consumed - base; index < pending.length && this.#mode !== "invalid"; index++) {
1295
+ if (this.#mode === "string" || this.#mode === "nested" && this.#nestedInString) {
1296
+ const end = this.stringBoundary(pending, index);
1297
+ this.#consumed += end - index;
1298
+ index = end;
1299
+ if (index === pending.length) break;
1300
+ }
1301
+ this.step(pending[index], this.#consumed);
1302
+ this.#consumed++;
1303
+ }
1304
+ }
1305
+ }
1306
+ /** Only raw quotes and their preceding backslash runs can terminate a string. */
1307
+ stringBoundary(fragment, start) {
1308
+ let at = start;
1309
+ while (true) {
1310
+ const quote = fragment.indexOf("\"", at);
1311
+ const end = quote < 0 ? fragment.length : quote;
1312
+ if (this.#mode === "string") {
1313
+ const entry = this.#current;
1314
+ if (!entry.needsDecoding && CONTENT_ESCAPE.test(fragment.slice(at, end))) entry.needsDecoding = true;
1315
+ }
1316
+ let slashStart = end;
1317
+ while (slashStart > at && fragment[slashStart - 1] === "\\") slashStart--;
1318
+ const escaped = (end - slashStart) % 2 === 1 !== (slashStart === at && this.#escape);
1319
+ this.#escape = quote < 0 && escaped;
1320
+ if (quote < 0 || !escaped) return end;
1321
+ at = quote + 1;
1322
+ }
1323
+ }
1324
+ step(c, at) {
1325
+ switch (this.#mode) {
1326
+ case "root":
1327
+ if (isWhitespace(c)) return;
1328
+ if (c === "{") {
1329
+ this.#mode = "key-or-end";
1330
+ return;
1331
+ }
1332
+ this.fail();
1333
+ return;
1334
+ case "key-or-end":
1335
+ if (isWhitespace(c)) return;
1336
+ if (c === "}") {
1337
+ this.#mode = "closed";
1338
+ return;
1339
+ }
1340
+ if (c === "\"") {
1341
+ this.beginKey(at);
1342
+ return;
1343
+ }
1344
+ this.fail();
1345
+ return;
1346
+ case "key-only":
1347
+ if (isWhitespace(c)) return;
1348
+ if (c === "\"") {
1349
+ this.beginKey(at);
1350
+ return;
1351
+ }
1352
+ this.fail();
1353
+ return;
1354
+ case "key":
1355
+ this.stepKey(c, at);
1356
+ return;
1357
+ case "colon":
1358
+ if (isWhitespace(c)) return;
1359
+ if (c === ":") {
1360
+ this.#mode = "value";
1361
+ return;
1362
+ }
1363
+ this.fail();
1364
+ return;
1365
+ case "value":
1366
+ this.beginValue(c, at);
1367
+ return;
1368
+ case "string": {
1369
+ const entry = this.#current;
1370
+ entry.end = at;
1371
+ this.#current = null;
1372
+ this.#mode = "comma-or-end";
1373
+ return;
1374
+ }
1375
+ case "scalar":
1376
+ this.stepScalar(c, at);
1377
+ return;
1378
+ case "nested":
1379
+ this.stepNested(c, at);
1380
+ return;
1381
+ case "comma-or-end":
1382
+ if (isWhitespace(c)) return;
1383
+ if (c === ",") {
1384
+ this.#mode = "key-only";
1385
+ return;
1386
+ }
1387
+ if (c === "}") {
1388
+ this.#mode = "closed";
1389
+ return;
1390
+ }
1391
+ this.fail();
1392
+ return;
1393
+ case "closed":
1394
+ if (isWhitespace(c)) return;
1395
+ this.fail();
1396
+ return;
1397
+ /* v8 ignore next 2 -- scan() stops stepping once the view is invalid. */
1398
+ case "invalid": return;
1399
+ /* v8 ignore next 2 -- Every scanner mode has a handler above. */
1400
+ default: assertNever(this.#mode);
1401
+ }
1402
+ }
1403
+ fail() {
1404
+ this.#invalidAt = this.#consumed;
1405
+ this.#mode = "invalid";
1406
+ this.#current = null;
1407
+ }
1408
+ beginKey(at) {
1409
+ this.#mode = "key";
1410
+ this.#keyStart = at + 1;
1411
+ this.#keyEscaped = false;
1412
+ this.#escape = false;
1413
+ }
1414
+ stepKey(c, at) {
1415
+ if (c < " ") {
1416
+ this.fail();
1417
+ return;
1418
+ }
1419
+ if (this.#escape) {
1420
+ this.#escape = false;
1421
+ return;
1422
+ }
1423
+ if (c === "\\") {
1424
+ this.#escape = true;
1425
+ this.#keyEscaped = true;
1426
+ return;
1427
+ }
1428
+ if (c !== "\"") return;
1429
+ const raw = this.slice(this.#keyStart, at);
1430
+ if (this.#keyEscaped) try {
1431
+ this.#key = JSON.parse(`"${raw}"`);
1432
+ } catch (_error) {
1433
+ this.fail();
1434
+ return;
1435
+ }
1436
+ else this.#key = raw;
1437
+ this.#mode = "colon";
1438
+ }
1439
+ open(entry) {
1440
+ if (!this.#entries.has(this.#key)) this.#order.push(this.#key);
1441
+ this.#entries.set(this.#key, entry);
1442
+ this.#current = entry;
1443
+ }
1444
+ beginValue(c, at) {
1445
+ if (isWhitespace(c)) return;
1446
+ if (c === "\"") {
1447
+ this.open({
1448
+ kind: "string",
1449
+ start: at + 1,
1450
+ end: -1,
1451
+ needsDecoding: false,
1452
+ invalidAt: void 0,
1453
+ length: void 0,
1454
+ text: void 0,
1455
+ prefixes: void 0
1456
+ });
1457
+ this.#escape = false;
1458
+ this.#mode = "string";
1459
+ return;
1460
+ }
1461
+ if (c === "}" || c === "," || c === ":" || c === "]") {
1462
+ this.fail();
1463
+ return;
1464
+ }
1465
+ this.open({
1466
+ kind: "value",
1467
+ start: at,
1468
+ end: -1,
1469
+ parsed: void 0,
1470
+ invalid: false
1471
+ });
1472
+ if (c === "{" || c === "[") {
1473
+ this.#mode = "nested";
1474
+ this.#nestedEnds = [c === "{" ? "}" : "]"];
1475
+ this.#nestedInString = false;
1476
+ this.#escape = false;
1477
+ return;
1478
+ }
1479
+ this.#mode = "scalar";
1480
+ }
1481
+ stepScalar(c, at) {
1482
+ if (c !== "," && c !== "}" && !isWhitespace(c)) return;
1483
+ this.closeValue(at);
1484
+ this.#mode = c === "," ? "key-only" : c === "}" ? "closed" : "comma-or-end";
1485
+ }
1486
+ stepNested(c, at) {
1487
+ if (this.#nestedInString) {
1488
+ this.#nestedInString = false;
1489
+ return;
1490
+ }
1491
+ if (c === "\"") {
1492
+ this.#nestedInString = true;
1493
+ return;
1494
+ }
1495
+ if (c === "{" || c === "[") {
1496
+ this.#nestedEnds.push(c === "{" ? "}" : "]");
1497
+ return;
1498
+ }
1499
+ if (c === "}" || c === "]") {
1500
+ if (this.#nestedEnds.pop() !== c) {
1501
+ this.fail();
1502
+ return;
1503
+ }
1504
+ if (this.#nestedEnds.length === 0) {
1505
+ this.closeValue(at + 1);
1506
+ this.#mode = "comma-or-end";
1507
+ }
1508
+ }
1509
+ }
1510
+ closeValue(end) {
1511
+ const entry = this.#current;
1512
+ entry.end = end;
1513
+ this.#current = null;
1514
+ }
1515
+ });
1516
+ //#endregion
1517
+ //#region ../../../vendor/deepseek-harness/packages/util/values/src/index.ts
1518
+ /**
1519
+ * Mark an unreachable closed-union branch.
1520
+ * @param value - impossible value; an unhandled typed variant fails at the call site.
1521
+ * @param context - optional switch-site label included in the failure message.
1522
+ * @returns never; a runtime value that escaped its type always throws.
1523
+ */
1524
+ function assertNever(value, context) {
1525
+ const rendered = JSON.stringify(value) ?? String(value);
1526
+ throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
1527
+ }
1528
+ //#endregion
1529
+ //#region ../../../vendor/deepseek-harness/packages/client/shortcuts/src/configuration.ts
707
1530
  /** Validated preference documents and deterministic conflict resolution, without browser dependencies. */
708
1531
  const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
709
1532
  const commandPattern = /^[a-z][a-zA-Z0-9-]*(?:\.[a-zA-Z][a-zA-Z0-9-]*)+$/u;
@@ -950,6 +1773,21 @@ function parseShortcutDefinitions(value) {
950
1773
  }
951
1774
  return definitions;
952
1775
  }
1776
+ //#endregion
1777
+ //#region ../../../vendor/deepseek-harness/packages/util/crypto/src/index.ts
1778
+ /**
1779
+ * Random v4 UUID, minted from `crypto.getRandomValues`.
1780
+ * @returns the UUID string.
1781
+ */
1782
+ function randomUUID() {
1783
+ const bytes = globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
1784
+ const hex = Array.from(bytes, (byte, index) => {
1785
+ return (index === 6 ? byte & 15 | 64 : index === 8 ? byte & 63 | 128 : byte).toString(16).padStart(2, "0");
1786
+ }).join("");
1787
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1788
+ }
1789
+ //#endregion
1790
+ //#region ../../../vendor/deepseek-harness/packages/client/shortcuts/src/persistence.ts
953
1791
  /** Serialized preference transactions; storage owners publish only accepted writes or read diagnostics. */
954
1792
  /**
955
1793
  * Create a disabled initial snapshot for asynchronous adapter startup.
@@ -1098,7 +1936,7 @@ var ShortcutPersistence = class {
1098
1936
  }
1099
1937
  };
1100
1938
  //#endregion
1101
- //#region ../../../vendor/deepseek-harness/packages/util/atomic-write/lib/index.js
1939
+ //#region ../../../vendor/deepseek-harness/packages/util/atomic-write/src/index.ts
1102
1940
  /**
1103
1941
  * Zero-dependency atomic file replacement and writer coordination.
1104
1942
  * `writeFileAtomic` writes a random-suffix sibling with exclusive create and
@@ -1399,13 +2237,20 @@ function shellWrappedSpawn(command, args, options) {
1399
2237
  //#endregion
1400
2238
  //#region src/index.ts
1401
2239
  let focusPrimaryWindow = () => {};
2240
+ /** app 目录(dev 是装配目录、打包是 `Resources/app`):由引导写进全局。 */
2241
+ function shellRoot() {
2242
+ const root = globalThis.__DSH_SHELL_ROOT__;
2243
+ if (root === void 0 || root === "") throw new Error("dsh desktop: the shell root is missing (bootstrap did not report it)");
2244
+ return root;
2245
+ }
1402
2246
  function appConfigDir() {
1403
- const configured = process.env.DSH_DESKTOP_APPCONFIG_DIR;
2247
+ const configured = developmentLayout() === void 0 ? void 0 : join(shellRoot(), "..", "runtime");
1404
2248
  return app.isPackaged || configured === void 0 || configured === "" ? process.resourcesPath : configured;
1405
2249
  }
1406
2250
  function startupFailure(error) {
1407
2251
  const message = error instanceof Error ? error.message : String(error);
1408
- console.error(error);
2252
+ shellLog("入口失败或收尾失败 %o", error);
2253
+ shellLog(`启动失败:${message}`);
1409
2254
  dialog.showErrorBox("dsh desktop startup failed", message);
1410
2255
  app.exit(1);
1411
2256
  throw error instanceof Error ? error : new Error(message);
@@ -1429,44 +2274,49 @@ protocol.registerSchemesAsPrivileged([{
1429
2274
  codeCache: true
1430
2275
  }
1431
2276
  }]);
1432
- const WEB_FRONTEND_PACKAGE = "@deepseek-ai/dsh-web-frontend";
1433
- const APPLICATION_URL = `${SCHEME}://app/`;
1434
2277
  const WINDOWS_TITLEBAR_HEIGHT = 40;
1435
- async function pathExists(path) {
1436
- try {
1437
- await access(path);
1438
- return true;
1439
- } catch {
1440
- return false;
2278
+ /**
2279
+ * 打包形态下"当 node 用"的可执行文件。
2280
+ *
2281
+ * macOS 上 spawn 主二进制(`process.execPath`)会让系统**多出一个叫 `exec` 的 Dock 图标**——它落在 `.app`
2282
+ * 里、被当成 GUI 应用注册。改用同 bundle 的 **Helper**(它的 `Info.plist` 带 `LSUIElement`)就没有这个图标,
2283
+ * 而 `ELECTRON_RUN_AS_NODE` 对它一样生效。其余平台/形态直接用 `process.execPath`。
2284
+ * @returns 可执行文件的绝对路径。
2285
+ */
2286
+ async function nodeExecutableOf() {
2287
+ if (process.platform === "darwin") {
2288
+ const app = basename(process.execPath);
2289
+ const helper = join(dirname(process.execPath), "..", "Frameworks", `${app} Helper.app`, "Contents", "MacOS", `${app} Helper`);
2290
+ if (await pathExistsOf(helper)) return helper;
1441
2291
  }
2292
+ return process.execPath;
1442
2293
  }
1443
- function runtimeResources() {
1444
- const development = !app.isPackaged;
1445
- const node = (development ? process.env.DSH_DESKTOP_NODE_BINARY : void 0) ?? join(process.resourcesPath, "runtime", "node", process.platform === "win32" ? "node.exe" : "node");
1446
- const seed = (development ? process.env.DSH_DESKTOP_SEED_DIR : void 0) ?? join(process.resourcesPath, "seed");
2294
+ async function runtimeResources() {
2295
+ const resourcesDir = developmentLayout() === void 0 ? process.resourcesPath : join(shellRoot(), "..");
2296
+ const node = developmentLayout() === void 0 ? await nodeExecutableOf() : "node";
2297
+ const seed = join(resourcesDir, "seed");
1447
2298
  return {
1448
2299
  node,
1449
2300
  seed,
1450
- runtime: join(seed, SEED_RUNTIME_DIR_NAME),
1451
- pnpm: join(process.resourcesPath, "runtime", "pnpm", "bin", "pnpm.mjs"),
1452
- nodeBin: join(process.resourcesPath, "runtime", "bin")
2301
+ runtime: developmentLayout() === void 0 ? join(resourcesDir, "runtime") : join(seed, SEED_RUNTIME_DIR_NAME),
2302
+ pnpm: join(resourcesDir, "runtime", "pnpm", "bin", "pnpm.mjs"),
2303
+ nodeBin: join(resourcesDir, "runtime", "bin"),
2304
+ loader: join(resourcesDir, "runtime", "loader", "register.mjs")
1453
2305
  };
1454
2306
  }
1455
- async function installPlantedProfile(profileDir, resources) {
1456
- const settingsPath = join(profileDir, PROFILE_WORKSPACE_NAME);
1457
- const overrides = await runtimeOverrides(profileDir, resources.runtime);
1458
- await writeFile(settingsPath, workspaceWithOverrides(await readFile(settingsPath, "utf8"), overrides));
1459
- await installProfile({
1460
- node: resources.node,
1461
- pnpmEntry: resources.pnpm,
1462
- profileDir
1463
- });
2307
+ function configuredPath(value) {
2308
+ return value === void 0 || value === "" ? void 0 : value;
1464
2309
  }
1465
- function developmentProject() {
1466
- const configured = process.env.DSH_DESKTOP_DEV_PROJECT_DIR;
1467
- if (configured === void 0 || configured === "") return void 0;
1468
- if (app.isPackaged) throw new Error("dsh desktop: development project override is unavailable in packaged applications");
1469
- return resolve(configured);
2310
+ function developmentLayout() {
2311
+ const profileDir = configuredPath(process.env.DSH_DESKTOP_DEV_PROFILE_DIR);
2312
+ const runtimeDir = configuredPath(process.env.DSH_DESKTOP_DEV_RUNTIME_DIR);
2313
+ if (profileDir === void 0 && runtimeDir === void 0) return void 0;
2314
+ if (app.isPackaged) throw new Error("dsh desktop: the development layout is unavailable in packaged applications");
2315
+ if (profileDir === void 0 || runtimeDir === void 0) throw new Error("dsh desktop: DSH_DESKTOP_DEV_PROFILE_DIR and DSH_DESKTOP_DEV_RUNTIME_DIR must be set together");
2316
+ return {
2317
+ profileDir: resolve(profileDir),
2318
+ runtimeDir: resolve(runtimeDir)
2319
+ };
1470
2320
  }
1471
2321
  function windowFrame() {
1472
2322
  if (process.platform === "darwin") return {
@@ -1516,31 +2366,55 @@ function developmentHostInspectPort(enabled) {
1516
2366
  const configured = process.env.DSH_DESKTOP_HOST_INSPECT_PORT;
1517
2367
  if (!enabled || configured === void 0 || configured === "") return void 0;
1518
2368
  const port = Number(configured);
1519
- if (!Number.isSafeInteger(port) || port < 1 || port > 65535) throw new Error("dsh desktop: DSH_DESKTOP_HOST_INSPECT_PORT must be an integer from 1 through 65535");
2369
+ if (!Number.isSafeInteger(port) || port < 0 || port > 65535) throw new Error("dsh desktop: DSH_DESKTOP_HOST_INSPECT_PORT must be an integer from 0 through 65535");
1520
2370
  return port;
1521
2371
  }
1522
- function developmentNodeArgs() {
1523
- const specifier = process.env.DSH_DESKTOP_TSX_IMPORT;
1524
- return specifier === void 0 || specifier === "" ? [] : [`--import=${specifier}`];
2372
+ function sourceLoader(resources) {
2373
+ return resources.loader;
2374
+ }
2375
+ function sourceLoaderEnvironment(path) {
2376
+ return { DSH_SOURCE_LOADER: path };
2377
+ }
2378
+ /**
2379
+ * 壳的日志。
2380
+ *
2381
+ * 打包形态的 Electron **不把 `console` 写进 stdout**(它进系统日志),出了事只看到"窗口没起来"。
2382
+ * 所以壳自己的关键节点(启动、宿主就绪、fatal)落一份文件日志,与宿主那份(`${DSH_HOME}/logs/dev-host.log`,
2383
+ * 由 `@morlay/dsh-client-modules` 写)同目录,两边对着看就能定位"页面 → 壳 → 宿主"断在哪一段。
2384
+ */
2385
+ async function shellLog(message, ...args) {
2386
+ const home = resolveDshHome(appConfig) ?? join(process.cwd(), ".dsh-store");
2387
+ const file = join(home, "logs", "shell.log");
2388
+ try {
2389
+ await mkdir(dirname(file), { recursive: true });
2390
+ await appendFile(file, `${(/* @__PURE__ */ new Date()).toISOString()} ${format(message, ...args)}\n`, "utf8");
2391
+ } catch {}
2392
+ }
2393
+ /** 文件在不在(项目禁同步 IO)。 */
2394
+ async function pathExistsOf(path) {
2395
+ return await access(path).then(() => true, () => false);
1525
2396
  }
1526
2397
  async function main() {
1527
- const development = developmentProject();
1528
- const resources = runtimeResources();
2398
+ const development = developmentLayout();
2399
+ const resources = await runtimeResources();
1529
2400
  const hostInspectPort = developmentHostInspectPort(development !== void 0);
1530
- const activeProject = development ?? join(resolveDshHome(appConfig) ?? "", "profiles", "desktop");
1531
- const runtimeProject = development ?? resources.runtime;
1532
- const webDocumentRoot = join(runtimeProject, "node_modules", ...WEB_FRONTEND_PACKAGE.split("/"), "dist");
1533
- const dshHome = resolveDshHome(appConfig);
2401
+ const profileDir = development?.profileDir ?? join(resolveDshHome(appConfig) ?? "", "profiles", "desktop");
2402
+ const runtimeDir = development?.runtimeDir ?? resources.runtime;
2403
+ const dataDir = resolveDshHome(appConfig);
2404
+ const bundleCount = await readFile(join(profileDir, "package.json"), "utf8").then((text) => JSON.parse(text).dsh?.profile?.bundles?.length).catch(() => void 0);
2405
+ await shellLog(`启动:形态=${development === void 0 ? "bundle" : "dev"} root=${globalThis.__DSH_SHELL_ROOT__ ?? "(未知)"} profileDir=${profileDir} dataDir=${dataDir ?? "(未解析)"} dataDirSpec=${appConfig.dataDir} bundles=${String(bundleCount)}`);
1534
2406
  if (development === void 0) {
1535
- if (dshHome === void 0) throw new Error("dsh desktop: packaged applications require a concrete dshHome");
1536
- if (await ensureSeedProfile(resources.seed, dshHome)) await installPlantedProfile(activeProject, resources);
2407
+ if (dataDir === void 0) throw new Error("dsh desktop: packaged applications require a concrete dataDir");
2408
+ const seeded = await ensureSeedProfile(resources.seed, dataDir);
2409
+ if (seeded === "no-seed") throw new Error(`dsh desktop: the packaged app carries no ${PROFILE_NAME} profile seed at ${resources.seed}`);
2410
+ await shellLog(`profile 种子:${seeded === "seeded" ? "已重种" : "已是最新"}`);
1537
2411
  }
1538
- if (!await pathExists(join(webDocumentRoot, "index.html"))) throw new Error(`dsh desktop: the Web frontend is missing at ${webDocumentRoot}; the runtime closure must carry ${WEB_FRONTEND_PACKAGE}`);
1539
2412
  let host;
1540
2413
  let mainWindow;
1541
2414
  let quitConfirmed = false;
1542
2415
  let quitPrompting = false;
1543
- const appPreload = fileURLToPath(new URL("./preload-app.cjs", import.meta.url));
2416
+ /** preload 落盘的位置:字节由宿主现场打包(sandboxed preload 只能是 CJS),这里只是放它。 */
2417
+ const preloadPathOf = () => join(app.getPath("userData"), "preload-app.cjs");
1544
2418
  const confirmQuit = async (window) => {
1545
2419
  if (quitPrompting) return;
1546
2420
  quitPrompting = true;
@@ -1567,17 +2441,26 @@ async function main() {
1567
2441
  const handleHostFailure = (error) => {
1568
2442
  if (hostFailureHandled) return;
1569
2443
  hostFailureHandled = true;
1570
- console.error(`[dsh-shell] backend stopped: ${error.message}`);
2444
+ shellLog(`后端停止:${error.message}`);
2445
+ shellLog(`宿主停止:${error.message}`);
1571
2446
  dialog.showErrorBox("dsh desktop backend stopped", error.message);
1572
2447
  quitConfirmed = true;
1573
2448
  app.exit(1);
1574
2449
  };
1575
- const startHost = async (projectDir = activeProject) => {
1576
- const next = new DesktopHostProcess(resources.node, runtimeProject, projectDir, hostInspectPort, {
1577
- nodeArgs: development === void 0 ? [] : developmentNodeArgs(),
2450
+ const startHost = async (target = profileDir) => {
2451
+ const loader = sourceLoader(resources);
2452
+ const next = new DesktopHostProcess(resources.node, runtimeDir, target, hostInspectPort, {
2453
+ nodeArgs: [`--import=${loader}`],
2454
+ logFile: join(dataDir ?? join(process.cwd(), ".dsh-store"), "logs", "host.log"),
2455
+ entry: createRequire(import.meta.url).resolve(DESKTOP_HOST_PACKAGE),
1578
2456
  extraEnv: {
1579
- ...dshHome === void 0 ? {} : { DSH_HOME: dshHome },
1580
- ...development === void 0 ? {} : { ELECTRON_RUN_AS_NODE: "1" }
2457
+ ELECTRON_RUN_AS_NODE: "1",
2458
+ ...dataDir === void 0 ? {} : { DSH_HOME: dataDir },
2459
+ ...globalThis.__DSH_SHELL_ROOT__ === void 0 ? {} : { DSH_DESKTOP_SHELL_ROOT: globalThis.__DSH_SHELL_ROOT__ },
2460
+ DSH_DEV_FORM: "desktop",
2461
+ UV_THREADPOOL_SIZE: process.env["UV_THREADPOOL_SIZE"] ?? "64",
2462
+ ...development === void 0 ? { ELECTRON_RUN_AS_NODE: "1" } : {},
2463
+ ...sourceLoaderEnvironment(loader)
1581
2464
  },
1582
2465
  processTitle: `${appConfig.name}-server`,
1583
2466
  ...development === void 0 ? { packageManager: {
@@ -1587,17 +2470,27 @@ async function main() {
1587
2470
  spawn: shellWrappedSpawn,
1588
2471
  onFailure: handleHostFailure
1589
2472
  });
1590
- await next.start();
1591
- return next;
2473
+ return {
2474
+ process: next,
2475
+ ready: await next.start()
2476
+ };
1592
2477
  };
1593
2478
  protocol.handle(SCHEME, async (request) => {
1594
2479
  const url = new URL(request.url);
1595
2480
  if (url.hostname !== "app") return new Response(null, { status: 404 });
1596
2481
  const active = host;
1597
2482
  if (active === void 0) return new Response("backend unavailable", { status: 503 });
1598
- if (!app.isPackaged) console.error(`[dsh-shell] recv ${request.method} ${url.pathname}`);
1599
- const response = await active.fetch(request);
1600
- if (!app.isPackaged) console.error(`[dsh-shell] ${request.method} ${url.pathname} → ${String(response.status)}`);
2483
+ if (!app.isPackaged) shellLog(`recv ${request.method} ${url.pathname}`);
2484
+ const conditional = new Request(request.url, {
2485
+ method: request.method,
2486
+ headers: [...request.headers].filter(([name]) => name !== "if-none-match" && name !== "if-modified-since"),
2487
+ ...request.body === null ? {} : {
2488
+ body: request.body,
2489
+ duplex: "half"
2490
+ }
2491
+ });
2492
+ const response = await active.fetch(conditional);
2493
+ if (!app.isPackaged) shellLog(`${request.method} ${url.pathname} → ${String(response.status)}`);
1601
2494
  return response;
1602
2495
  });
1603
2496
  installDesktopDirectoryPicker(() => mainWindow, SCHEME);
@@ -1611,7 +2504,7 @@ async function main() {
1611
2504
  try {
1612
2505
  uplink.close();
1613
2506
  } catch (error) {
1614
- if (!app.isPackaged) console.error("[dsh-shell] stream uplink close failed", error);
2507
+ if (!app.isPackaged) shellLog("stream uplink close failed %o", error);
1615
2508
  }
1616
2509
  };
1617
2510
  ipcMain.handle(DESKTOP_IPC.streamOpen, async (event, endpoint, payload) => {
@@ -1619,7 +2512,7 @@ async function main() {
1619
2512
  if (typeof endpoint !== "string") throw new Error("dsh desktop: stream endpoint must be text");
1620
2513
  const active = host;
1621
2514
  if (active === void 0) throw new Error("dsh desktop: Host is unavailable");
1622
- if (!app.isPackaged) console.error(`[dsh-shell] stream open ${endpoint}`);
2515
+ if (!app.isPackaged) shellLog(`stream open ${endpoint}`);
1623
2516
  const id = nextStreamId++;
1624
2517
  const abort = new AbortController();
1625
2518
  const stream = {
@@ -1646,7 +2539,7 @@ async function main() {
1646
2539
  (async () => {
1647
2540
  try {
1648
2541
  const response = await active.fetch(request);
1649
- if (!app.isPackaged) console.error(`[dsh-shell] stream ${endpoint} → ${String(response.status)}`);
2542
+ if (!app.isPackaged) shellLog(`stream ${endpoint} → ${String(response.status)}`);
1650
2543
  event.sender.send(DESKTOP_IPC.streamChunk, {
1651
2544
  id,
1652
2545
  status: response.status
@@ -1686,7 +2579,7 @@ async function main() {
1686
2579
  try {
1687
2580
  uplink.enqueue(streamEncoder.encode(encodeDesktopStreamItem(item)));
1688
2581
  } catch (error) {
1689
- if (!app.isPackaged) console.error("[dsh-shell] stream uplink rejected", error);
2582
+ if (!app.isPackaged) shellLog("stream uplink rejected %o", error);
1690
2583
  }
1691
2584
  });
1692
2585
  ipcMain.on(DESKTOP_IPC.streamUplinkEnd, (event, id) => {
@@ -1704,6 +2597,9 @@ async function main() {
1704
2597
  app.on("will-quit", () => {
1705
2598
  shortcuts.dispose();
1706
2599
  });
2600
+ /** 宿主回环的 ws 端口:宿主起好之后填入(窗口重建时仍用它)。 */
2601
+ let hostOrigin = "";
2602
+ let appPreload = preloadPathOf();
1707
2603
  const createMainWindow = () => {
1708
2604
  const window = createWindow(appPreload, appConfig.window);
1709
2605
  mainWindow = window;
@@ -1724,16 +2620,31 @@ async function main() {
1724
2620
  focusPrimaryWindow = () => {
1725
2621
  const window = mainWindow;
1726
2622
  if (window === void 0 || window.isDestroyed()) {
1727
- createMainWindow().loadURL(APPLICATION_URL);
2623
+ createMainWindow().loadURL(hostOrigin);
1728
2624
  return;
1729
2625
  }
1730
2626
  if (window.isMinimized()) window.restore();
1731
2627
  window.show();
1732
2628
  window.focus();
1733
2629
  };
1734
- host = await startHost();
2630
+ const developerMenu = installDeveloperMenu({
2631
+ scheme: SCHEME,
2632
+ appName: appConfig.displayName,
2633
+ host: () => host,
2634
+ mainWindow: () => mainWindow
2635
+ });
2636
+ app.on("will-quit", () => {
2637
+ developerMenu.dispose();
2638
+ });
2639
+ const started = await startHost();
2640
+ host = started.process;
2641
+ hostOrigin = started.ready.origin;
2642
+ appPreload = preloadPathOf();
2643
+ const preload = await fetch(new Request(`${hostOrigin}${PRELOAD_SOURCE_PATH}`));
2644
+ if (!preload.ok) throw new Error(`dsh desktop: preload bundling failed (${String(preload.status)}): ${await preload.text()}`);
2645
+ await writeFile(appPreload, await preload.text(), "utf8");
1735
2646
  mainWindow = createMainWindow();
1736
- await mainWindow.loadURL(APPLICATION_URL);
2647
+ await mainWindow.loadURL(hostOrigin);
1737
2648
  if (development !== void 0 && process.env.DSH_DESKTOP_OPEN_DEVTOOLS !== "0") mainWindow.webContents.openDevTools({ mode: "detach" });
1738
2649
  app.on("activate", () => {
1739
2650
  if (BrowserWindow.getAllWindows().length === 0) focusPrimaryWindow();
@@ -1752,7 +2663,7 @@ async function main() {
1752
2663
  const active = host;
1753
2664
  host = void 0;
1754
2665
  active.stop().catch((error) => {
1755
- console.error(error);
2666
+ shellLog("入口失败或收尾失败 %o", error);
1756
2667
  }).finally(() => {
1757
2668
  app.quit();
1758
2669
  });
@@ -1763,7 +2674,7 @@ if (app.isPackaged) try {
1763
2674
  await mkdir(profile, { recursive: true });
1764
2675
  app.setPath("userData", profile);
1765
2676
  } catch (error) {
1766
- console.error(error);
2677
+ shellLog("入口失败或收尾失败 %o", error);
1767
2678
  }
1768
2679
  if ((() => {
1769
2680
  if (!app.requestSingleInstanceLock()) {
@@ -1776,7 +2687,7 @@ if ((() => {
1776
2687
  return true;
1777
2688
  })()) app.whenReady().then(main).catch(async (error) => {
1778
2689
  const message = error instanceof Error ? error.message : String(error);
1779
- console.error(error);
2690
+ shellLog("入口失败或收尾失败 %o", error);
1780
2691
  const diagnosticFile = process.env.DSH_DESKTOP_DIAGNOSTIC_FILE;
1781
2692
  if (diagnosticFile !== void 0) await import("node:fs/promises").then((fs) => fs.writeFile(diagnosticFile, `${error instanceof Error ? error.stack ?? message : message}\n`)).catch(() => void 0);
1782
2693
  dialog.showErrorBox("dsh desktop startup failed", message);