@dimina-kit/devtools 0.4.0-dev.20260702182435 → 0.4.0-dev.20260703101348

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 (87) hide show
  1. package/dist/main/app/app.js +5 -25
  2. package/dist/main/app/native-overview.d.ts +27 -0
  3. package/dist/main/app/native-overview.js +44 -0
  4. package/dist/main/index.bundle.js +1217 -735
  5. package/dist/main/ipc/bridge-router.d.ts +8 -0
  6. package/dist/main/ipc/bridge-router.js +526 -173
  7. package/dist/main/ipc/project-fs.js +39 -21
  8. package/dist/main/services/automation/handlers/app.js +143 -109
  9. package/dist/main/services/console-forward/index.d.ts +12 -1
  10. package/dist/main/services/console-forward/index.js +99 -1
  11. package/dist/main/services/diagnostics/index.d.ts +49 -0
  12. package/dist/main/services/diagnostics/index.js +61 -0
  13. package/dist/main/services/mcp/tools/simulator-tools.js +118 -97
  14. package/dist/main/services/network-forward/dispatch-batch.d.ts +22 -0
  15. package/dist/main/services/network-forward/dispatch-batch.js +21 -0
  16. package/dist/main/services/network-forward/index.js +37 -20
  17. package/dist/main/services/notifications/renderer-notifier.d.ts +39 -0
  18. package/dist/main/services/notifications/renderer-notifier.js +4 -1
  19. package/dist/main/services/projects/create-project-service.js +54 -34
  20. package/dist/main/services/projects/project-repository.js +2 -1
  21. package/dist/main/services/projects/types.d.ts +1 -32
  22. package/dist/main/services/service-console/console-api.js +36 -27
  23. package/dist/main/services/simulator-temp-files/disk.js +109 -86
  24. package/dist/main/services/views/simulator-session-policy.js +17 -0
  25. package/dist/main/services/workbench-coi-server.js +58 -45
  26. package/dist/main/services/workbench-context.d.ts +10 -0
  27. package/dist/main/services/workspace/workspace-service.js +12 -3
  28. package/dist/main/utils/logger.d.ts +5 -12
  29. package/dist/main/utils/logger.js +5 -45
  30. package/dist/main/windows/service-host-window/create.d.ts +15 -1
  31. package/dist/main/windows/service-host-window/create.js +11 -3
  32. package/dist/preload/instrumentation/wxml-extract.js +94 -62
  33. package/dist/preload/runtime/main-api-runner.d.ts +1 -1
  34. package/dist/preload/shared/api-compat.js +18 -55
  35. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  36. package/dist/preload/windows/main.cjs +284 -565
  37. package/dist/preload/windows/main.cjs.map +2 -2
  38. package/dist/preload/windows/simulator.cjs +154 -83
  39. package/dist/preload/windows/simulator.cjs.map +3 -3
  40. package/dist/preload/windows/simulator.js +154 -83
  41. package/dist/render-host/render-inspect.js +63 -52
  42. package/dist/renderer/assets/index-BwzPwCmM.js +49 -0
  43. package/dist/renderer/assets/{input-Cjk0wSEh.js → input-CTb_le7F.js} +2 -2
  44. package/dist/renderer/assets/ipc-transport-BPWIV5hA.css +1 -0
  45. package/dist/renderer/assets/{ipc-transport-OZS-KmOW.js → ipc-transport-Ck5Xa8Tu.js} +2 -2
  46. package/dist/renderer/assets/popover-B1Zu2UQu.js +2 -0
  47. package/dist/renderer/assets/{select-BQDXi5ih.js → select-ozSk5Pt6.js} +2 -2
  48. package/dist/renderer/assets/{settings-tP3Px2KR.js → settings-CNwGOI22.js} +2 -2
  49. package/dist/renderer/assets/settings-api-DMbMp1Eq.js +2 -0
  50. package/dist/renderer/assets/workbenchSettings-Cr4EHo5w.js +8 -0
  51. package/dist/renderer/entries/main/index.html +6 -6
  52. package/dist/renderer/entries/popover/index.html +5 -5
  53. package/dist/renderer/entries/settings/index.html +5 -5
  54. package/dist/renderer/entries/workbench-settings/index.html +4 -4
  55. package/dist/shared/appdata-accumulator.js +53 -48
  56. package/dist/shared/bridge-channels.d.ts +22 -0
  57. package/dist/shared/ipc-channels.d.ts +3 -0
  58. package/dist/shared/ipc-channels.js +9 -0
  59. package/dist/shared/open-in-editor-resource-path.d.ts +56 -0
  60. package/dist/shared/open-in-editor-resource-path.js +133 -0
  61. package/dist/shared/open-in-editor.d.ts +2 -9
  62. package/dist/shared/open-in-editor.js +8 -93
  63. package/dist/shared/request-core.d.ts +84 -0
  64. package/dist/shared/request-core.js +168 -0
  65. package/dist/shared/simulator-api-metadata.d.ts +22 -17
  66. package/dist/shared/simulator-api-metadata.js +36 -0
  67. package/dist/shared/types.d.ts +20 -4
  68. package/dist/simulator/assets/{device-shell-CUl0ILfn.js → device-shell-C-wcFq3Z.js} +2 -2
  69. package/dist/simulator/assets/{jsx-runtime-CDK-o-S0.js → jsx-runtime-BDTY6fEq.js} +2 -2
  70. package/dist/simulator/assets/simulator-DU3-fS3v.js +10 -0
  71. package/dist/simulator/assets/simulator-mini-app-DzfMfnVM.js +2 -0
  72. package/dist/simulator/simulator.html +2 -2
  73. package/dist/vscode-workbench/assets/__vite-browser-external-CXi6Kz9W.js +1 -0
  74. package/dist/vscode-workbench/assets/{dist-CS7SQP3K.js → dist-TpGpmMGi.js} +3 -3
  75. package/dist/vscode-workbench/assets/{iconv-lite-umd-D3q-1-o6.js → iconv-lite-umd-C9tiCAJa.js} +1 -1
  76. package/dist/vscode-workbench/assets/{index-zjigpZ25.js → index-DJ1HyMZ7.js} +20 -22
  77. package/dist/vscode-workbench/assets/{jschardet-Cu4ufeHq.js → jschardet-DE1jV513.js} +1 -1
  78. package/dist/vscode-workbench/index.html +1 -1
  79. package/package.json +6 -6
  80. package/dist/renderer/assets/index-B-Dqb7G0.js +0 -49
  81. package/dist/renderer/assets/ipc-transport-D5dKIti1.css +0 -1
  82. package/dist/renderer/assets/popover-CQVvOe90.js +0 -2
  83. package/dist/renderer/assets/settings-api-B6x2mhaD.js +0 -2
  84. package/dist/renderer/assets/workbenchSettings-Bim9ol9R.js +0 -8
  85. package/dist/simulator/assets/simulator-Dz8iGcgy.js +0 -10
  86. package/dist/simulator/assets/simulator-mini-app-Bt639XL_.js +0 -2
  87. package/dist/vscode-workbench/assets/__vite-browser-external-B0DTNerG.js +0 -1
@@ -173,55 +173,20 @@ function validate(channel, schema, args) {
173
173
  }
174
174
 
175
175
  // src/main/utils/logger.ts
176
- var LEVEL_PRIORITY = {
177
- debug: 0,
178
- info: 1,
179
- warn: 2,
180
- error: 3
181
- };
182
- var LEVEL_PREFIX = {
183
- debug: "[DEBUG]",
184
- info: "[INFO]",
185
- warn: "[WARN]",
186
- error: "[ERROR]"
187
- };
188
- var currentLevel = "debug";
189
- function shouldLog(level) {
190
- return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];
191
- }
192
- function formatMessage(level, tag, message) {
193
- const ts = (/* @__PURE__ */ new Date()).toISOString();
194
- return `${ts} ${LEVEL_PREFIX[level]} [${tag}] ${message}`;
195
- }
196
- function createLogger(tag) {
197
- return {
198
- debug(message, ...args) {
199
- if (shouldLog("debug")) console.debug(formatMessage("debug", tag, message), ...args);
200
- },
201
- info(message, ...args) {
202
- if (shouldLog("info")) console.info(formatMessage("info", tag, message), ...args);
203
- },
204
- warn(message, ...args) {
205
- if (shouldLog("warn")) console.warn(formatMessage("warn", tag, message), ...args);
206
- },
207
- error(message, ...args) {
208
- if (shouldLog("error")) console.error(formatMessage("error", tag, message), ...args);
209
- }
210
- };
211
- }
176
+ import { createLogger, setLogLevel } from "@dimina-kit/electron-deck/main";
212
177
 
213
178
  // src/main/utils/ipc-registry.ts
214
179
  var log = createLogger("ipc");
215
- function reportListenerError(channel, err) {
216
- if (err instanceof IpcValidationError) {
217
- log.warn(`schema reject on '${channel}' at [${err.paths.join(", ")}]`);
180
+ function reportListenerError(channel, err2) {
181
+ if (err2 instanceof IpcValidationError) {
182
+ log.warn(`schema reject on '${channel}' at [${err2.paths.join(", ")}]`);
218
183
  return;
219
184
  }
220
- if (err instanceof Error) {
221
- log.error(`listener threw on '${channel}': ${err.message}`, err.stack);
185
+ if (err2 instanceof Error) {
186
+ log.error(`listener threw on '${channel}': ${err2.message}`, err2.stack);
222
187
  return;
223
188
  }
224
- log.error(`listener threw on '${channel}'`, err);
189
+ log.error(`listener threw on '${channel}'`, err2);
225
190
  }
226
191
  function isThenable(v) {
227
192
  return v != null && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
@@ -284,13 +249,13 @@ var IpcRegistry = class {
284
249
  return;
285
250
  }
286
251
  event.returnValue = fn(event, ...args);
287
- } catch (err) {
288
- reportListenerError(channel, err);
289
- const code = err?.code;
252
+ } catch (err2) {
253
+ reportListenerError(channel, err2);
254
+ const code = err2?.code;
290
255
  event.returnValue = {
291
256
  ok: false,
292
257
  code: typeof code === "string" ? code : "EUNKNOWN",
293
- message: err instanceof Error ? err.message : String(err)
258
+ message: err2 instanceof Error ? err2.message : String(err2)
294
259
  };
295
260
  }
296
261
  };
@@ -307,10 +272,10 @@ var IpcRegistry = class {
307
272
  try {
308
273
  const ret = raw(event, ...args);
309
274
  if (isThenable(ret)) {
310
- Promise.resolve(ret).catch((err) => reportListenerError(channel, err));
275
+ Promise.resolve(ret).catch((err2) => reportListenerError(channel, err2));
311
276
  }
312
- } catch (err) {
313
- reportListenerError(channel, err);
277
+ } catch (err2) {
278
+ reportListenerError(channel, err2);
314
279
  }
315
280
  };
316
281
  const guarded = policy ? (event, ...args) => {
@@ -428,6 +393,9 @@ var ProjectChannel = {
428
393
  CaptureThumbnail: "project:captureThumbnail",
429
394
  GetThumbnail: "project:getThumbnail"
430
395
  };
396
+ var SessionChannel = {
397
+ RuntimeStatus: "session:runtimeStatus"
398
+ };
431
399
  var ProjectFsChannel = {
432
400
  GetRoot: "project:fs:getRoot",
433
401
  ReadFile: "project:fs:readFile",
@@ -555,9 +523,9 @@ var ReadFileSchema = z.tuple([PathArg]);
555
523
  var WriteFileSchema = z.tuple([PathArg, z.string()]);
556
524
  var ListFilesSchema = z.tuple([PathArg]);
557
525
  function makeError(code, message) {
558
- const err = new Error(message);
559
- err.code = code;
560
- return err;
526
+ const err2 = new Error(message);
527
+ err2.code = code;
528
+ return err2;
561
529
  }
562
530
  function isWithin(parent, child) {
563
531
  if (child === parent) return true;
@@ -600,8 +568,8 @@ async function resolveWithinProjectRoot(abs, root) {
600
568
  let realTarget;
601
569
  try {
602
570
  realTarget = await fs2.realpath(lexical);
603
- } catch (err) {
604
- if (err.code !== "ENOENT") throw err;
571
+ } catch (err2) {
572
+ if (err2.code !== "ENOENT") throw err2;
605
573
  const parent = path2.dirname(lexical);
606
574
  const realParent = await fs2.realpath(parent);
607
575
  if (!isWithin(realRoot, realParent)) {
@@ -648,8 +616,8 @@ async function assertWritableAncestor(lexical, root) {
648
616
  let realAncestor;
649
617
  try {
650
618
  realAncestor = await fs2.realpath(ancestor);
651
- } catch (err) {
652
- if (err.code !== "ENOENT") throw err;
619
+ } catch (err2) {
620
+ if (err2.code !== "ENOENT") throw err2;
653
621
  const next = path2.dirname(ancestor);
654
622
  if (next === ancestor) {
655
623
  throw makeError("EACCES", `Path escapes project root: ${lexical}`);
@@ -714,9 +682,9 @@ async function deleteWithin(root, rel) {
714
682
  let safe;
715
683
  try {
716
684
  safe = await resolveWithinProjectRoot(joinRel(root, rel), root);
717
- } catch (err) {
718
- if (err.code === "ENOENT") return;
719
- throw err;
685
+ } catch (err2) {
686
+ if (err2.code === "ENOENT") return;
687
+ throw err2;
720
688
  }
721
689
  await fs2.rm(safe, { recursive: true, force: true });
722
690
  }
@@ -733,8 +701,8 @@ function resolveWithinProjectRootSync(abs, root) {
733
701
  let realTarget;
734
702
  try {
735
703
  realTarget = nodeFs.realpathSync(lexical);
736
- } catch (err) {
737
- if (err.code !== "ENOENT") throw err;
704
+ } catch (err2) {
705
+ if (err2.code !== "ENOENT") throw err2;
738
706
  const parent = path2.dirname(lexical);
739
707
  const realParent = nodeFs.realpathSync(parent);
740
708
  if (!isWithin(realRoot, realParent)) {
@@ -754,8 +722,8 @@ function assertWritableAncestorSync(lexical, root) {
754
722
  let realAncestor;
755
723
  try {
756
724
  realAncestor = nodeFs.realpathSync(ancestor);
757
- } catch (err) {
758
- if (err.code !== "ENOENT") throw err;
725
+ } catch (err2) {
726
+ if (err2.code !== "ENOENT") throw err2;
759
727
  const next = path2.dirname(ancestor);
760
728
  if (next === ancestor) {
761
729
  throw makeError("EACCES", `Path escapes project root: ${lexical}`);
@@ -800,6 +768,25 @@ function writeFileSync(root, p, content) {
800
768
  nodeFs.closeSync(fd);
801
769
  }
802
770
  }
771
+ async function readDirEntriesSafe(dir) {
772
+ try {
773
+ return await fs2.readdir(dir, { withFileTypes: true });
774
+ } catch (err2) {
775
+ const code = err2.code;
776
+ if (code === "EACCES" || code === "ENOENT" || code === "ENOTDIR") return null;
777
+ throw err2;
778
+ }
779
+ }
780
+ function collectListEntry(entry, dir, safeRoot, out, queue) {
781
+ const full = path2.join(dir, entry.name);
782
+ if (entry.isDirectory()) {
783
+ if (!SKIP_DIRS.has(entry.name)) queue.push(full);
784
+ return;
785
+ }
786
+ if (entry.isFile()) {
787
+ out.push(path2.relative(safeRoot, full).split(path2.sep).join("/"));
788
+ }
789
+ }
803
790
  async function listFiles(root, rootArg) {
804
791
  const safeRoot = await resolveWithinProjectRoot(rootArg, root);
805
792
  const out = [];
@@ -807,24 +794,11 @@ async function listFiles(root, rootArg) {
807
794
  while (queue.length > 0) {
808
795
  if (out.length >= LIST_FILE_LIMIT) break;
809
796
  const dir = queue.shift();
810
- let entries;
811
- try {
812
- entries = await fs2.readdir(dir, { withFileTypes: true });
813
- } catch (err) {
814
- const code = err.code;
815
- if (code === "EACCES" || code === "ENOENT" || code === "ENOTDIR") continue;
816
- throw err;
817
- }
818
- for (const e of entries) {
797
+ const entries = await readDirEntriesSafe(dir);
798
+ if (entries === null) continue;
799
+ for (const entry of entries) {
819
800
  if (out.length >= LIST_FILE_LIMIT) break;
820
- const full = path2.join(dir, e.name);
821
- if (e.isDirectory()) {
822
- if (SKIP_DIRS.has(e.name)) continue;
823
- queue.push(full);
824
- continue;
825
- }
826
- if (!e.isFile()) continue;
827
- out.push(path2.relative(safeRoot, full).split(path2.sep).join("/"));
801
+ collectListEntry(entry, dir, safeRoot, out, queue);
828
802
  }
829
803
  }
830
804
  return out;
@@ -1050,6 +1024,37 @@ function registerAppLifecycle() {
1050
1024
  });
1051
1025
  }
1052
1026
 
1027
+ // src/main/app/native-overview.ts
1028
+ async function resolveNativeStorageOverview(context, appId) {
1029
+ try {
1030
+ const info = await context.storageApi?.invoke(appId, "getStorageInfo", {});
1031
+ if (info && typeof info === "object") {
1032
+ const keys = info.keys;
1033
+ if (Array.isArray(keys)) {
1034
+ return {
1035
+ storageKeys: keys.filter((key) => typeof key === "string"),
1036
+ storageCount: keys.length
1037
+ };
1038
+ }
1039
+ }
1040
+ } catch {
1041
+ }
1042
+ return { storageKeys: [], storageCount: 0 };
1043
+ }
1044
+ function resolveNativeAppDataKeys(context, appId) {
1045
+ try {
1046
+ const snapshot = context.appData?.snapshot?.(appId);
1047
+ if (snapshot && typeof snapshot === "object") {
1048
+ const entries = snapshot.entries;
1049
+ if (entries && typeof entries === "object") {
1050
+ return Object.keys(entries);
1051
+ }
1052
+ }
1053
+ } catch {
1054
+ }
1055
+ return [];
1056
+ }
1057
+
1053
1058
  // src/main/services/workbench-context.ts
1054
1059
  import {
1055
1060
  createConnectionRegistry,
@@ -1103,6 +1108,9 @@ function createRendererNotifier(ctx) {
1103
1108
  projectStatus(payload) {
1104
1109
  sendToMain(ProjectChannel.Status, payload);
1105
1110
  },
1111
+ sessionRuntimeStatus(payload) {
1112
+ sendToMain(SessionChannel.RuntimeStatus, payload);
1113
+ },
1106
1114
  compileLog(payload) {
1107
1115
  sendToMain(ProjectChannel.CompileLog, payload);
1108
1116
  },
@@ -1150,8 +1158,8 @@ function createSafeAreaController(options = {}) {
1150
1158
  const attached = /* @__PURE__ */ new Map();
1151
1159
  function override(wc, device, isTabPage2) {
1152
1160
  if (wc.isDestroyed()) return;
1153
- void wc.debugger.sendCommand("Emulation.setSafeAreaInsetsOverride", { insets: guestInsets(device, isTabPage2) }).catch((err) => {
1154
- console.warn("[safe-area] setSafeAreaInsetsOverride failed:", err instanceof Error ? err.message : err);
1161
+ void wc.debugger.sendCommand("Emulation.setSafeAreaInsetsOverride", { insets: guestInsets(device, isTabPage2) }).catch((err2) => {
1162
+ console.warn("[safe-area] setSafeAreaInsetsOverride failed:", err2 instanceof Error ? err2.message : err2);
1155
1163
  });
1156
1164
  }
1157
1165
  return {
@@ -1162,8 +1170,8 @@ function createSafeAreaController(options = {}) {
1162
1170
  }
1163
1171
  try {
1164
1172
  wc.debugger.attach("1.3");
1165
- } catch (err) {
1166
- console.warn("[safe-area] debugger.attach failed; env(safe-area-inset-*) stays 0:", err instanceof Error ? err.message : err);
1173
+ } catch (err2) {
1174
+ console.warn("[safe-area] debugger.attach failed; env(safe-area-inset-*) stays 0:", err2 instanceof Error ? err2.message : err2);
1167
1175
  return;
1168
1176
  }
1169
1177
  attached.set(wc, isTabPage2);
@@ -1403,8 +1411,8 @@ function createWorkbenchView(ctx, reconciler) {
1403
1411
  }
1404
1412
  ctx.windows.mainWindow.contentView.addChildView(view);
1405
1413
  const loadUrl = `${url}index.html?theme=${workbenchThemeScheme()}`;
1406
- await view.webContents.loadURL(loadUrl).catch((err) => {
1407
- console.error("[workbench] attachWorkbench \u2014 loadURL failed", err);
1414
+ await view.webContents.loadURL(loadUrl).catch((err2) => {
1415
+ console.error("[workbench] attachWorkbench \u2014 loadURL failed", err2);
1408
1416
  });
1409
1417
  }
1410
1418
  function setWorkbenchSource(url) {
@@ -1439,7 +1447,7 @@ function createWorkbenchView(ctx, reconciler) {
1439
1447
  return true
1440
1448
  } catch (e) { return false }
1441
1449
  })()`;
1442
- return workbenchView.webContents.executeJavaScript(script, true).then((ok) => ok === true).catch(() => false);
1450
+ return workbenchView.webContents.executeJavaScript(script, true).then((ok2) => ok2 === true).catch(() => false);
1443
1451
  }
1444
1452
  function openFileInWorkbench(relPath, line, column) {
1445
1453
  if (!workbenchView || workbenchView.webContents.isDestroyed()) return false;
@@ -1476,6 +1484,84 @@ function createWorkbenchView(ctx, reconciler) {
1476
1484
  // src/main/services/views/native-simulator-devtools-host.ts
1477
1485
  import { WebContentsView as WebContentsView3 } from "electron";
1478
1486
 
1487
+ // src/shared/open-in-editor-resource-path.ts
1488
+ function decodePathname(pathname) {
1489
+ return pathname.split("/").map((segment) => {
1490
+ try {
1491
+ return decodeURIComponent(segment);
1492
+ } catch {
1493
+ return segment;
1494
+ }
1495
+ }).join("/");
1496
+ }
1497
+ function normalizeSlashPath(value) {
1498
+ return decodePathname(value.replace(/\\/g, "/")).replace(/\/+/g, "/");
1499
+ }
1500
+ function safeRelativePath(segments) {
1501
+ if (segments.length === 0 || segments.some((segment) => !segment || segment === "." || segment === "..")) {
1502
+ return null;
1503
+ }
1504
+ return segments.join("/");
1505
+ }
1506
+ function excludeBuildChunk(rel) {
1507
+ if (!rel) return rel;
1508
+ const baseName = rel.split("/").pop();
1509
+ return baseName === "common.js" || baseName === "vendors.js" || baseName === "runtime.js" || baseName === "taro.js" || baseName === "babelHelpers.js" ? null : rel;
1510
+ }
1511
+ function isOutsideKnownRoot(normalizedRaw) {
1512
+ return /^\/(?:Volumes|Users|home|private|tmp|var|opt|Applications)\//.test(normalizedRaw);
1513
+ }
1514
+ function resolveFilesystemRawPath(normalizedRaw, projectRoot) {
1515
+ if (!projectRoot) return void 0;
1516
+ if (normalizedRaw !== projectRoot && !normalizedRaw.startsWith(`${projectRoot}/`)) return void 0;
1517
+ const relative = normalizedRaw.slice(projectRoot.length).replace(/^\/+/, "");
1518
+ return excludeBuildChunk(safeRelativePath(relative.split("/").filter(Boolean)));
1519
+ }
1520
+ function parseResourceUrl(raw, base) {
1521
+ try {
1522
+ return raw.startsWith("/") && !raw.startsWith("//") ? new URL(raw, base.origin) : new URL(raw);
1523
+ } catch {
1524
+ return null;
1525
+ }
1526
+ }
1527
+ function resolveFileProtocolPath(resource, projectRoot) {
1528
+ const filePath = normalizeSlashPath(resource.pathname);
1529
+ if (!projectRoot || filePath !== projectRoot && !filePath.startsWith(`${projectRoot}/`)) return null;
1530
+ return excludeBuildChunk(safeRelativePath(
1531
+ filePath.slice(projectRoot.length).replace(/^\/+/, "").split("/").filter(Boolean)
1532
+ ));
1533
+ }
1534
+ function resolveHttpProtocolPath(resource, base, context) {
1535
+ if (resource.protocol !== "http:" && resource.protocol !== "https:") return null;
1536
+ if (resource.origin !== base.origin) return null;
1537
+ let segments = decodePathname(resource.pathname).split("/").filter(Boolean);
1538
+ if (context.appId && segments[0] === context.appId) {
1539
+ segments = segments.slice(1);
1540
+ if (context.outputRoot && segments[0] === context.outputRoot) segments = segments.slice(1);
1541
+ }
1542
+ return excludeBuildChunk(safeRelativePath(segments));
1543
+ }
1544
+ function projectAwareResourcePath(resourceUrl, context) {
1545
+ let base;
1546
+ try {
1547
+ base = new URL(context.resourceBaseUrl);
1548
+ } catch {
1549
+ return null;
1550
+ }
1551
+ const projectRoot = normalizeSlashPath(context.projectRoot).replace(/\/$/, "");
1552
+ const raw = resourceUrl.trim();
1553
+ if (!raw) return null;
1554
+ const normalizedRaw = normalizeSlashPath(raw);
1555
+ const filesystemRelative = resolveFilesystemRawPath(normalizedRaw, projectRoot);
1556
+ if (filesystemRelative !== void 0) return filesystemRelative;
1557
+ if (isOutsideKnownRoot(normalizedRaw)) return null;
1558
+ if (!raw.startsWith("/") && !/^[a-z][a-z0-9+.-]*:/i.test(raw)) return null;
1559
+ const resource = parseResourceUrl(raw, base);
1560
+ if (!resource) return null;
1561
+ if (resource.protocol === "file:") return resolveFileProtocolPath(resource, projectRoot);
1562
+ return resolveHttpProtocolPath(resource, base, context);
1563
+ }
1564
+
1479
1565
  // src/shared/open-in-editor.ts
1480
1566
  var OPEN_IN_EDITOR_SCHEME = "dimina-open-in-editor";
1481
1567
  function decodeOpenInEditorUrl(raw) {
@@ -1531,70 +1617,6 @@ function resourceUrlToProjectRelativePath(resourceUrl, expectedOriginOrContext)
1531
1617
  if (segments.length < 2) return null;
1532
1618
  return segments.slice(1).join("/");
1533
1619
  }
1534
- function decodePathname(pathname) {
1535
- return pathname.split("/").map((segment) => {
1536
- try {
1537
- return decodeURIComponent(segment);
1538
- } catch {
1539
- return segment;
1540
- }
1541
- }).join("/");
1542
- }
1543
- function normalizeSlashPath(value) {
1544
- return decodePathname(value.replace(/\\/g, "/")).replace(/\/+/g, "/");
1545
- }
1546
- function safeRelativePath(segments) {
1547
- if (segments.length === 0 || segments.some((segment) => !segment || segment === "." || segment === "..")) {
1548
- return null;
1549
- }
1550
- return segments.join("/");
1551
- }
1552
- function projectAwareResourcePath(resourceUrl, context) {
1553
- let base;
1554
- try {
1555
- base = new URL(context.resourceBaseUrl);
1556
- } catch {
1557
- return null;
1558
- }
1559
- const projectRoot = normalizeSlashPath(context.projectRoot).replace(/\/$/, "");
1560
- const raw = resourceUrl.trim();
1561
- if (!raw) return null;
1562
- const excludeBuildChunk = (rel) => {
1563
- if (!rel) return rel;
1564
- const base2 = rel.split("/").pop();
1565
- return base2 === "common.js" || base2 === "vendors.js" || base2 === "runtime.js" || base2 === "taro.js" || base2 === "babelHelpers.js" ? null : rel;
1566
- };
1567
- const normalizedRaw = normalizeSlashPath(raw);
1568
- if (projectRoot && (normalizedRaw === projectRoot || normalizedRaw.startsWith(`${projectRoot}/`))) {
1569
- const relative = normalizedRaw.slice(projectRoot.length).replace(/^\/+/, "");
1570
- return excludeBuildChunk(safeRelativePath(relative.split("/").filter(Boolean)));
1571
- }
1572
- if (/^\/(?:Volumes|Users|home|private|tmp|var|opt|Applications)\//.test(normalizedRaw)) {
1573
- return null;
1574
- }
1575
- if (!raw.startsWith("/") && !/^[a-z][a-z0-9+.-]*:/i.test(raw)) return null;
1576
- let resource;
1577
- try {
1578
- resource = raw.startsWith("/") && !raw.startsWith("//") ? new URL(raw, base.origin) : new URL(raw);
1579
- } catch {
1580
- return null;
1581
- }
1582
- if (resource.protocol === "file:") {
1583
- const filePath = normalizeSlashPath(resource.pathname);
1584
- if (!projectRoot || filePath !== projectRoot && !filePath.startsWith(`${projectRoot}/`)) return null;
1585
- return excludeBuildChunk(safeRelativePath(
1586
- filePath.slice(projectRoot.length).replace(/^\/+/, "").split("/").filter(Boolean)
1587
- ));
1588
- }
1589
- if (resource.protocol !== "http:" && resource.protocol !== "https:") return null;
1590
- if (resource.origin !== base.origin) return null;
1591
- let segments = decodePathname(resource.pathname).split("/").filter(Boolean);
1592
- if (context.appId && segments[0] === context.appId) {
1593
- segments = segments.slice(1);
1594
- if (context.outputRoot && segments[0] === context.outputRoot) segments = segments.slice(1);
1595
- }
1596
- return excludeBuildChunk(safeRelativePath(segments));
1597
- }
1598
1620
  function projectSourceContextFromServiceHostUrl(serviceHostUrl, activeProjectRoot) {
1599
1621
  let url;
1600
1622
  try {
@@ -1632,10 +1654,16 @@ function buildDevtoolsProjectSourceLinksScript(context) {
1632
1654
  }
1633
1655
 
1634
1656
  const cfg = ${config}
1635
- const diminaProjectSourcePath = ${projectAwareResourcePath.toString()}
1636
1657
  const decodePathname = ${decodePathname.toString()}
1637
1658
  const normalizeSlashPath = ${normalizeSlashPath.toString()}
1638
1659
  const safeRelativePath = ${safeRelativePath.toString()}
1660
+ const excludeBuildChunk = ${excludeBuildChunk.toString()}
1661
+ const isOutsideKnownRoot = ${isOutsideKnownRoot.toString()}
1662
+ const resolveFilesystemRawPath = ${resolveFilesystemRawPath.toString()}
1663
+ const parseResourceUrl = ${parseResourceUrl.toString()}
1664
+ const resolveFileProtocolPath = ${resolveFileProtocolPath.toString()}
1665
+ const resolveHttpProtocolPath = ${resolveHttpProtocolPath.toString()}
1666
+ const diminaProjectSourcePath = ${projectAwareResourcePath.toString()}
1639
1667
  const observedRoots = new Set()
1640
1668
  const observers = []
1641
1669
  const clickBindings = []
@@ -2243,9 +2271,9 @@ function installElementsForward(deps) {
2243
2271
  });
2244
2272
  }
2245
2273
  replyResult(cmd, result);
2246
- }).catch((err) => {
2274
+ }).catch((err2) => {
2247
2275
  if (disposed) return;
2248
- replyError(cmd, err instanceof Error ? err.message : String(err));
2276
+ replyError(cmd, err2 instanceof Error ? err2.message : String(err2));
2249
2277
  });
2250
2278
  }
2251
2279
  function handleOutbound(batch) {
@@ -2325,18 +2353,15 @@ function mapConsoleApiType(type) {
2325
2353
  return "log";
2326
2354
  }
2327
2355
  var BIGINT_LITERAL = /^-?\d+n$/;
2328
- function remoteObjectToValue(ro) {
2329
- if (!ro) return ro;
2330
- if ("value" in ro) return ro.value;
2331
- if (typeof ro.unserializableValue === "string") {
2332
- const u = ro.unserializableValue;
2333
- if (u === "Infinity") return Infinity;
2334
- if (u === "-Infinity") return -Infinity;
2335
- if (u === "NaN") return NaN;
2336
- if (u === "-0") return -0;
2337
- if (BIGINT_LITERAL.test(u)) return u;
2338
- return u;
2339
- }
2356
+ function unserializableToValue(u) {
2357
+ if (u === "Infinity") return Infinity;
2358
+ if (u === "-Infinity") return -Infinity;
2359
+ if (u === "NaN") return NaN;
2360
+ if (u === "-0") return -0;
2361
+ if (BIGINT_LITERAL.test(u)) return u;
2362
+ return u;
2363
+ }
2364
+ function typeFallbackToValue(ro) {
2340
2365
  switch (ro.type) {
2341
2366
  case "undefined":
2342
2367
  return void 0;
@@ -2351,6 +2376,14 @@ function remoteObjectToValue(ro) {
2351
2376
  return ro.description ?? "[Unknown]";
2352
2377
  }
2353
2378
  }
2379
+ function remoteObjectToValue(ro) {
2380
+ if (!ro) return ro;
2381
+ if ("value" in ro) return ro.value;
2382
+ if (typeof ro.unserializableValue === "string") {
2383
+ return unserializableToValue(ro.unserializableValue);
2384
+ }
2385
+ return typeFallbackToValue(ro);
2386
+ }
2354
2387
  function needsDeepFetch(ro) {
2355
2388
  if (!ro) return false;
2356
2389
  if ("value" in ro) return false;
@@ -2627,8 +2660,8 @@ function createDevtoolsHost(ctx, reconciler, deps) {
2627
2660
  emit: (entry) => ctx.consoleForwarder?.emit(entry)
2628
2661
  }).stop;
2629
2662
  return true;
2630
- } catch (err) {
2631
- console.warn("[devtools-host] point-at-service-wc failed; retry will rebuild:", err);
2663
+ } catch (err2) {
2664
+ console.warn("[devtools-host] point-at-service-wc failed; retry will rebuild:", err2);
2632
2665
  if (nativeDevtoolsSourceWc?.id === next.id) {
2633
2666
  nativeDevtoolsSourceWc = null;
2634
2667
  }
@@ -2822,8 +2855,8 @@ function createHostToolbarPortChannel(opts) {
2822
2855
  function invokeReadyHandler(handler) {
2823
2856
  try {
2824
2857
  handler();
2825
- } catch (err) {
2826
- console.error("[host-toolbar] onReady handler threw:", err);
2858
+ } catch (err2) {
2859
+ console.error("[host-toolbar] onReady handler threw:", err2);
2827
2860
  }
2828
2861
  }
2829
2862
  function fireReadyHandlers() {
@@ -3228,8 +3261,8 @@ async function handleCustomApiBridgeRequest(apis, req) {
3228
3261
  try {
3229
3262
  const result = req.op === "list" ? apis.list() : await apis.invoke(req.name, req.params);
3230
3263
  return { id: req.id, result };
3231
- } catch (err) {
3232
- return { id: req.id, error: err instanceof Error ? err.message : String(err) };
3264
+ } catch (err2) {
3265
+ return { id: req.id, error: err2 instanceof Error ? err2.message : String(err2) };
3233
3266
  }
3234
3267
  }
3235
3268
  function createSimulatorApiRegistry() {
@@ -3286,8 +3319,8 @@ function registerMiniappSessionConfigurator(fn) {
3286
3319
  for (const partition of configuredPartitions) {
3287
3320
  try {
3288
3321
  fn(electron.session.fromPartition(partition), partition);
3289
- } catch (err) {
3290
- console.warn("[miniapp-partition] configurator failed for", partition, err);
3322
+ } catch (err2) {
3323
+ console.warn("[miniapp-partition] configurator failed for", partition, err2);
3291
3324
  }
3292
3325
  }
3293
3326
  return () => {
@@ -3303,8 +3336,8 @@ function configureMiniappSession(partition) {
3303
3336
  for (const fn of configurators) {
3304
3337
  try {
3305
3338
  fn(sess, partition);
3306
- } catch (err) {
3307
- console.warn("[miniapp-partition] configurator failed for", partition, err);
3339
+ } catch (err2) {
3340
+ console.warn("[miniapp-partition] configurator failed for", partition, err2);
3308
3341
  }
3309
3342
  }
3310
3343
  return sess;
@@ -3457,7 +3490,7 @@ function createNativeSimulatorView(ctx, reconciler, deps) {
3457
3490
  }
3458
3491
  try {
3459
3492
  if (!nativeSimulatorView.webContents.isDestroyed()) {
3460
- ctx.bridge?.disposeSessionsForSimulator?.(nativeSimulatorView.webContents.id)?.catch((err) => console.warn(`[view-manager] dispose sessions (${label}) failed:`, err));
3493
+ ctx.bridge?.disposeSessionsForSimulator?.(nativeSimulatorView.webContents.id)?.catch((err2) => console.warn(`[view-manager] dispose sessions (${label}) failed:`, err2));
3461
3494
  nativeSimulatorView.webContents.close();
3462
3495
  }
3463
3496
  } catch {
@@ -3597,8 +3630,8 @@ function createNativeSimulatorView(ctx, reconciler, deps) {
3597
3630
  e.preventDefault();
3598
3631
  }
3599
3632
  });
3600
- void simWc.loadURL(simulatorUrl).catch((err) => {
3601
- console.error("[workbench] attachNativeSimulator \u2014 loadURL failed", err);
3633
+ void simWc.loadURL(simulatorUrl).catch((err2) => {
3634
+ console.error("[workbench] attachNativeSimulator \u2014 loadURL failed", err2);
3602
3635
  });
3603
3636
  simulatorWebContentsId = simWc.id;
3604
3637
  ctx.networkForward?.attachSimulator(simWc);
@@ -3840,8 +3873,8 @@ function validateProjectDir(dirPath) {
3840
3873
  const resolvedRoot = path10.resolve(dirPath, cfg.miniprogramRoot);
3841
3874
  return `\u8BE5\u76EE\u5F55\u7F3A\u5C11 app.json\uFF0Cproject.config.json \u4E2D\u6307\u5B9A\u4E86 miniprogramRoot: "${cfg.miniprogramRoot}"\uFF0C\u8BF7\u5BFC\u5165 ${resolvedRoot}`;
3842
3875
  }
3843
- } catch (err) {
3844
- log2.warn("Failed to parse project.config.json", err);
3876
+ } catch (err2) {
3877
+ log2.warn("Failed to parse project.config.json", err2);
3845
3878
  }
3846
3879
  }
3847
3880
  return "\u8BE5\u76EE\u5F55\u7F3A\u5C11 app.json\uFF0C\u8BF7\u9009\u62E9\u5305\u542B\u5C0F\u7A0B\u5E8F\u6E90\u7801\u7684\u76EE\u5F55";
@@ -3857,8 +3890,8 @@ function addProject(dirPath) {
3857
3890
  const cfg = JSON.parse(fs5.readFileSync(configPath, "utf-8"));
3858
3891
  if (cfg.projectname) name = cfg.projectname;
3859
3892
  }
3860
- } catch (err) {
3861
- log2.warn("Failed to read project name from config", err);
3893
+ } catch (err2) {
3894
+ log2.warn("Failed to read project name from config", err2);
3862
3895
  }
3863
3896
  const project = { name, path: dirPath, lastOpened: null };
3864
3897
  const idx = projects.findIndex((p) => p.path === dirPath);
@@ -3900,7 +3933,8 @@ function getProjectPages(dirPath) {
3900
3933
  pages: appJson.pages || [],
3901
3934
  entryPagePath: appJson.entryPagePath || appJson.pages?.[0] || ""
3902
3935
  };
3903
- } catch {
3936
+ } catch (err2) {
3937
+ log2.warn(`Failed to read project pages from ${appJsonPath}`, err2);
3904
3938
  return { pages: [], entryPagePath: "" };
3905
3939
  }
3906
3940
  }
@@ -4002,14 +4036,14 @@ function createWorkspaceService(ctx) {
4002
4036
  let lastClosedProjectPath = "";
4003
4037
  let logGeneration = 0;
4004
4038
  const opLock = createOpLock();
4005
- function sendStatus(status2, message, hotReload) {
4006
- ctx.notify.projectStatus(hotReload ? { status: status2, message, hotReload: true } : { status: status2, message });
4039
+ function sendStatus(status2, message, hotReload, pages, watcherDead) {
4040
+ ctx.notify.projectStatus({ status: status2, message, ...hotReload ? { hotReload: true } : {}, ...pages ? { pages } : {}, ...watcherDead ? { watcher: "dead" } : {} });
4007
4041
  }
4008
4042
  function bestEffort(label, fn) {
4009
4043
  try {
4010
4044
  fn();
4011
- } catch (err) {
4012
- console.warn(`[workspace] ${label} failed (non-fatal):`, err);
4045
+ } catch (err2) {
4046
+ console.warn(`[workspace] ${label} failed (non-fatal):`, err2);
4013
4047
  }
4014
4048
  }
4015
4049
  async function disposeSession() {
@@ -4023,8 +4057,8 @@ function createWorkspaceService(ctx) {
4023
4057
  (_, reject) => setTimeout(() => reject(new Error("session close timed out")), 5e3)
4024
4058
  )
4025
4059
  ]);
4026
- } catch (err) {
4027
- console.error("[workspace] session close failed:", err);
4060
+ } catch (err2) {
4061
+ console.error("[workspace] session close failed:", err2);
4028
4062
  }
4029
4063
  }
4030
4064
  async function runOpenTeardown(mySeq) {
@@ -4064,17 +4098,22 @@ function createWorkspaceService(ctx) {
4064
4098
  sourcemap: true,
4065
4099
  fileTypes: ctx.fileTypes,
4066
4100
  watch: compile.watch,
4067
- onRebuild: () => sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210\uFF0C\u5DF2\u91CD\u542F", true),
4068
- onBuildError: (err) => sendStatus("error", String(err)),
4101
+ onRebuild: () => {
4102
+ const { pages } = getProjectPages(projectPath);
4103
+ sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210\uFF0C\u5DF2\u91CD\u542F", true, pages.length ? pages : void 0);
4104
+ },
4105
+ onBuildError: (err2) => sendStatus("error", String(err2)),
4106
+ // Watcher died mid-session (EMFILE, permission loss, …): non-fatal, so 'ready' stays but `watcher: 'dead'` flags that saves no longer auto-rebuild.
4107
+ onWatcherError: () => sendStatus("ready", "\u6587\u4EF6\u76D1\u542C\u5DF2\u505C\u6B62\uFF0C\u4FDD\u5B58\u4E0D\u518D\u89E6\u53D1\u81EA\u52A8\u7F16\u8BD1", false, void 0, true),
4069
4108
  onLog: (entry) => {
4070
4109
  if (sessionGeneration !== logGeneration) return;
4071
4110
  ctx.notify.compileLog({ stream: entry.stream, text: entry.text, at: Date.now() });
4072
4111
  }
4073
4112
  });
4074
4113
  return { session: session5 };
4075
- } catch (err) {
4114
+ } catch (err2) {
4076
4115
  clearSimulatorServicewechatReferer();
4077
- return { error: String(err) };
4116
+ return { error: String(err2) };
4078
4117
  }
4079
4118
  }
4080
4119
  async function rejectInvalidAppId(session5) {
@@ -4112,8 +4151,8 @@ function createWorkspaceService(ctx) {
4112
4151
  if (ctx.onBeforeOpenProject) {
4113
4152
  try {
4114
4153
  await ctx.onBeforeOpenProject(projectPath);
4115
- } catch (err) {
4116
- const error = err instanceof Error ? err.message : String(err);
4154
+ } catch (err2) {
4155
+ const error = err2 instanceof Error ? err2.message : String(err2);
4117
4156
  sendStatus("error", error);
4118
4157
  return { success: false, error };
4119
4158
  }
@@ -4607,51 +4646,60 @@ import path14 from "path";
4607
4646
  import fs7 from "node:fs";
4608
4647
  import path13 from "node:path";
4609
4648
  var DEFAULT_TEMPLATE_ID = "blank";
4610
- async function createProject(input, ctx) {
4611
- const name = (input.name ?? "").trim();
4649
+ function resolveName(rawName) {
4650
+ const name = (rawName ?? "").trim();
4612
4651
  if (name.length === 0) {
4613
4652
  throw new Error("Project name cannot be empty");
4614
4653
  }
4615
- const target = input.path;
4654
+ return name;
4655
+ }
4656
+ function validateTargetPath(target) {
4616
4657
  if (!target || typeof target !== "string") {
4617
4658
  throw new Error("Project path is required");
4618
4659
  }
4619
- if (fs7.existsSync(target)) {
4620
- const stat = fs7.statSync(target);
4621
- if (!stat.isDirectory()) {
4622
- throw new Error(`Project path exists and is not a directory: ${target}`);
4623
- }
4624
- const entries = fs7.readdirSync(target);
4625
- if (entries.length > 0) {
4626
- throw new Error(
4627
- `Project path is not empty (refusing to overwrite): ${target}`
4628
- );
4629
- }
4660
+ if (!fs7.existsSync(target)) return;
4661
+ const stat = fs7.statSync(target);
4662
+ if (!stat.isDirectory()) {
4663
+ throw new Error(`Project path exists and is not a directory: ${target}`);
4630
4664
  }
4631
- const templateId = input.templateId ?? DEFAULT_TEMPLATE_ID;
4632
- const template = ctx.templates.find((t) => t.id === templateId);
4665
+ const entries = fs7.readdirSync(target);
4666
+ if (entries.length > 0) {
4667
+ throw new Error(
4668
+ `Project path is not empty (refusing to overwrite): ${target}`
4669
+ );
4670
+ }
4671
+ }
4672
+ function resolveTemplate(templates, templateId) {
4673
+ const id = templateId ?? DEFAULT_TEMPLATE_ID;
4674
+ const template = templates.find((t) => t.id === id);
4633
4675
  if (!template) {
4634
- throw new Error(`Template not found: ${templateId}`);
4676
+ throw new Error(`Template not found: ${id}`);
4635
4677
  }
4636
4678
  if (!template.source && !template.generate) {
4637
4679
  throw new Error(
4638
- `Template '${templateId}' has neither a source directory nor a generate function`
4680
+ `Template '${id}' has neither a source directory nor a generate function`
4639
4681
  );
4640
4682
  }
4683
+ return template;
4684
+ }
4685
+ async function materializeTemplate(target, template, name) {
4641
4686
  fs7.mkdirSync(target, { recursive: true });
4642
4687
  if (template.generate) {
4643
4688
  await template.generate(target, { name });
4644
- } else if (template.source) {
4645
- if (!fs7.existsSync(template.source.path)) {
4646
- throw new Error(
4647
- `Template source missing on disk: ${template.source.path}`
4648
- );
4649
- }
4650
- fs7.cpSync(template.source.path, target, {
4651
- recursive: true,
4652
- force: true
4653
- });
4689
+ return;
4690
+ }
4691
+ if (!template.source) return;
4692
+ if (!fs7.existsSync(template.source.path)) {
4693
+ throw new Error(
4694
+ `Template source missing on disk: ${template.source.path}`
4695
+ );
4654
4696
  }
4697
+ fs7.cpSync(template.source.path, target, {
4698
+ recursive: true,
4699
+ force: true
4700
+ });
4701
+ }
4702
+ function writeProjectConfig(target, name) {
4655
4703
  const cfgPath = path13.join(target, "project.config.json");
4656
4704
  let cfg = {};
4657
4705
  if (fs7.existsSync(cfgPath)) {
@@ -4663,6 +4711,14 @@ async function createProject(input, ctx) {
4663
4711
  }
4664
4712
  cfg.projectname = name;
4665
4713
  fs7.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
4714
+ }
4715
+ async function createProject(input, ctx) {
4716
+ const name = resolveName(input.name);
4717
+ const target = input.path;
4718
+ validateTargetPath(target);
4719
+ const template = resolveTemplate(ctx.templates, input.templateId);
4720
+ await materializeTemplate(target, template, name);
4721
+ writeProjectConfig(target, name);
4666
4722
  const created = await ctx.projectsProvider.addProject(target);
4667
4723
  return created;
4668
4724
  }
@@ -4726,15 +4782,15 @@ function registerProjectsIpc(ctx) {
4726
4782
  templates: ctx.projectTemplates ?? [],
4727
4783
  projectsProvider: ctx.projectsProvider
4728
4784
  });
4729
- } catch (err) {
4785
+ } catch (err2) {
4730
4786
  await dialog.showMessageBox(ctx.windows.mainWindow, {
4731
4787
  type: "error",
4732
4788
  title: "\u521B\u5EFA\u9879\u76EE\u5931\u8D25",
4733
4789
  message: "\u65E0\u6CD5\u521B\u5EFA\u9879\u76EE",
4734
- detail: err instanceof Error ? err.message : String(err),
4790
+ detail: err2 instanceof Error ? err2.message : String(err2),
4735
4791
  buttons: ["\u786E\u5B9A"]
4736
4792
  });
4737
- throw err;
4793
+ throw err2;
4738
4794
  }
4739
4795
  try {
4740
4796
  const settings = loadWorkbenchSettings();
@@ -4742,8 +4798,8 @@ function registerProjectsIpc(ctx) {
4742
4798
  if (newBase && settings.lastCreateBaseDir !== newBase) {
4743
4799
  saveWorkbenchSettings({ ...settings, lastCreateBaseDir: newBase });
4744
4800
  }
4745
- } catch (err) {
4746
- console.warn("[projects:create] failed to persist lastCreateBaseDir", err);
4801
+ } catch (err2) {
4802
+ console.warn("[projects:create] failed to persist lastCreateBaseDir", err2);
4747
4803
  }
4748
4804
  return project;
4749
4805
  }).handle(ProjectsChannel.GetCreateDefaults, () => {
@@ -4913,11 +4969,30 @@ import { app as app12, ipcMain as ipcMain3, protocol as protocol2, session as el
4913
4969
  import path17 from "node:path";
4914
4970
  import { pathToFileURL as pathToFileURL3 } from "node:url";
4915
4971
 
4972
+ // src/shared/request-core.ts
4973
+ var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
4974
+ var MAX_TIMEOUT_MS = 2147483647;
4975
+ function resolveTimeoutBudgetMs(timeout) {
4976
+ const t = Number(timeout);
4977
+ return Number.isFinite(t) && t > 0 && t <= MAX_TIMEOUT_MS ? t : DEFAULT_REQUEST_TIMEOUT_MS;
4978
+ }
4979
+
4916
4980
  // src/shared/simulator-api-metadata.ts
4917
4981
  var PERSISTENT_SIMULATOR_APIS = /* @__PURE__ */ new Set(["audioListen"]);
4918
4982
  function isPersistentSimulatorApi(name) {
4919
4983
  return PERSISTENT_SIMULATOR_APIS.has(name);
4920
4984
  }
4985
+ var NETWORK_BUDGET_SIMULATOR_APIS = /* @__PURE__ */ new Set([
4986
+ "request",
4987
+ "downloadFile",
4988
+ "uploadFile"
4989
+ ]);
4990
+ var API_CALL_WATCHDOG_MS = 5e3;
4991
+ function apiCallWatchdogMs(name, params) {
4992
+ if (!NETWORK_BUDGET_SIMULATOR_APIS.has(name)) return API_CALL_WATCHDOG_MS;
4993
+ const budget = resolveTimeoutBudgetMs(params?.timeout);
4994
+ return Math.min(budget + API_CALL_WATCHDOG_MS, MAX_TIMEOUT_MS);
4995
+ }
4921
4996
 
4922
4997
  // src/main/ipc/session-listener-bag.ts
4923
4998
  function createSessionListenerBag() {
@@ -4984,7 +5059,7 @@ async function startDiminaResourceServer(rootDir) {
4984
5059
  baseUrl: `http://127.0.0.1:${address.port}/`,
4985
5060
  close() {
4986
5061
  return new Promise((resolve, reject) => {
4987
- server.close((err) => err ? reject(err) : resolve());
5062
+ server.close((err2) => err2 ? reject(err2) : resolve());
4988
5063
  });
4989
5064
  }
4990
5065
  };
@@ -5084,10 +5159,13 @@ function buildServiceHostSpawnUrl(opts) {
5084
5159
  }
5085
5160
  return url.toString();
5086
5161
  }
5087
- function navigateServiceHost(win, url) {
5162
+ function navigateServiceHost(win, url, opts) {
5088
5163
  const loaded = Promise.resolve(win.loadURL(url)).then(
5089
5164
  () => void 0,
5090
- () => void 0
5165
+ (err2) => {
5166
+ opts?.onLoadFailed?.(err2);
5167
+ return void 0;
5168
+ }
5091
5169
  );
5092
5170
  if (!app10.isPackaged && process.env.NODE_ENV !== "test") {
5093
5171
  const w = win;
@@ -5126,7 +5204,7 @@ function serviceHostSpec(appId, projectPath) {
5126
5204
  }
5127
5205
  function createServiceHostWindow(opts) {
5128
5206
  const win = constructServiceHostWindow({ appId: opts.appId, partition: miniappPartition(opts.appId, opts.projectPath) });
5129
- void navigateServiceHost(win, buildServiceHostSpawnUrl(opts));
5207
+ void navigateServiceHost(win, buildServiceHostSpawnUrl(opts), { onLoadFailed: opts.onLoadFailed });
5130
5208
  return win;
5131
5209
  }
5132
5210
 
@@ -5443,9 +5521,49 @@ function buildForwardScript(level, args) {
5443
5521
  return `(()=>{try{const a=JSON.parse(${JSON.stringify(argsJson)});console[${JSON.stringify(method)}]('[\u89C6\u56FE]',...a)}catch(_){}})()
5444
5522
  //# sourceURL=${RENDER_FORWARD_SOURCE_URL}`;
5445
5523
  }
5446
- function createConsoleForwarder(bridge) {
5524
+ var DIAGNOSTIC_CONSOLE_CALL = {
5525
+ error: "console.error",
5526
+ warn: "console.warn",
5527
+ info: "console.info"
5528
+ };
5529
+ function buildDiagnosticScript(severity, message) {
5530
+ const call = DIAGNOSTIC_CONSOLE_CALL[severity];
5531
+ const argsJson = JSON.stringify([`[dimina-kit] ${message}`]);
5532
+ return `(()=>{try{const a=JSON.parse(${JSON.stringify(argsJson)});${call}(...a)}catch(_){}})()
5533
+ //# sourceURL=${RENDER_FORWARD_SOURCE_URL}`;
5534
+ }
5535
+ function createConsoleForwarder(bridge, diagnostics) {
5447
5536
  const sinks = /* @__PURE__ */ new Set();
5448
5537
  const registry = new DisposableRegistry4();
5538
+ const pendingBySession = /* @__PURE__ */ new Map();
5539
+ const pendingGlobal = [];
5540
+ const readySessions = /* @__PURE__ */ new Set();
5541
+ function injectDiagnostic(wc, d) {
5542
+ wc.executeJavaScript(buildDiagnosticScript(d.severity, d.message), true).catch(() => {
5543
+ });
5544
+ }
5545
+ function handleDiagnostic(d) {
5546
+ if (d.appSessionId) {
5547
+ const wc2 = readySessions.has(d.appSessionId) && bridge.getServiceWcForBridge ? bridge.getServiceWcForBridge(d.appSessionId) : null;
5548
+ if (wc2 && !wc2.isDestroyed()) {
5549
+ injectDiagnostic(wc2, d);
5550
+ return;
5551
+ }
5552
+ const bucket = pendingBySession.get(d.appSessionId);
5553
+ if (bucket) bucket.push(d);
5554
+ else pendingBySession.set(d.appSessionId, [d]);
5555
+ return;
5556
+ }
5557
+ const wc = bridge.getServiceWc();
5558
+ if (wc && !wc.isDestroyed()) {
5559
+ injectDiagnostic(wc, d);
5560
+ return;
5561
+ }
5562
+ pendingGlobal.push(d);
5563
+ }
5564
+ if (diagnostics) {
5565
+ registry.add(diagnostics.subscribe(handleDiagnostic, { replay: true }));
5566
+ }
5449
5567
  function forwardRenderToServiceHost(entry) {
5450
5568
  let wc = null;
5451
5569
  if (entry.bridgeId && bridge.getServiceWcForBridge) {
@@ -5482,13 +5600,84 @@ function createConsoleForwarder(bridge) {
5482
5600
  sinks.delete(sink);
5483
5601
  }));
5484
5602
  },
5603
+ notifyServiceHostReady(appSessionId) {
5604
+ readySessions.add(appSessionId);
5605
+ const wc = bridge.getServiceWcForBridge ? bridge.getServiceWcForBridge(appSessionId) : null;
5606
+ if (!wc) return;
5607
+ const sessionEntries = pendingBySession.get(appSessionId);
5608
+ if (sessionEntries) {
5609
+ for (const d of sessionEntries) injectDiagnostic(wc, d);
5610
+ pendingBySession.delete(appSessionId);
5611
+ }
5612
+ if (pendingGlobal.length) {
5613
+ for (const d of pendingGlobal) injectDiagnostic(wc, d);
5614
+ pendingGlobal.length = 0;
5615
+ }
5616
+ },
5485
5617
  dispose() {
5486
5618
  sinks.clear();
5619
+ pendingBySession.clear();
5620
+ pendingGlobal.length = 0;
5621
+ readySessions.clear();
5487
5622
  return registry.disposeAll();
5488
5623
  }
5489
5624
  };
5490
5625
  }
5491
5626
 
5627
+ // src/main/services/diagnostics/index.ts
5628
+ var DEFAULT_BUFFER_CAP = 200;
5629
+ var CONSOLE_METHOD = {
5630
+ error: "error",
5631
+ warn: "warn",
5632
+ info: "info"
5633
+ };
5634
+ function createDiagnosticsBus(opts) {
5635
+ const bufferCap = opts?.bufferCap && opts.bufferCap > 0 ? opts.bufferCap : DEFAULT_BUFFER_CAP;
5636
+ const buffer = [];
5637
+ const sinks = /* @__PURE__ */ new Set();
5638
+ let disposed = false;
5639
+ return {
5640
+ report(d) {
5641
+ if (disposed) return;
5642
+ const entry = { ...d, ts: Date.now() };
5643
+ buffer.push(entry);
5644
+ if (buffer.length > bufferCap) buffer.shift();
5645
+ console[CONSOLE_METHOD[entry.severity]](`[dimina-kit:${entry.code}] ${entry.message}`);
5646
+ for (const sink of sinks) {
5647
+ try {
5648
+ sink(entry);
5649
+ } catch {
5650
+ }
5651
+ }
5652
+ },
5653
+ subscribe(sink, subOpts) {
5654
+ const replay = subOpts?.replay ?? true;
5655
+ if (replay) {
5656
+ for (const entry of buffer) {
5657
+ try {
5658
+ sink(entry);
5659
+ } catch {
5660
+ }
5661
+ }
5662
+ }
5663
+ sinks.add(sink);
5664
+ let released = false;
5665
+ return {
5666
+ dispose() {
5667
+ if (released) return;
5668
+ released = true;
5669
+ sinks.delete(sink);
5670
+ }
5671
+ };
5672
+ },
5673
+ dispose() {
5674
+ disposed = true;
5675
+ sinks.clear();
5676
+ buffer.length = 0;
5677
+ }
5678
+ };
5679
+ }
5680
+
5492
5681
  // src/main/services/simulator-storage/index.ts
5493
5682
  import { app as app11, webContents as wcStatic } from "electron";
5494
5683
  import { DisposableRegistry as DisposableRegistry5 } from "@dimina-kit/electron-deck/main";
@@ -6059,6 +6248,7 @@ function rewriteSourceMappingUrl(source, scriptUrl) {
6059
6248
  //# sourceMappingURL=${absolute}`;
6060
6249
  }
6061
6250
  var STACK_ID = "stack_0";
6251
+ var LAUNCH_TIMEOUT_MS = 2e4;
6062
6252
  var PREWARM_MAX_POOL_SIZE = 4;
6063
6253
  function resolvePrewarmPoolSize() {
6064
6254
  if (process.env.DIMINA_PREWARM_DISABLE === "1") return 0;
@@ -6080,28 +6270,33 @@ function summarizeBridgeMsg(payload) {
6080
6270
  const parts = [type, bridgeId ? `bridge=${bridgeId}` : void 0].filter(Boolean);
6081
6271
  return parts.length > 0 ? parts.join(" ") : void 0;
6082
6272
  }
6083
- var API_CALL_TIMEOUT_MS = 5e3;
6273
+ function findAppSessionByAppId(state, appId) {
6274
+ let match;
6275
+ for (const ap of state.appSessions.values()) if (ap.appId === appId) match = ap;
6276
+ return match;
6277
+ }
6278
+ function resolveFallbackAppSession(state) {
6279
+ let last;
6280
+ const distinctAppIds = /* @__PURE__ */ new Set();
6281
+ for (const ap of state.appSessions.values()) {
6282
+ last = ap;
6283
+ distinctAppIds.add(ap.appId);
6284
+ }
6285
+ return distinctAppIds.size <= 1 ? last : void 0;
6286
+ }
6084
6287
  function resolveCurrentApp(state, ctx, appId) {
6085
6288
  if (appId) {
6086
- let match;
6087
- for (const ap of state.appSessions.values()) if (ap.appId === appId) match = ap;
6289
+ const match = findAppSessionByAppId(state, appId);
6088
6290
  if (match) return match;
6089
6291
  }
6090
6292
  const appInfo = ctx.workspace?.getSession?.()?.appInfo;
6091
6293
  const activeAppId = appInfo?.appId;
6092
6294
  if (activeAppId) {
6093
- let match;
6094
- for (const ap of state.appSessions.values()) if (ap.appId === activeAppId) match = ap;
6295
+ const match = findAppSessionByAppId(state, activeAppId);
6095
6296
  if (match) return match;
6096
6297
  }
6097
6298
  if (ctx.workspace?.isClosing?.()) return void 0;
6098
- let last;
6099
- const distinctAppIds = /* @__PURE__ */ new Set();
6100
- for (const ap of state.appSessions.values()) {
6101
- last = ap;
6102
- distinctAppIds.add(ap.appId);
6103
- }
6104
- return distinctAppIds.size <= 1 ? last : void 0;
6299
+ return resolveFallbackAppSession(state);
6105
6300
  }
6106
6301
  function installBridgeRouter(ctx) {
6107
6302
  const state = {
@@ -6244,13 +6439,20 @@ function installBridgeRouter(ctx) {
6244
6439
  ;
6245
6440
  globalThis.__diminaResourceCensus = () => bridgeHandle.census?.();
6246
6441
  }
6247
- const consoleForwarder = createConsoleForwarder(bridgeHandle);
6442
+ const ownsDiagnosticsBus = ctx.diagnostics === void 0;
6443
+ const diagnosticsBus = ctx.diagnostics ?? createDiagnosticsBus();
6444
+ ctx.diagnostics = diagnosticsBus;
6445
+ const consoleForwarder = createConsoleForwarder(bridgeHandle, diagnosticsBus);
6248
6446
  ctx.consoleForwarder = consoleForwarder;
6249
6447
  ctx.guestConsole = consoleForwarder;
6250
6448
  ctx.registry.add(() => {
6251
6449
  void consoleForwarder.dispose();
6252
6450
  ctx.consoleForwarder = void 0;
6253
6451
  ctx.guestConsole = void 0;
6452
+ if (ownsDiagnosticsBus) {
6453
+ diagnosticsBus.dispose();
6454
+ ctx.diagnostics = void 0;
6455
+ }
6254
6456
  });
6255
6457
  const onActivePage = (event, payload) => {
6256
6458
  const ap = state.appSessions.get(payload.appSessionId);
@@ -6410,6 +6612,39 @@ function installBridgeRouter(ctx) {
6410
6612
  );
6411
6613
  });
6412
6614
  }
6615
+ function clearLaunchTimer(ap) {
6616
+ if (ap.launchTimer === null) return;
6617
+ clearTimeout(ap.launchTimer);
6618
+ ap.launchTimer = null;
6619
+ }
6620
+ function startLaunchTimer(state, ctx, ap) {
6621
+ ap.launchTimer = setTimeout(() => {
6622
+ ap.launchTimer = null;
6623
+ if (ap.running) return;
6624
+ if (state.appSessions.get(ap.appSessionId) !== ap) return;
6625
+ const reason = `Service host did not report readiness within ${LAUNCH_TIMEOUT_MS}ms`;
6626
+ ctx.diagnostics?.report({
6627
+ severity: "error",
6628
+ code: "launch-timeout",
6629
+ message: reason,
6630
+ appSessionId: ap.appSessionId
6631
+ });
6632
+ pushRuntimeStatus(ctx, ap, { phase: "launch-failed", code: "timeout", reason });
6633
+ }, LAUNCH_TIMEOUT_MS);
6634
+ }
6635
+ function markSessionRunning(ctx, ap, page) {
6636
+ if (!page.isRoot || ap.running) return;
6637
+ ap.running = true;
6638
+ clearLaunchTimer(ap);
6639
+ pushRuntimeStatus(ctx, ap, { phase: "running" });
6640
+ }
6641
+ function pushRuntimeStatus(ctx, session5, status2) {
6642
+ ctx.notify?.sessionRuntimeStatus?.({
6643
+ appId: session5.appId,
6644
+ ...status2,
6645
+ ...session5.pageFallback ? { pageFallback: session5.pageFallback } : {}
6646
+ });
6647
+ }
6413
6648
  async function handleSpawn(state, ctx, event, opts) {
6414
6649
  const appId = opts.appId;
6415
6650
  if (!appId) throw new Error("[bridge-router] spawn requires appId");
@@ -6435,20 +6670,48 @@ async function handleSpawn(state, ctx, event, opts) {
6435
6670
  ...selectedDevice ? deviceInfoToHostEnv(selectedDevice) : {}
6436
6671
  });
6437
6672
  const appConfig = await loadAppConfig(
6438
- resourceServer ? resourceServer.baseUrl : `${resourceBaseUrl}${appId}/${root}/`
6673
+ resourceServer ? resourceServer.baseUrl : `${resourceBaseUrl}${appId}/${root}/`,
6674
+ ({ url, error }) => {
6675
+ ctx.diagnostics?.report({
6676
+ severity: "error",
6677
+ code: "app-config-unreachable",
6678
+ message: `app-config.json unreachable at ${url}: ${String(error)}`,
6679
+ appSessionId
6680
+ });
6681
+ }
6439
6682
  );
6440
6683
  const manifest = buildAppManifest(appConfig, pagePath);
6441
- const rootWindowConfig = resolvePageWindowConfig(appConfig, pagePath);
6442
- const isTab = isTabPage(appConfig, pagePath);
6684
+ const { resolvedPagePath, pageFallbackApplied } = resolveRootPagePath(manifest, pagePath);
6685
+ if (pageFallbackApplied) {
6686
+ reportPageNotFound(ctx, appSessionId, pagePath, resolvedPagePath);
6687
+ }
6688
+ const rootWindowConfig = resolvePageWindowConfig(appConfig, resolvedPagePath);
6689
+ const isTab = isTabPage(appConfig, resolvedPagePath);
6443
6690
  const usedPool = state.pool !== null;
6444
6691
  let poolEntryId = null;
6445
6692
  let serviceWindow;
6446
- if (state.pool) {
6447
- const acquired = await state.pool.acquire(serviceHostSpec());
6693
+ const reportServiceHostNavigationFailed = (spawnUrl, err2) => {
6694
+ const message = `Failed to navigate service host to ${spawnUrl}: ${String(err2)}`;
6695
+ ctx.diagnostics?.report({
6696
+ severity: "error",
6697
+ code: "service-host-navigation-failed",
6698
+ message,
6699
+ appSessionId
6700
+ });
6701
+ const ap = state.appSessions.get(appSessionId);
6702
+ if (ap) clearLaunchTimer(ap);
6703
+ pushRuntimeStatus(
6704
+ ctx,
6705
+ ap ?? { appId, pageFallback: pageFallbackApplied ? { requested: pagePath, resolved: resolvedPagePath } : null },
6706
+ { phase: "launch-failed", code: "service-host-navigation-failed", reason: message }
6707
+ );
6708
+ };
6709
+ if (state.pool) {
6710
+ const acquired = await state.pool.acquire(serviceHostSpec());
6448
6711
  serviceWindow = acquired.win;
6449
6712
  poolEntryId = acquired.entryId;
6450
6713
  } else {
6451
- serviceWindow = createServiceHostWindow({
6714
+ const freshWindowOptions = {
6452
6715
  bridgeId,
6453
6716
  appId,
6454
6717
  // Same (appId, projectPath) pair the simulator WCV uses, so this project's
@@ -6458,12 +6721,16 @@ async function handleSpawn(state, ctx, event, opts) {
6458
6721
  // awaits above would otherwise give the service host a different path than
6459
6722
  // the simulator WCV was built with, splitting the partition.
6460
6723
  projectPath: workspaceProjectPath || void 0,
6461
- pagePath,
6724
+ pagePath: resolvedPagePath,
6462
6725
  pkgRoot,
6463
6726
  root,
6464
6727
  resourceBaseUrl,
6465
6728
  hostEnvSnapshot: hostEnv,
6466
6729
  apiNamespaces
6730
+ };
6731
+ serviceWindow = createServiceHostWindow({
6732
+ ...freshWindowOptions,
6733
+ onLoadFailed: (err2) => reportServiceHostNavigationFailed(buildServiceHostSpawnUrl(freshWindowOptions), err2)
6467
6734
  });
6468
6735
  }
6469
6736
  const appSession = {
@@ -6487,12 +6754,15 @@ async function handleSpawn(state, ctx, event, opts) {
6487
6754
  poolEntryId,
6488
6755
  onServiceBoot: null,
6489
6756
  listenerBag: createSessionListenerBag(),
6490
- registryHandle: null
6757
+ registryHandle: null,
6758
+ running: false,
6759
+ launchTimer: null,
6760
+ pageFallback: pageFallbackApplied ? { requested: pagePath, resolved: resolvedPagePath } : null
6491
6761
  };
6492
6762
  const rootPage = {
6493
6763
  bridgeId,
6494
6764
  appSessionId,
6495
- pagePath,
6765
+ pagePath: resolvedPagePath,
6496
6766
  query: opts.query ?? {},
6497
6767
  isRoot: true,
6498
6768
  isTab,
@@ -6537,28 +6807,43 @@ async function handleSpawn(state, ctx, event, opts) {
6537
6807
  };
6538
6808
  appSession.onServiceBoot = bootOnServiceLoad;
6539
6809
  serviceWindow.webContents.on("did-finish-load", bootOnServiceLoad);
6540
- void navigateServiceHost(
6541
- serviceWindow,
6542
- buildServiceHostSpawnUrl({
6543
- bridgeId,
6544
- appId,
6545
- pagePath,
6546
- pkgRoot,
6547
- root,
6548
- resourceBaseUrl,
6549
- hostEnvSnapshot: hostEnv,
6550
- apiNamespaces
6551
- })
6552
- );
6810
+ const spawnUrl = buildServiceHostSpawnUrl({
6811
+ bridgeId,
6812
+ appId,
6813
+ pagePath: resolvedPagePath,
6814
+ pkgRoot,
6815
+ root,
6816
+ resourceBaseUrl,
6817
+ hostEnvSnapshot: hostEnv,
6818
+ apiNamespaces
6819
+ });
6820
+ void navigateServiceHost(serviceWindow, spawnUrl, {
6821
+ onLoadFailed: (err2) => reportServiceHostNavigationFailed(spawnUrl, err2)
6822
+ });
6553
6823
  } else {
6554
6824
  serviceWindow.webContents.once("did-finish-load", () => {
6555
6825
  void bootServiceHost(state, appSession, ctx);
6556
6826
  });
6557
6827
  }
6828
+ const onServiceCrashed = () => {
6829
+ clearLaunchTimer(appSession);
6830
+ ctx.diagnostics?.report({
6831
+ severity: "error",
6832
+ code: "service-host-crashed",
6833
+ message: `Service host renderer process gone for appSessionId=${appSessionId}`,
6834
+ appSessionId
6835
+ });
6836
+ pushRuntimeStatus(ctx, appSession, { phase: "crashed", code: "service-host-crashed" });
6837
+ };
6838
+ appSession.listenerBag.on(serviceWindow.webContents, "render-process-gone", onServiceCrashed);
6839
+ pushRuntimeStatus(ctx, appSession, { phase: "launching" });
6840
+ startLaunchTimer(state, ctx, appSession);
6558
6841
  return {
6559
6842
  appSessionId,
6560
6843
  bridgeId,
6561
6844
  pagePath,
6845
+ resolvedPagePath,
6846
+ pageFallbackApplied,
6562
6847
  serviceWcId: serviceWindow.webContents.id,
6563
6848
  resourceBaseUrl,
6564
6849
  manifest,
@@ -6572,6 +6857,9 @@ async function handlePageOpen(state, event, opts) {
6572
6857
  throw new Error("[bridge-router] PAGE_OPEN rejected: caller not bound to app session");
6573
6858
  }
6574
6859
  const pagePath = normalizePagePath(opts.pagePath);
6860
+ if (ap.manifest.source === "app-config" && !ap.manifest.pages.includes(pagePath)) {
6861
+ throw new Error(`[bridge-router] PAGE_OPEN rejected: page-not-found "${pagePath}" is not in the compiled manifest`);
6862
+ }
6575
6863
  const bridgeId = opts.bridgeId || newBridgeId();
6576
6864
  const windowConfig = resolvePageWindowConfig(ap.appConfig, pagePath);
6577
6865
  const isTab = isTabPage(ap.appConfig, pagePath);
@@ -6631,15 +6919,18 @@ function handleNavCallback(state, sender, payload) {
6631
6919
  }
6632
6920
  async function bootServiceHost(state, ap, ctx) {
6633
6921
  if (state.appSessions.get(ap.appSessionId) !== ap) return;
6922
+ ctx.consoleForwarder?.notifyServiceHostReady?.(ap.appSessionId);
6634
6923
  ap.logicInjected = await injectLogicBundle(ap);
6635
6924
  if (!ap.logicInjected) {
6636
- reportLogicLoadFailure(ap, ctx);
6925
+ const reason = reportLogicLoadFailure(ap, ctx);
6926
+ clearLaunchTimer(ap);
6927
+ pushRuntimeStatus(ctx, ap, { phase: "launch-failed", code: "logic-bundle-unreachable", reason });
6637
6928
  return;
6638
6929
  }
6639
6930
  const rootPage = ap.pages.get(ap.appSessionId);
6640
6931
  const rootMissing = !!rootPage && !pageInManifest(ap, rootPage.pagePath);
6641
6932
  if (rootMissing) {
6642
- reportPageNotFound(ap, ctx, rootPage.pagePath);
6933
+ reportPageNotFound(ctx, ap.appSessionId, rootPage.pagePath);
6643
6934
  } else if (rootPage) {
6644
6935
  forwardToService(ap, makeLoadResource(ap, rootPage, "service"));
6645
6936
  }
@@ -6673,16 +6964,30 @@ async function injectLogicBundle(ap) {
6673
6964
  }
6674
6965
  function reportLogicLoadFailure(ap, ctx) {
6675
6966
  const hint = ap.appId === "unknown" ? ' appId could not be resolved (it fell back to "unknown") \u2014 the mini-program likely failed to compile or its project manifest/app config is missing.' : "";
6676
- const message = `[dimina-kit] Failed to load the mini-program logic bundle from ${logicBundleUrl(ap)}. The service runtime has no registered modules, so no page can mount.` + hint + ` Verify the project compiled successfully and that the resource server serves "${ap.appId}/${ap.root}/".`;
6677
- console.error(message);
6967
+ const shortReason = `[dimina-kit] Failed to load the mini-program logic bundle from ${logicBundleUrl(ap)}.`;
6968
+ const message = `${shortReason} The service runtime has no registered modules, so no page can mount.` + hint + ` Verify the project compiled successfully and that the resource server serves "${ap.appId}/${ap.root}/".`;
6969
+ ctx.diagnostics?.report({
6970
+ severity: "error",
6971
+ code: "logic-bundle-unreachable",
6972
+ message,
6973
+ appSessionId: ap.appSessionId
6974
+ });
6678
6975
  ctx.guestConsole?.emit({ source: "service", level: "error", args: [message] });
6976
+ return shortReason;
6679
6977
  }
6680
6978
  function pageInManifest(ap, pagePath) {
6979
+ if (ap.manifest.source !== "app-config") return true;
6681
6980
  return ap.manifest.pages.includes(normalizePagePath(pagePath));
6682
6981
  }
6683
- function reportPageNotFound(ap, ctx, pagePath) {
6684
- const message = `Page[${pagePath}] not found. May be caused by: 1. Forgetting to add page route in app.json. 2. Invoking Page() in async task.`;
6685
- console.error(`[bridge-router] ${message}`);
6982
+ function reportPageNotFound(ctx, appSessionId, pagePath, fallbackTo) {
6983
+ const base = `Page[${pagePath}] not found. May be caused by: 1. Forgetting to add page route in app.json. 2. Invoking Page() in async task.`;
6984
+ const message = fallbackTo ? `${base} Falling back to "${fallbackTo}".` : base;
6985
+ ctx.diagnostics?.report({
6986
+ severity: "error",
6987
+ code: "page-not-found",
6988
+ message,
6989
+ appSessionId
6990
+ });
6686
6991
  ctx.guestConsole?.emit({ source: "service", level: "error", args: [message] });
6687
6992
  }
6688
6993
  function maybeSendResourceLoaded(ap, page) {
@@ -6739,6 +7044,11 @@ function routeFromRender(state, ap, page, msg, ctx) {
6739
7044
  handleContainerMsg(ap, page, msg, ctx, state);
6740
7045
  }
6741
7046
  }
7047
+ function reportServiceUncaughtError(ctx, ap, body) {
7048
+ const severity = body.level === "error" ? "error" : body.level === "warn" ? "warn" : "info";
7049
+ const message = Array.isArray(body.args) ? body.args.map((a) => String(a)).join(" ") : String(body.args ?? "");
7050
+ ctx.diagnostics?.report({ severity, code: "service-uncaught-error", message, appSessionId: ap.appSessionId });
7051
+ }
6742
7052
  function handleContainerMsg(ap, page, msg, ctx, state) {
6743
7053
  switch (msg.type) {
6744
7054
  case "serviceResourceLoaded":
@@ -6757,22 +7067,31 @@ function handleContainerMsg(ap, page, msg, ctx, state) {
6757
7067
  ap.simulatorWc.send(SIMULATOR_EVENTS.DOM_READY, { bridgeId: page.bridgeId });
6758
7068
  }
6759
7069
  state.emitRenderEvent({ kind: "domReady", appId: ap.appId, bridgeId: page.bridgeId });
7070
+ markSessionRunning(ctx, ap, page);
6760
7071
  break;
6761
7072
  case "invokeAPI":
6762
7073
  void handleSimulatorApi(state, ap, page, msg.body, ctx);
6763
7074
  break;
6764
7075
  case "serviceHostError": {
6765
- console.warn("[bridge-router] service host error:", msg.body);
6766
7076
  const errBody = msg.body;
6767
7077
  const errArg = errBody?.message ?? msg.body;
7078
+ ctx.diagnostics?.report({
7079
+ severity: "error",
7080
+ code: "service-host-error",
7081
+ message: String(errArg),
7082
+ appSessionId: ap.appSessionId
7083
+ });
6768
7084
  for (const id of state.appLifecycle.listeners(ap.appSessionId, "onError")) {
6769
7085
  sendCallback(ap, id, errArg);
6770
7086
  }
6771
7087
  break;
6772
7088
  }
6773
- case "consoleLog":
7089
+ case "consoleLog": {
6774
7090
  ctx.guestConsole?.emit(msg.body);
7091
+ const body = msg.body;
7092
+ if (body?.source === "service") reportServiceUncaughtError(ctx, ap, body);
6775
7093
  break;
7094
+ }
6776
7095
  case "storageChanged":
6777
7096
  ctx.onServiceStorageChanged?.(ap.appId, msg.body);
6778
7097
  break;
@@ -6841,99 +7160,136 @@ var APP_LIFECYCLE_UNREGISTER = {
6841
7160
  offAppHide: "onAppHide",
6842
7161
  offError: "onError"
6843
7162
  };
6844
- async function handleSimulatorApi(state, ap, page, body, ctx) {
6845
- const name = String(body.name ?? "");
6846
- const params = normalizeParams(body.params);
6847
- if (NAV_BAR_API_NAMES.has(name)) {
6848
- if (!ap.simulatorWc.isDestroyed()) {
6849
- ap.simulatorWc.send(SIMULATOR_EVENTS.NAV_BAR, {
6850
- bridgeId: page.bridgeId,
6851
- name,
6852
- params
6853
- });
6854
- }
6855
- const successResult = { errMsg: `${name}:ok` };
6856
- sendCallback(ap, params.success, successResult);
6857
- sendCallback(ap, params.complete, successResult);
6858
- return;
6859
- }
6860
- if (NAV_ACTION_NAMES.has(name)) {
6861
- const payload = {
6862
- appSessionId: ap.appSessionId,
7163
+ function handleNavBarApi(ap, page, name, params) {
7164
+ if (!ap.simulatorWc.isDestroyed()) {
7165
+ ap.simulatorWc.send(SIMULATOR_EVENTS.NAV_BAR, {
6863
7166
  bridgeId: page.bridgeId,
6864
7167
  name,
6865
- params,
6866
- callbacks: extractCallbacks(params)
6867
- };
6868
- if (!ap.simulatorWc.isDestroyed()) {
6869
- ap.simulatorWc.send(SIMULATOR_EVENTS.NAV_ACTION, payload);
6870
- } else {
6871
- const fail = { errMsg: `${name}:fail simulator window destroyed` };
6872
- sendCallback(ap, params.fail, fail);
6873
- sendCallback(ap, params.complete, fail);
6874
- }
7168
+ params
7169
+ });
7170
+ }
7171
+ const successResult = { errMsg: `${name}:ok` };
7172
+ sendCallback(ap, params.success, successResult);
7173
+ sendCallback(ap, params.complete, successResult);
7174
+ }
7175
+ function failActionCallback(ap, params, errMsg) {
7176
+ const fail = { errMsg };
7177
+ sendCallback(ap, params.fail, fail);
7178
+ sendCallback(ap, params.complete, fail);
7179
+ }
7180
+ function sendActionOrFail(ap, eventName, payload, name, params) {
7181
+ if (!ap.simulatorWc.isDestroyed()) {
7182
+ ap.simulatorWc.send(eventName, payload);
6875
7183
  return;
6876
7184
  }
6877
- if (TAB_ACTION_NAMES.has(name)) {
6878
- const payload = {
6879
- appSessionId: ap.appSessionId,
6880
- bridgeId: page.bridgeId,
6881
- name,
6882
- params,
6883
- callbacks: extractCallbacks(params)
6884
- };
6885
- if (!ap.simulatorWc.isDestroyed()) {
6886
- ap.simulatorWc.send(SIMULATOR_EVENTS.TAB_ACTION, payload);
6887
- } else {
6888
- const fail = { errMsg: `${name}:fail simulator window destroyed` };
6889
- sendCallback(ap, params.fail, fail);
6890
- sendCallback(ap, params.complete, fail);
7185
+ failActionCallback(ap, params, `${name}:fail simulator window destroyed`);
7186
+ }
7187
+ function extractNavTargetPagePath(params) {
7188
+ const raw = params.url;
7189
+ if (typeof raw !== "string" || !raw) return void 0;
7190
+ return normalizePagePath(raw.split("?")[0] ?? raw);
7191
+ }
7192
+ function checkNavTarget(ap, name, targetPagePath) {
7193
+ if (ap.manifest.source !== "app-config") return { ok: true };
7194
+ if (!ap.manifest.pages.includes(targetPagePath)) {
7195
+ return { ok: false, errMsg: `${name}:fail page "${targetPagePath}" is not found`, reportNotFound: true };
7196
+ }
7197
+ if (name === "switchTab") {
7198
+ const inTabBar = ap.manifest.tabBar?.list.some((item) => normalizePagePath(item.pagePath) === targetPagePath) ?? false;
7199
+ if (!inTabBar) return { ok: false, errMsg: "switchTab:fail can not switch to no-tabBar page" };
7200
+ }
7201
+ return { ok: true };
7202
+ }
7203
+ function handleNavActionApi(ap, ctx, page, name, params) {
7204
+ const targetPagePath = extractNavTargetPagePath(params);
7205
+ if (targetPagePath !== void 0) {
7206
+ const verdict = checkNavTarget(ap, name, targetPagePath);
7207
+ if (!verdict.ok) {
7208
+ if (verdict.reportNotFound) reportPageNotFound(ctx, ap.appSessionId, targetPagePath);
7209
+ failActionCallback(ap, params, verdict.errMsg);
7210
+ return;
6891
7211
  }
6892
- return;
6893
7212
  }
7213
+ const payload = {
7214
+ appSessionId: ap.appSessionId,
7215
+ bridgeId: page.bridgeId,
7216
+ name,
7217
+ params,
7218
+ callbacks: extractCallbacks(params)
7219
+ };
7220
+ sendActionOrFail(ap, SIMULATOR_EVENTS.NAV_ACTION, payload, name, params);
7221
+ }
7222
+ function handleTabActionApi(ap, page, name, params) {
7223
+ const payload = {
7224
+ appSessionId: ap.appSessionId,
7225
+ bridgeId: page.bridgeId,
7226
+ name,
7227
+ params,
7228
+ callbacks: extractCallbacks(params)
7229
+ };
7230
+ sendActionOrFail(ap, SIMULATOR_EVENTS.TAB_ACTION, payload, name, params);
7231
+ }
7232
+ function handleAppLifecycleToggle(state, ap, name, params) {
6894
7233
  const lifecycleRegister = APP_LIFECYCLE_REGISTER[name];
6895
7234
  if (lifecycleRegister) {
6896
7235
  state.appLifecycle.register(ap.appSessionId, lifecycleRegister, params.success);
6897
- return;
7236
+ return true;
6898
7237
  }
6899
7238
  const lifecycleUnregister = APP_LIFECYCLE_UNREGISTER[name];
6900
7239
  if (lifecycleUnregister) {
6901
7240
  state.appLifecycle.unregister(ap.appSessionId, lifecycleUnregister, params.success);
7241
+ return true;
7242
+ }
7243
+ return false;
7244
+ }
7245
+ function handlePageScrollApi(ap, page, params) {
7246
+ const renderWc = page.renderWc;
7247
+ if (renderWc && !renderWc.isDestroyed()) {
7248
+ void renderWc.executeJavaScript(buildPageScrollScript(params)).catch(() => {
7249
+ });
7250
+ }
7251
+ const successResult = { errMsg: "pageScrollTo:ok" };
7252
+ sendCallback(ap, params.success, successResult);
7253
+ sendCallback(ap, params.complete, successResult);
7254
+ }
7255
+ async function invokeSimulatorApiAndCallback(ap, name, params, invoke) {
7256
+ try {
7257
+ const result = await invoke();
7258
+ sendCallback(ap, params.success, result);
7259
+ sendCallback(ap, params.complete, result);
7260
+ } catch (error) {
7261
+ const failResult = { errMsg: `${name}:fail ${error instanceof Error ? error.message : String(error)}` };
7262
+ sendCallback(ap, params.fail, failResult);
7263
+ sendCallback(ap, params.complete, failResult);
7264
+ }
7265
+ }
7266
+ async function handleSimulatorApi(state, ap, page, body, ctx) {
7267
+ const name = String(body.name ?? "");
7268
+ const params = normalizeParams(body.params);
7269
+ if (NAV_BAR_API_NAMES.has(name)) {
7270
+ handleNavBarApi(ap, page, name, params);
6902
7271
  return;
6903
7272
  }
7273
+ if (NAV_ACTION_NAMES.has(name)) {
7274
+ handleNavActionApi(ap, ctx, page, name, params);
7275
+ return;
7276
+ }
7277
+ if (TAB_ACTION_NAMES.has(name)) {
7278
+ handleTabActionApi(ap, page, name, params);
7279
+ return;
7280
+ }
7281
+ if (handleAppLifecycleToggle(state, ap, name, params)) return;
6904
7282
  if (name === "pageScrollTo") {
6905
- const renderWc = page.renderWc;
6906
- if (renderWc && !renderWc.isDestroyed()) {
6907
- void renderWc.executeJavaScript(buildPageScrollScript(params)).catch(() => {
6908
- });
6909
- }
6910
- const successResult = { errMsg: "pageScrollTo:ok" };
6911
- sendCallback(ap, params.success, successResult);
6912
- sendCallback(ap, params.complete, successResult);
7283
+ handlePageScrollApi(ap, page, params);
6913
7284
  return;
6914
7285
  }
6915
7286
  if (ctx.storageApi && STORAGE_API_NAMES.has(name)) {
6916
- try {
6917
- const result = await ctx.storageApi.invoke(ap.appId, name, params);
6918
- sendCallback(ap, params.success, result);
6919
- sendCallback(ap, params.complete, result);
6920
- } catch (error) {
6921
- const failResult = { errMsg: `${name}:fail ${error instanceof Error ? error.message : String(error)}` };
6922
- sendCallback(ap, params.fail, failResult);
6923
- sendCallback(ap, params.complete, failResult);
6924
- }
7287
+ const storageApi = ctx.storageApi;
7288
+ await invokeSimulatorApiAndCallback(ap, name, params, () => storageApi.invoke(ap.appId, name, params));
6925
7289
  return;
6926
7290
  }
6927
7291
  if (ctx.simulatorApis.has(name)) {
6928
- try {
6929
- const result = await ctx.simulatorApis.invoke(name, params);
6930
- sendCallback(ap, params.success, result);
6931
- sendCallback(ap, params.complete, result);
6932
- } catch (error) {
6933
- const failResult = { errMsg: `${name}:fail ${error instanceof Error ? error.message : String(error)}` };
6934
- sendCallback(ap, params.fail, failResult);
6935
- sendCallback(ap, params.complete, failResult);
6936
- }
7292
+ await invokeSimulatorApiAndCallback(ap, name, params, () => ctx.simulatorApis.invoke(name, params));
6937
7293
  return;
6938
7294
  }
6939
7295
  forwardApiCallToSimulator(state, ap, page, name, params);
@@ -6957,7 +7313,7 @@ function forwardApiCallToSimulator(state, ap, page, name, params) {
6957
7313
  const fail = { errMsg: `${pending.name}:fail no handler (timeout)` };
6958
7314
  sendCallback(target, pending.callbacks.fail, fail);
6959
7315
  sendCallback(target, pending.callbacks.complete, fail);
6960
- }, API_CALL_TIMEOUT_MS);
7316
+ }, apiCallWatchdogMs(name, params));
6961
7317
  state.pendingApiCalls.set(requestId, {
6962
7318
  appSessionId: ap.appSessionId,
6963
7319
  callbacks,
@@ -7006,7 +7362,12 @@ function handleApiResponse(state, sender, payload) {
7006
7362
  sendCallback(ap, pending.callbacks.success, payload.result);
7007
7363
  sendCallback(ap, pending.callbacks.complete, payload.result ?? { errMsg: `${pending.name}:ok` });
7008
7364
  } else {
7009
- const fail = { errMsg: payload.errMsg ?? `${pending.name}:fail` };
7365
+ const result = payload.result && typeof payload.result === "object" && !Array.isArray(payload.result) ? payload.result : void 0;
7366
+ const resultErrMsg = typeof result?.errMsg === "string" ? result.errMsg : void 0;
7367
+ const fail = {
7368
+ ...result,
7369
+ errMsg: payload.errMsg || resultErrMsg || `${pending.name}:fail`
7370
+ };
7010
7371
  sendCallback(ap, pending.callbacks.fail, fail);
7011
7372
  sendCallback(ap, pending.callbacks.complete, fail);
7012
7373
  }
@@ -7152,20 +7513,14 @@ function disposePageSession(state, ap, page) {
7152
7513
  ap.pages.delete(page.bridgeId);
7153
7514
  state.pageSessions.delete(page.bridgeId);
7154
7515
  }
7155
- async function disposeAppSession(state, appSessionId, opts = {}) {
7156
- const ap = state.appSessions.get(appSessionId);
7157
- if (!ap) return;
7158
- state.appSessions.delete(appSessionId);
7159
- const registryHandle = ap.registryHandle;
7160
- ap.registryHandle = null;
7161
- void registryHandle?.dispose();
7162
- state.appLifecycle.dispose(appSessionId);
7163
- state.evictAppDataBridges(ap);
7516
+ function drainPendingApiCallsForSession(state, appSessionId) {
7164
7517
  for (const [requestId, pending] of state.pendingApiCalls) {
7165
7518
  if (pending.appSessionId !== appSessionId) continue;
7166
7519
  clearTimeout(pending.timer);
7167
7520
  state.pendingApiCalls.delete(requestId);
7168
7521
  }
7522
+ }
7523
+ function closeSessionPages(state, ap) {
7169
7524
  for (const page of ap.pages.values()) {
7170
7525
  if (page.renderWc && !page.renderWc.isDestroyed()) {
7171
7526
  state.wcIdToBridgeId.delete(page.renderWc.id);
@@ -7176,8 +7531,8 @@ async function disposeAppSession(state, appSessionId, opts = {}) {
7176
7531
  }
7177
7532
  state.pageSessions.delete(page.bridgeId);
7178
7533
  }
7179
- ap.pages.clear();
7180
- ap.listenerBag.dispose();
7534
+ }
7535
+ async function releaseServiceWindow(state, ap, opts) {
7181
7536
  if (ap.poolEntryId !== null && state.pool && !opts.serviceAlreadyClosed) {
7182
7537
  state.connections.reset(ap.serviceWc.id);
7183
7538
  if (ap.onServiceBoot && !ap.serviceWindow.isDestroyed()) {
@@ -7191,6 +7546,8 @@ async function disposeAppSession(state, appSessionId, opts = {}) {
7191
7546
  } else if (!opts.serviceAlreadyClosed && !ap.serviceWindow.isDestroyed()) {
7192
7547
  ap.serviceWindow.close();
7193
7548
  }
7549
+ }
7550
+ function unbindSessionFromSharedMaps(state, ap, appSessionId) {
7194
7551
  if (state.serviceWcIdToAppSessionId.get(ap.serviceWc.id) === appSessionId) {
7195
7552
  state.serviceWcIdToAppSessionId.delete(ap.serviceWc.id);
7196
7553
  }
@@ -7199,31 +7556,68 @@ async function disposeAppSession(state, appSessionId, opts = {}) {
7199
7556
  hosted.delete(appSessionId);
7200
7557
  if (hosted.size === 0) state.simulatorWcIdToAppSessionIds.delete(ap.simulatorWc.id);
7201
7558
  }
7559
+ }
7560
+ async function disposeAppSession(state, appSessionId, opts = {}) {
7561
+ const ap = state.appSessions.get(appSessionId);
7562
+ if (!ap) return;
7563
+ state.appSessions.delete(appSessionId);
7564
+ clearLaunchTimer(ap);
7565
+ const registryHandle = ap.registryHandle;
7566
+ ap.registryHandle = null;
7567
+ void registryHandle?.dispose();
7568
+ state.appLifecycle.dispose(appSessionId);
7569
+ state.evictAppDataBridges(ap);
7570
+ drainPendingApiCallsForSession(state, appSessionId);
7571
+ closeSessionPages(state, ap);
7572
+ ap.pages.clear();
7573
+ ap.listenerBag.dispose();
7574
+ await releaseServiceWindow(state, ap, opts);
7575
+ unbindSessionFromSharedMaps(state, ap, appSessionId);
7202
7576
  if (ap.resourceServer) {
7203
7577
  await ap.resourceServer.close().catch((error) => {
7204
7578
  console.warn("[bridge-router] resource server close failed:", error);
7205
7579
  });
7206
7580
  }
7207
7581
  }
7208
- async function loadAppConfig(resourceBase) {
7582
+ async function loadAppConfig(resourceBase, onUnreachable) {
7209
7583
  const cfgUrl = new URL("app-config.json", resourceBase.endsWith("/") ? resourceBase : `${resourceBase}/`).toString();
7210
7584
  try {
7211
7585
  const res = await fetch(cfgUrl);
7212
7586
  if (!res.ok) {
7587
+ const error = new Error(`app-config.json fetch ${res.status} at ${cfgUrl}`);
7213
7588
  console.warn(`[bridge-router] no app-config.json at ${cfgUrl} (${res.status})`);
7589
+ onUnreachable?.({ url: cfgUrl, error });
7214
7590
  return {};
7215
7591
  }
7216
7592
  return await res.json();
7217
7593
  } catch (error) {
7218
7594
  console.warn("[bridge-router] failed to fetch/parse app-config.json:", error);
7595
+ onUnreachable?.({ url: cfgUrl, error });
7219
7596
  return {};
7220
7597
  }
7221
7598
  }
7222
7599
  function buildAppManifest(appConfig, fallbackEntry) {
7223
7600
  const entry = appConfig.app?.entryPagePath || fallbackEntry;
7224
- const pages = appConfig.app?.pages?.length ? appConfig.app.pages : [entry];
7601
+ const hasCompiledPages = !!appConfig.app?.pages?.length;
7602
+ const pages = hasCompiledPages ? appConfig.app.pages : [entry];
7225
7603
  const tabBar = appConfig.app?.tabBar && Array.isArray(appConfig.app.tabBar.list) && appConfig.app.tabBar.list.length > 0 ? appConfig.app.tabBar : void 0;
7226
- return { entryPagePath: normalizePagePath(entry), pages: pages.map(normalizePagePath), tabBar };
7604
+ return {
7605
+ entryPagePath: normalizePagePath(entry),
7606
+ pages: pages.map(normalizePagePath),
7607
+ tabBar,
7608
+ // 'app-config' means `pages` above is the real compiled list mount gates
7609
+ // can trust; 'fallback' means app-config.json was unreachable and `pages`
7610
+ // is just the single requested page — nothing to validate membership
7611
+ // against, so every gate keyed on this source lets it through unchanged.
7612
+ source: hasCompiledPages ? "app-config" : "fallback"
7613
+ };
7614
+ }
7615
+ function resolveRootPagePath(manifest, requestedPagePath) {
7616
+ if (manifest.source !== "app-config" || manifest.pages.includes(requestedPagePath)) {
7617
+ return { resolvedPagePath: requestedPagePath, pageFallbackApplied: false };
7618
+ }
7619
+ const resolvedPagePath = manifest.pages.includes(manifest.entryPagePath) ? manifest.entryPagePath : manifest.pages[0];
7620
+ return { resolvedPagePath, pageFallbackApplied: true };
7227
7621
  }
7228
7622
  function resolvePageWindowConfig(appConfig, pagePath) {
7229
7623
  const normalized = normalizePagePath(pagePath);
@@ -7513,89 +7907,98 @@ appHandlers["App.getPageStack"] = async (ctx) => {
7513
7907
  }
7514
7908
  return { pageStack };
7515
7909
  };
7516
- appHandlers["App.callWxMethod"] = async (ctx, params) => {
7517
- const method = params.method;
7518
- const args = params.args || [];
7519
- if (ctx.bridge?.isNativeHost()) {
7520
- const serviceWc = ctx.bridge.getServiceWc();
7521
- if (!serviceWc) throw new Error("Service host not connected");
7522
- if (NAV_METHODS.has(method) || method === "navigateBack") {
7523
- const arg = method === "navigateBack" ? args[0] ?? { delta: 1 } : args[0] ?? {};
7524
- const since = ctx.bridge.getActiveBridgeId();
7525
- await serviceWc.executeJavaScript(`wx.${method}(${JSON.stringify(arg)})`);
7526
- const timeoutMs = method === "navigateBack" ? 1500 : 2e3;
7527
- await waitForActivePage(ctx.bridge, {
7528
- since,
7529
- timeoutMs,
7530
- onTimeout: () => console.warn(`[automation] ${method}: activePage signal not seen within ${timeoutMs}ms \u2014 proceeding on timeout floor`)
7531
- });
7532
- return { result: void 0 };
7533
- }
7534
- const argsStr2 = args.map((a) => JSON.stringify(a)).join(", ");
7535
- const result2 = await serviceWc.executeJavaScript(`
7536
- new Promise((resolve, reject) => {
7910
+ async function runNativeHostNav(bridge, serviceWc, method, args) {
7911
+ const arg = method === "navigateBack" ? args[0] ?? { delta: 1 } : args[0] ?? {};
7912
+ const since = bridge.getActiveBridgeId();
7913
+ await serviceWc.executeJavaScript(`wx.${method}(${JSON.stringify(arg)})`);
7914
+ const timeoutMs = method === "navigateBack" ? 1500 : 2e3;
7915
+ await waitForActivePage(bridge, {
7916
+ since,
7917
+ timeoutMs,
7918
+ onTimeout: () => console.warn(`[automation] ${method}: activePage signal not seen within ${timeoutMs}ms \u2014 proceeding on timeout floor`)
7919
+ });
7920
+ return { result: void 0 };
7921
+ }
7922
+ async function runNativeHostGeneric(serviceWc, method, args) {
7923
+ const argsStr = args.map((a) => JSON.stringify(a)).join(", ");
7924
+ const result = await serviceWc.executeJavaScript(`
7925
+ new Promise((resolve, reject) => {
7926
+ try {
7927
+ if (typeof wx === 'undefined' || typeof wx[${JSON.stringify(method)}] !== 'function') {
7928
+ reject(new Error('wx.' + ${JSON.stringify(method)} + ' is not a function in service host'))
7929
+ return
7930
+ }
7931
+ resolve(wx.${method}(${argsStr}))
7932
+ } catch (e) { reject(e && e.message ? e.message : String(e)) }
7933
+ })
7934
+ `);
7935
+ return { result };
7936
+ }
7937
+ async function runNativeHostWxMethod(bridge, method, args) {
7938
+ const serviceWc = bridge.getServiceWc();
7939
+ if (!serviceWc) throw new Error("Service host not connected");
7940
+ if (NAV_METHODS.has(method) || method === "navigateBack") {
7941
+ return runNativeHostNav(bridge, serviceWc, method, args);
7942
+ }
7943
+ return runNativeHostGeneric(serviceWc, method, args);
7944
+ }
7945
+ async function clickNavTarget(ctx, cleanUrl) {
7946
+ return evalInSim(ctx, `(() => {
7947
+ const iframes = Array.from(document.querySelectorAll('iframe'))
7948
+ const visible = iframes.filter((f) => {
7949
+ const cs = window.getComputedStyle(f)
7950
+ if (cs.display === 'none' || cs.visibility === 'hidden') return false
7951
+ const rect = f.getBoundingClientRect()
7952
+ return rect.width > 0 && rect.height > 0
7953
+ })
7954
+ const target = visible[visible.length - 1] || iframes[iframes.length - 1]
7955
+ if (!target || !target.contentDocument) return false
7956
+ const path = ${JSON.stringify(cleanUrl)}
7957
+ const el = Array.from(target.contentDocument.querySelectorAll('[data-path]'))
7958
+ .find((e) => e.getAttribute('data-path') === path)
7959
+ if (el) { el.click(); return true }
7960
+ return false
7961
+ })()`).catch(() => false);
7962
+ }
7963
+ async function invokeNavViaWx(ctx, method, cleanUrl) {
7964
+ const apiName = JSON.stringify(method);
7965
+ const urlJson = JSON.stringify(cleanUrl);
7966
+ await evalInSim(
7967
+ ctx,
7968
+ `(() => {
7969
+ const iframes = Array.from(document.querySelectorAll('iframe'))
7970
+ for (const f of iframes) {
7537
7971
  try {
7538
- if (typeof wx === 'undefined' || typeof wx[${JSON.stringify(method)}] !== 'function') {
7539
- reject(new Error('wx.' + ${JSON.stringify(method)} + ' is not a function in service host'))
7972
+ if (f.contentWindow && f.contentWindow.wx && typeof f.contentWindow.wx[${apiName}] === 'function') {
7973
+ f.contentWindow.wx[${apiName}]({ url: ${urlJson} })
7540
7974
  return
7541
7975
  }
7542
- resolve(wx.${method}(${argsStr2}))
7543
- } catch (e) { reject(e && e.message ? e.message : String(e)) }
7544
- })
7545
- `);
7546
- return { result: result2 };
7547
- }
7548
- if (["navigateTo", "redirectTo", "reLaunch", "switchTab"].includes(method)) {
7549
- const opts = args[0];
7550
- const url = opts?.url;
7551
- if (url) {
7552
- const cleanUrl = url.startsWith("/") ? url : `/${url}`;
7553
- const clicked = await evalInSim(ctx, `(() => {
7554
- const iframes = Array.from(document.querySelectorAll('iframe'))
7555
- const visible = iframes.filter((f) => {
7556
- const cs = window.getComputedStyle(f)
7557
- if (cs.display === 'none' || cs.visibility === 'hidden') return false
7558
- const rect = f.getBoundingClientRect()
7559
- return rect.width > 0 && rect.height > 0
7560
- })
7561
- const target = visible[visible.length - 1] || iframes[iframes.length - 1]
7562
- if (!target || !target.contentDocument) return false
7563
- const path = ${JSON.stringify(cleanUrl)}
7564
- const el = Array.from(target.contentDocument.querySelectorAll('[data-path]'))
7565
- .find((e) => e.getAttribute('data-path') === path)
7566
- if (el) { el.click(); return true }
7567
- return false
7568
- })()`).catch(() => false);
7569
- if (!clicked) {
7570
- const apiName = JSON.stringify(method);
7571
- const urlJson = JSON.stringify(cleanUrl);
7572
- await evalInSim(
7573
- ctx,
7574
- `(() => {
7575
- const iframes = Array.from(document.querySelectorAll('iframe'))
7576
- for (const f of iframes) {
7577
- try {
7578
- if (f.contentWindow && f.contentWindow.wx && typeof f.contentWindow.wx[${apiName}] === 'function') {
7579
- f.contentWindow.wx[${apiName}]({ url: ${urlJson} })
7580
- return
7581
- }
7582
- } catch (_) {}
7583
- }
7584
- if (typeof wx !== 'undefined' && wx && typeof wx[${apiName}] === 'function') {
7585
- wx[${apiName}]({ url: ${urlJson} })
7586
- }
7587
- })()`
7588
- );
7976
+ } catch (_) {}
7589
7977
  }
7590
- await new Promise((r) => setTimeout(r, 2e3));
7591
- }
7592
- return { result: void 0 };
7593
- }
7594
- if (method === "navigateBack") {
7595
- await evalInSim(ctx, "history.back()");
7596
- await new Promise((r) => setTimeout(r, 1500));
7597
- return { result: void 0 };
7978
+ if (typeof wx !== 'undefined' && wx && typeof wx[${apiName}] === 'function') {
7979
+ wx[${apiName}]({ url: ${urlJson} })
7980
+ }
7981
+ })()`
7982
+ );
7983
+ }
7984
+ async function runSimulatorNav(ctx, method, args) {
7985
+ const opts = args[0];
7986
+ const url = opts?.url;
7987
+ if (!url) return { result: void 0 };
7988
+ const cleanUrl = url.startsWith("/") ? url : `/${url}`;
7989
+ const clicked = await clickNavTarget(ctx, cleanUrl);
7990
+ if (!clicked) {
7991
+ await invokeNavViaWx(ctx, method, cleanUrl);
7598
7992
  }
7993
+ await new Promise((r) => setTimeout(r, 2e3));
7994
+ return { result: void 0 };
7995
+ }
7996
+ async function runSimulatorNavigateBack(ctx) {
7997
+ await evalInSim(ctx, "history.back()");
7998
+ await new Promise((r) => setTimeout(r, 1500));
7999
+ return { result: void 0 };
8000
+ }
8001
+ async function runSimulatorGeneric(ctx, method, args) {
7599
8002
  const argsStr = args.map((a) => JSON.stringify(a)).join(", ");
7600
8003
  const result = await evalInSim(ctx, `
7601
8004
  new Promise((resolve, reject) => {
@@ -7608,6 +8011,21 @@ appHandlers["App.callWxMethod"] = async (ctx, params) => {
7608
8011
  })
7609
8012
  `);
7610
8013
  return { result };
8014
+ }
8015
+ appHandlers["App.callWxMethod"] = async (ctx, params) => {
8016
+ const method = params.method;
8017
+ const args = params.args || [];
8018
+ const bridge = ctx.bridge;
8019
+ if (bridge?.isNativeHost()) {
8020
+ return runNativeHostWxMethod(bridge, method, args);
8021
+ }
8022
+ if (NAV_METHODS.has(method)) {
8023
+ return runSimulatorNav(ctx, method, args);
8024
+ }
8025
+ if (method === "navigateBack") {
8026
+ return runSimulatorNavigateBack(ctx);
8027
+ }
8028
+ return runSimulatorGeneric(ctx, method, args);
7611
8029
  };
7612
8030
  appHandlers["App.callFunction"] = async (ctx, params) => {
7613
8031
  const fnDecl = params.functionDeclaration;
@@ -7870,7 +8288,7 @@ async function startAutomationServer(ctx, port = 0) {
7870
8288
  const clients = /* @__PURE__ */ new Set();
7871
8289
  await new Promise((resolve, reject) => {
7872
8290
  wss.once("listening", () => resolve());
7873
- wss.once("error", (err) => reject(err));
8291
+ wss.once("error", (err2) => reject(err2));
7874
8292
  });
7875
8293
  const addr = wss.address();
7876
8294
  const resolvedPort = typeof addr === "object" && addr ? addr.port : port;
@@ -7975,8 +8393,8 @@ async function startAutomationServer(ctx, port = 0) {
7975
8393
  try {
7976
8394
  const result = await handler(ctx, params);
7977
8395
  response = { id, result };
7978
- } catch (err) {
7979
- response = { id, error: { message: err.message || String(err) } };
8396
+ } catch (err2) {
8397
+ response = { id, error: { message: err2.message || String(err2) } };
7980
8398
  }
7981
8399
  }
7982
8400
  if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(response));
@@ -8513,6 +8931,96 @@ function registerContextTools(server) {
8513
8931
 
8514
8932
  // src/main/services/mcp/tools/simulator-tools.ts
8515
8933
  import { z as z5 } from "zod";
8934
+ function ok(text) {
8935
+ return { content: [{ type: "text", text }] };
8936
+ }
8937
+ function err(msg) {
8938
+ return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
8939
+ }
8940
+ async function clickAt(c, x, y) {
8941
+ await c.Input.dispatchMouseEvent({ type: "mousePressed", x, y, button: "left", clickCount: 1 });
8942
+ await c.Input.dispatchMouseEvent({ type: "mouseReleased", x, y, button: "left", clickCount: 1 });
8943
+ }
8944
+ async function tapCoord(c, x, y) {
8945
+ if (typeof x !== "number" || typeof y !== "number") return err("tap_coord requires x and y");
8946
+ await clickAt(c, x, y);
8947
+ return ok(`Tapped at (${x}, ${y})`);
8948
+ }
8949
+ function buildTapSelectorExpression(selector, index) {
8950
+ return `(() => {
8951
+ const payload = ${JSON.stringify({ selector, index })}
8952
+ try {
8953
+ const matches = Array.from(document.querySelectorAll(payload.selector))
8954
+ if (matches.length === 0) {
8955
+ return { ok: false, reason: 'no_match', message: \`selector matched no elements: \${payload.selector}\` }
8956
+ }
8957
+ if (payload.index < 0 || payload.index >= matches.length) {
8958
+ return { ok: false, reason: 'out_of_range', message: \`nth \${payload.index} out of range (matches: \${matches.length})\` }
8959
+ }
8960
+ const element = matches[payload.index]
8961
+ element.scrollIntoView({ block: 'center', inline: 'center' })
8962
+ const rect = element.getBoundingClientRect()
8963
+ if (rect.width === 0 || rect.height === 0) {
8964
+ return { ok: false, reason: 'not_visible', message: \`selector matched an element, but it is not visible or rendered (zero rect): \${payload.selector}[\${payload.index}]\` }
8965
+ }
8966
+ return {
8967
+ ok: true,
8968
+ selector: payload.selector,
8969
+ index: payload.index,
8970
+ x: rect.left + rect.width / 2,
8971
+ y: rect.top + rect.height / 2,
8972
+ rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height },
8973
+ }
8974
+ } catch (error) {
8975
+ const message = error instanceof Error ? error.message : String(error)
8976
+ return { ok: false, reason: 'selector_error', message: \`invalid selector: \${payload.selector} (\${message})\` }
8977
+ }
8978
+ })()`;
8979
+ }
8980
+ async function tapSelector(c, selector, nth) {
8981
+ if (!selector) return err("tap_selector requires selector");
8982
+ const index = nth ?? 0;
8983
+ const expression = buildTapSelectorExpression(selector, index);
8984
+ const result = await c.Runtime.evaluate({ expression, returnByValue: true, awaitPromise: true });
8985
+ const value = result.result?.value;
8986
+ if (!value) return err(`selector evaluation returned no result: ${selector}[${index}]`);
8987
+ if (!value.ok) return err(value.message);
8988
+ await clickAt(c, value.x, value.y);
8989
+ return ok(`Tapped selector ${value.selector}[${value.index}] at (${value.x}, ${value.y}) within rect (${value.rect.left}, ${value.rect.top}, ${value.rect.width} x ${value.rect.height})`);
8990
+ }
8991
+ async function typeText(c, selector, nth, text) {
8992
+ if (!selector || typeof text !== "string") return err("type requires selector and text");
8993
+ const { root } = await c.DOM.getDocument({ depth: 0 });
8994
+ const { nodeIds } = await c.DOM.querySelectorAll({ nodeId: root.nodeId, selector });
8995
+ if (!nodeIds || nodeIds.length === 0) return err(`selector matched no elements: ${selector}`);
8996
+ const index = nth ?? 0;
8997
+ if (index < 0 || index >= nodeIds.length) return err(`nth ${index} out of range (matches: ${nodeIds.length})`);
8998
+ await c.DOM.focus({ nodeId: nodeIds[index] });
8999
+ try {
9000
+ await c.Input.insertText({ text });
9001
+ } catch {
9002
+ for (const ch of text) {
9003
+ await c.Input.dispatchKeyEvent({ type: "char", text: ch });
9004
+ }
9005
+ }
9006
+ return ok(`Typed ${text.length} char(s) into ${selector}[${index}]`);
9007
+ }
9008
+ async function scrollAt(c, x, y, deltaX, deltaY) {
9009
+ if (typeof x !== "number" || typeof y !== "number" || typeof deltaX !== "number" || typeof deltaY !== "number") {
9010
+ return err("scroll requires x, y, deltaX, deltaY");
9011
+ }
9012
+ await c.Input.dispatchMouseEvent({ type: "mouseWheel", x, y, deltaX, deltaY });
9013
+ return ok(`Scrolled at (${x}, ${y}) by (${deltaX}, ${deltaY})`);
9014
+ }
9015
+ async function dispatchKey(c, key) {
9016
+ if (!key) return err("key requires key");
9017
+ const isSingleChar = key.length === 1;
9018
+ const down = { type: "keyDown", key, code: key };
9019
+ if (isSingleChar) down.text = key;
9020
+ await c.Input.dispatchKeyEvent(down);
9021
+ await c.Input.dispatchKeyEvent({ type: "keyUp", key, code: key });
9022
+ return ok(`Dispatched key ${key}`);
9023
+ }
8516
9024
  function registerSimulatorTools(server) {
8517
9025
  server.tool("simulator_navigate", "Navigate the simulator to a URL, or reload the current page", {
8518
9026
  url: z5.string().optional().describe("URL to navigate to"),
@@ -8542,94 +9050,20 @@ function registerSimulatorTools(server) {
8542
9050
  deltaY: z5.number().optional().describe("Vertical wheel delta for `scroll`")
8543
9051
  }, async ({ action, x, y, selector, nth, text, key, deltaX, deltaY }) => {
8544
9052
  const c = getClient("simulator");
8545
- const err = (msg) => ({ content: [{ type: "text", text: `Error: ${msg}` }], isError: true });
8546
- const clickAt = async (cx, cy) => {
8547
- await c.Input.dispatchMouseEvent({ type: "mousePressed", x: cx, y: cy, button: "left", clickCount: 1 });
8548
- await c.Input.dispatchMouseEvent({ type: "mouseReleased", x: cx, y: cy, button: "left", clickCount: 1 });
8549
- };
8550
- if (action === "tap_coord") {
8551
- if (typeof x !== "number" || typeof y !== "number") return err("tap_coord requires x and y");
8552
- await clickAt(x, y);
8553
- return { content: [{ type: "text", text: `Tapped at (${x}, ${y})` }] };
8554
- }
8555
- if (action === "tap_selector") {
8556
- if (!selector) return err("tap_selector requires selector");
8557
- const index = nth ?? 0;
8558
- const expression = `(() => {
8559
- const payload = ${JSON.stringify({ selector, index })}
8560
- try {
8561
- const matches = Array.from(document.querySelectorAll(payload.selector))
8562
- if (matches.length === 0) {
8563
- return { ok: false, reason: 'no_match', message: \`selector matched no elements: \${payload.selector}\` }
8564
- }
8565
- if (payload.index < 0 || payload.index >= matches.length) {
8566
- return { ok: false, reason: 'out_of_range', message: \`nth \${payload.index} out of range (matches: \${matches.length})\` }
8567
- }
8568
- const element = matches[payload.index]
8569
- element.scrollIntoView({ block: 'center', inline: 'center' })
8570
- const rect = element.getBoundingClientRect()
8571
- if (rect.width === 0 || rect.height === 0) {
8572
- return { ok: false, reason: 'not_visible', message: \`selector matched an element, but it is not visible or rendered (zero rect): \${payload.selector}[\${payload.index}]\` }
8573
- }
8574
- return {
8575
- ok: true,
8576
- selector: payload.selector,
8577
- index: payload.index,
8578
- x: rect.left + rect.width / 2,
8579
- y: rect.top + rect.height / 2,
8580
- rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height },
8581
- }
8582
- } catch (error) {
8583
- const message = error instanceof Error ? error.message : String(error)
8584
- return { ok: false, reason: 'selector_error', message: \`invalid selector: \${payload.selector} (\${message})\` }
8585
- }
8586
- })()`;
8587
- const result = await c.Runtime.evaluate({ expression, returnByValue: true, awaitPromise: true });
8588
- const value = result.result?.value;
8589
- if (!value) return err(`selector evaluation returned no result: ${selector}[${index}]`);
8590
- if (!value.ok) return err(value.message);
8591
- await clickAt(value.x, value.y);
8592
- return {
8593
- content: [{
8594
- type: "text",
8595
- text: `Tapped selector ${value.selector}[${value.index}] at (${value.x}, ${value.y}) within rect (${value.rect.left}, ${value.rect.top}, ${value.rect.width} x ${value.rect.height})`
8596
- }]
8597
- };
8598
- }
8599
- if (action === "type") {
8600
- if (!selector || typeof text !== "string") return err("type requires selector and text");
8601
- const { root } = await c.DOM.getDocument({ depth: 0 });
8602
- const { nodeIds } = await c.DOM.querySelectorAll({ nodeId: root.nodeId, selector });
8603
- if (!nodeIds || nodeIds.length === 0) return err(`selector matched no elements: ${selector}`);
8604
- const index = nth ?? 0;
8605
- if (index < 0 || index >= nodeIds.length) return err(`nth ${index} out of range (matches: ${nodeIds.length})`);
8606
- await c.DOM.focus({ nodeId: nodeIds[index] });
8607
- try {
8608
- await c.Input.insertText({ text });
8609
- } catch {
8610
- for (const ch of text) {
8611
- await c.Input.dispatchKeyEvent({ type: "char", text: ch });
8612
- }
8613
- }
8614
- return { content: [{ type: "text", text: `Typed ${text.length} char(s) into ${selector}[${index}]` }] };
8615
- }
8616
- if (action === "scroll") {
8617
- if (typeof x !== "number" || typeof y !== "number" || typeof deltaX !== "number" || typeof deltaY !== "number") {
8618
- return err("scroll requires x, y, deltaX, deltaY");
8619
- }
8620
- await c.Input.dispatchMouseEvent({ type: "mouseWheel", x, y, deltaX, deltaY });
8621
- return { content: [{ type: "text", text: `Scrolled at (${x}, ${y}) by (${deltaX}, ${deltaY})` }] };
8622
- }
8623
- if (action === "key") {
8624
- if (!key) return err("key requires key");
8625
- const isSingleChar = key.length === 1;
8626
- const down = { type: "keyDown", key, code: key };
8627
- if (isSingleChar) down.text = key;
8628
- await c.Input.dispatchKeyEvent(down);
8629
- await c.Input.dispatchKeyEvent({ type: "keyUp", key, code: key });
8630
- return { content: [{ type: "text", text: `Dispatched key ${key}` }] };
9053
+ switch (action) {
9054
+ case "tap_coord":
9055
+ return tapCoord(c, x, y);
9056
+ case "tap_selector":
9057
+ return tapSelector(c, selector, nth);
9058
+ case "type":
9059
+ return typeText(c, selector, nth, text);
9060
+ case "scroll":
9061
+ return scrollAt(c, x, y, deltaX, deltaY);
9062
+ case "key":
9063
+ return dispatchKey(c, key);
9064
+ default:
9065
+ return err(`unknown action: ${String(action)}`);
8631
9066
  }
8632
- return err(`unknown action: ${action}`);
8633
9067
  });
8634
9068
  }
8635
9069
 
@@ -8684,13 +9118,13 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
8684
9118
  res.writeHead(404).end();
8685
9119
  }
8686
9120
  });
8687
- httpServer.on("error", (err) => {
8688
- if (err.code === "EADDRINUSE") {
9121
+ httpServer.on("error", (err2) => {
9122
+ if (err2.code === "EADDRINUSE") {
8689
9123
  console.warn(`[MCP] Port ${mcpPort} already in use \u2014 MCP server not started`);
8690
9124
  recordMcpFailed("port-in-use");
8691
9125
  } else {
8692
- console.error("[MCP] Server error:", err);
8693
- recordMcpFailed(err.message);
9126
+ console.error("[MCP] Server error:", err2);
9127
+ recordMcpFailed(err2.message);
8694
9128
  }
8695
9129
  });
8696
9130
  httpServer.listen(mcpPort, "127.0.0.1", () => {
@@ -8706,6 +9140,28 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
8706
9140
 
8707
9141
  // src/main/services/network-forward/index.ts
8708
9142
  import { DisposableRegistry as DisposableRegistry7, toDisposable as toDisposable5 } from "@dimina-kit/electron-deck/main";
9143
+
9144
+ // src/main/services/network-forward/dispatch-batch.ts
9145
+ function packDispatchBatch(queue, maxSingleChars, maxBatchChars) {
9146
+ const batch = [];
9147
+ const chunked = [];
9148
+ let batchChars = 0;
9149
+ let i = 0;
9150
+ for (; i < queue.length; i++) {
9151
+ const msg = queue[i];
9152
+ if (msg.length > maxSingleChars) {
9153
+ if (batch.length > 0) break;
9154
+ chunked.push(msg);
9155
+ continue;
9156
+ }
9157
+ if (batch.length > 0 && batchChars + msg.length > maxBatchChars) break;
9158
+ batch.push(msg);
9159
+ batchChars += msg.length;
9160
+ }
9161
+ return { batch, chunked, remaining: queue.slice(i) };
9162
+ }
9163
+
9164
+ // src/main/services/network-forward/index.ts
8709
9165
  var REWRITE_REQUEST_ID_METHODS = /* @__PURE__ */ new Set([
8710
9166
  "Network.requestWillBeSent",
8711
9167
  "Network.requestWillBeSentExtraInfo",
@@ -8838,6 +9294,7 @@ function createNetworkForwarder(bridge) {
8838
9294
  let sink = "idle";
8839
9295
  let probeConsoleBuffer = [];
8840
9296
  let readyTimeoutTimer = null;
9297
+ let probeDeadline = null;
8841
9298
  let dispatchQueue = [];
8842
9299
  let flushScheduled = false;
8843
9300
  let readyRetryTimer = null;
@@ -8848,6 +9305,7 @@ function createNetworkForwarder(bridge) {
8848
9305
  function beginProbing() {
8849
9306
  if (sink === "probing") return;
8850
9307
  sink = "probing";
9308
+ probeDeadline = Date.now() + DEVTOOLS_READY_TIMEOUT_MS;
8851
9309
  if (readyTimeoutTimer) clearTimeout(readyTimeoutTimer);
8852
9310
  readyTimeoutTimer = setTimeout(() => {
8853
9311
  readyTimeoutTimer = null;
@@ -8856,6 +9314,7 @@ function createNetworkForwarder(bridge) {
8856
9314
  }
8857
9315
  function markReady() {
8858
9316
  sink = "ready";
9317
+ probeDeadline = null;
8859
9318
  if (readyTimeoutTimer) {
8860
9319
  clearTimeout(readyTimeoutTimer);
8861
9320
  readyTimeoutTimer = null;
@@ -8868,6 +9327,7 @@ function createNetworkForwarder(bridge) {
8868
9327
  }
8869
9328
  function degradeToConsole() {
8870
9329
  sink = "degraded";
9330
+ probeDeadline = null;
8871
9331
  dispatchQueue = [];
8872
9332
  if (readyTimeoutTimer) {
8873
9333
  clearTimeout(readyTimeoutTimer);
@@ -8922,21 +9382,8 @@ function createNetworkForwarder(bridge) {
8922
9382
  return;
8923
9383
  }
8924
9384
  if (sink === "idle") beginProbing();
8925
- const batch = [];
8926
- let batchChars = 0;
8927
- let i = 0;
8928
- for (; i < dispatchQueue.length; i++) {
8929
- const msg = dispatchQueue[i];
8930
- if (msg.length > MAX_SINGLE_DISPATCH_CHARS2) {
8931
- if (batch.length > 0) break;
8932
- dispatchChunked(wc, msg);
8933
- continue;
8934
- }
8935
- if (batch.length > 0 && batchChars + msg.length > MAX_BATCH_CHARS) break;
8936
- batch.push(msg);
8937
- batchChars += msg.length;
8938
- }
8939
- const remaining = dispatchQueue.slice(i);
9385
+ const { batch, chunked, remaining } = packDispatchBatch(dispatchQueue, MAX_SINGLE_DISPATCH_CHARS2, MAX_BATCH_CHARS);
9386
+ for (const msg of chunked) dispatchChunked(wc, msg);
8940
9387
  if (batch.length === 0) {
8941
9388
  dispatchQueue = remaining;
8942
9389
  if (dispatchQueue.length > 0) scheduleFlush();
@@ -8952,8 +9399,8 @@ function createNetworkForwarder(bridge) {
8952
9399
  }
8953
9400
  dispatchQueue = remaining;
8954
9401
  if (remaining.length > 0) scheduleFlush();
8955
- wc.executeJavaScript(script, true).then((ok) => {
8956
- if (ok === true) {
9402
+ wc.executeJavaScript(script, true).then((ok2) => {
9403
+ if (ok2 === true) {
8957
9404
  if (sink !== "ready") markReady();
8958
9405
  return;
8959
9406
  }
@@ -8985,9 +9432,17 @@ function createNetworkForwarder(bridge) {
8985
9432
  }
8986
9433
  function scheduleReadyRetry() {
8987
9434
  if (readyRetryTimer || sink === "ready" || sink === "degraded") return;
9435
+ if (probeDeadline !== null && Date.now() >= probeDeadline) {
9436
+ degradeToConsole();
9437
+ return;
9438
+ }
8988
9439
  readyRetryTimer = setTimeout(() => {
8989
9440
  readyRetryTimer = null;
8990
9441
  if (sink === "degraded") return;
9442
+ if (probeDeadline !== null && Date.now() >= probeDeadline) {
9443
+ degradeToConsole();
9444
+ return;
9445
+ }
8991
9446
  if (dispatchQueue.length > 0) scheduleFlush();
8992
9447
  }, READY_RETRY_MS);
8993
9448
  }
@@ -9026,6 +9481,7 @@ function createNetworkForwarder(bridge) {
9026
9481
  dispatchQueue = [];
9027
9482
  probeConsoleBuffer = [];
9028
9483
  sink = "idle";
9484
+ probeDeadline = null;
9029
9485
  if (readyRetryTimer) {
9030
9486
  clearTimeout(readyRetryTimer);
9031
9487
  readyRetryTimer = null;
@@ -9048,8 +9504,8 @@ function createNetworkForwarder(bridge) {
9048
9504
  if (!wc.debugger.isAttached()) {
9049
9505
  wc.debugger.attach("1.3");
9050
9506
  }
9051
- } catch (err) {
9052
- console.warn("[network-forward] debugger.attach failed; simulator network not captured:", err instanceof Error ? err.message : err);
9507
+ } catch (err2) {
9508
+ console.warn("[network-forward] debugger.attach failed; simulator network not captured:", err2 instanceof Error ? err2.message : err2);
9053
9509
  return;
9054
9510
  }
9055
9511
  simWc = wc;
@@ -9145,8 +9601,8 @@ function createNetworkForwarder(bridge) {
9145
9601
  }
9146
9602
  });
9147
9603
  }
9148
- void wc.debugger.sendCommand("Network.enable").catch((err) => {
9149
- console.warn("[network-forward] Network.enable failed:", err instanceof Error ? err.message : err);
9604
+ void wc.debugger.sendCommand("Network.enable").catch((err2) => {
9605
+ console.warn("[network-forward] Network.enable failed:", err2 instanceof Error ? err2.message : err2);
9150
9606
  });
9151
9607
  }
9152
9608
  function maybeFallback(record) {
@@ -9176,6 +9632,7 @@ function createNetworkForwarder(bridge) {
9176
9632
  clearTimeout(readyRetryTimer);
9177
9633
  readyRetryTimer = null;
9178
9634
  }
9635
+ probeDeadline = null;
9179
9636
  probeConsoleBuffer = [];
9180
9637
  if (!devtoolsWc) {
9181
9638
  sink = "idle";
@@ -9199,6 +9656,7 @@ function createNetworkForwarder(bridge) {
9199
9656
  }
9200
9657
  });
9201
9658
  }
9659
+ sink = "idle";
9202
9660
  beginProbing();
9203
9661
  if (dispatchQueue.length > 0) scheduleFlush();
9204
9662
  }
@@ -9316,41 +9774,46 @@ function setupSimulatorWxml(host, options) {
9316
9774
  }
9317
9775
 
9318
9776
  // src/shared/appdata-accumulator.ts
9319
- function decodeWorkerMessage(message) {
9320
- let payload = message;
9321
- if (typeof payload === "string") {
9322
- try {
9323
- payload = JSON.parse(payload);
9324
- } catch {
9325
- return null;
9326
- }
9777
+ function parseMessagePayload(message) {
9778
+ if (typeof message !== "string") return message;
9779
+ try {
9780
+ return JSON.parse(message);
9781
+ } catch {
9782
+ return null;
9327
9783
  }
9784
+ }
9785
+ function decodeUpdateBatchBody(rawBody) {
9786
+ const body = rawBody;
9787
+ if (!body || typeof body !== "object") return null;
9788
+ if (typeof body.bridgeId !== "string" || !Array.isArray(body.updates)) return null;
9789
+ const out = [];
9790
+ for (const u of body.updates) {
9791
+ if (!u || typeof u.moduleId !== "string") continue;
9792
+ if (!u.moduleId.startsWith("page_")) continue;
9793
+ out.push({ mode: "patch", bridgeId: body.bridgeId, moduleId: u.moduleId, data: u.data });
9794
+ }
9795
+ return out.length > 0 ? out : null;
9796
+ }
9797
+ function decodePageInitBody(moduleId, rawBody) {
9798
+ const body = rawBody;
9799
+ if (!body || typeof body !== "object") return null;
9800
+ if (typeof body.bridgeId !== "string" || typeof body.path !== "string") return null;
9801
+ if (!body.data || typeof body.data !== "object") return null;
9802
+ return [{
9803
+ mode: "init",
9804
+ bridgeId: body.bridgeId,
9805
+ moduleId,
9806
+ componentPath: body.path,
9807
+ data: body.data
9808
+ }];
9809
+ }
9810
+ function decodeWorkerMessage(message) {
9811
+ const payload = parseMessagePayload(message);
9328
9812
  if (!payload || typeof payload !== "object") return null;
9329
9813
  const record = payload;
9330
- if (record.type === "ub") {
9331
- const body = record.body;
9332
- if (!body || typeof body !== "object") return null;
9333
- if (typeof body.bridgeId !== "string" || !Array.isArray(body.updates)) return null;
9334
- const out = [];
9335
- for (const u of body.updates) {
9336
- if (!u || typeof u.moduleId !== "string") continue;
9337
- if (!u.moduleId.startsWith("page_")) continue;
9338
- out.push({ mode: "patch", bridgeId: body.bridgeId, moduleId: u.moduleId, data: u.data });
9339
- }
9340
- return out.length > 0 ? out : null;
9341
- }
9814
+ if (record.type === "ub") return decodeUpdateBatchBody(record.body);
9342
9815
  if (typeof record.type === "string" && record.type.startsWith("page_")) {
9343
- const body = record.body;
9344
- if (!body || typeof body !== "object") return null;
9345
- if (typeof body.bridgeId !== "string" || typeof body.path !== "string") return null;
9346
- if (!body.data || typeof body.data !== "object") return null;
9347
- return [{
9348
- mode: "init",
9349
- bridgeId: body.bridgeId,
9350
- moduleId: record.type,
9351
- componentPath: body.path,
9352
- data: body.data
9353
- }];
9816
+ return decodePageInitBody(record.type, record.body);
9354
9817
  }
9355
9818
  return null;
9356
9819
  }
@@ -9804,38 +10267,51 @@ var EXT_MIME = {
9804
10267
  ".pdf": "application/pdf",
9805
10268
  ".zip": "application/zip"
9806
10269
  };
10270
+ function isPng(head) {
10271
+ return head.length >= 8 && head[0] === 137 && head[1] === 80 && head[2] === 78 && head[3] === 71 && head[4] === 13 && head[5] === 10 && head[6] === 26 && head[7] === 10;
10272
+ }
10273
+ function isJpeg(head) {
10274
+ return head.length >= 3 && head[0] === 255 && head[1] === 216 && head[2] === 255;
10275
+ }
10276
+ function isGif(head) {
10277
+ return head.length >= 6 && head[0] === 71 && head[1] === 73 && head[2] === 70 && head[3] === 56 && (head[4] === 55 || head[4] === 57) && head[5] === 97;
10278
+ }
10279
+ function isWebp(head) {
10280
+ return head.length >= 12 && head[0] === 82 && head[1] === 73 && head[2] === 70 && head[3] === 70 && head[8] === 87 && head[9] === 69 && head[10] === 66 && head[11] === 80;
10281
+ }
10282
+ function isMp4(head) {
10283
+ return head.length >= 12 && head[4] === 102 && head[5] === 116 && head[6] === 121 && head[7] === 112;
10284
+ }
10285
+ function isPdf(head) {
10286
+ return head.length >= 4 && head[0] === 37 && head[1] === 80 && head[2] === 68 && head[3] === 70;
10287
+ }
10288
+ function isBmp(head) {
10289
+ return head.length >= 2 && head[0] === 66 && head[1] === 77;
10290
+ }
10291
+ function isZip(head) {
10292
+ return head.length >= 4 && head[0] === 80 && head[1] === 75 && head[2] === 3 && head[3] === 4;
10293
+ }
10294
+ function isId3Mp3(head) {
10295
+ return head.length >= 3 && head[0] === 73 && head[1] === 68 && head[2] === 51;
10296
+ }
10297
+ function isOgg(head) {
10298
+ return head.length >= 4 && head[0] === 79 && head[1] === 103 && head[2] === 103 && head[3] === 83;
10299
+ }
10300
+ var MAGIC_SNIFFERS = [
10301
+ { mime: "image/png", match: isPng },
10302
+ { mime: "image/jpeg", match: isJpeg },
10303
+ { mime: "image/gif", match: isGif },
10304
+ { mime: "image/webp", match: isWebp },
10305
+ { mime: "video/mp4", match: isMp4 },
10306
+ { mime: "application/pdf", match: isPdf },
10307
+ { mime: "image/bmp", match: isBmp },
10308
+ { mime: "application/zip", match: isZip },
10309
+ { mime: "audio/mpeg", match: isId3Mp3 },
10310
+ { mime: "audio/ogg", match: isOgg }
10311
+ ];
9807
10312
  function sniffMime(head) {
9808
- if (head.length >= 8 && head[0] === 137 && head[1] === 80 && head[2] === 78 && head[3] === 71 && head[4] === 13 && head[5] === 10 && head[6] === 26 && head[7] === 10) {
9809
- return "image/png";
9810
- }
9811
- if (head.length >= 3 && head[0] === 255 && head[1] === 216 && head[2] === 255) {
9812
- return "image/jpeg";
9813
- }
9814
- if (head.length >= 6 && head[0] === 71 && head[1] === 73 && head[2] === 70 && head[3] === 56 && (head[4] === 55 || head[4] === 57) && head[5] === 97) {
9815
- return "image/gif";
9816
- }
9817
- if (head.length >= 12 && head[0] === 82 && head[1] === 73 && head[2] === 70 && head[3] === 70 && head[8] === 87 && head[9] === 69 && head[10] === 66 && head[11] === 80) {
9818
- return "image/webp";
9819
- }
9820
- if (head.length >= 12 && head[4] === 102 && head[5] === 116 && head[6] === 121 && head[7] === 112) {
9821
- return "video/mp4";
9822
- }
9823
- if (head.length >= 4 && head[0] === 37 && head[1] === 80 && head[2] === 68 && head[3] === 70) {
9824
- return "application/pdf";
9825
- }
9826
- if (head.length >= 2 && head[0] === 66 && head[1] === 77) {
9827
- return "image/bmp";
9828
- }
9829
- if (head.length >= 4 && head[0] === 80 && head[1] === 75 && head[2] === 3 && head[3] === 4) {
9830
- return "application/zip";
9831
- }
9832
- if (head.length >= 3 && head[0] === 73 && head[1] === 68 && head[2] === 51) {
9833
- return "audio/mpeg";
9834
- }
9835
- if (head.length >= 4 && head[0] === 79 && head[1] === 103 && head[2] === 103 && head[3] === 83) {
9836
- return "audio/ogg";
9837
- }
9838
- return null;
10313
+ const hit = MAGIC_SNIFFERS.find((sniffer) => sniffer.match(head));
10314
+ return hit ? hit.mime : null;
9839
10315
  }
9840
10316
  function extMime(realPath) {
9841
10317
  const ext = path20.extname(realPath).toLowerCase();
@@ -9852,6 +10328,44 @@ function detectMime(realPath, head) {
9852
10328
  function etagOf(mtimeMs, size) {
9853
10329
  return `W/"${Math.floor(mtimeMs)}-${size}"`;
9854
10330
  }
10331
+ function resolveRange(range, totalSize) {
10332
+ const { start, end } = range;
10333
+ if (!Number.isFinite(start) || !Number.isFinite(end)) {
10334
+ throw new RangeError(`invalid range: ${start}-${end}`);
10335
+ }
10336
+ if (start < 0) throw new RangeError(`range start out of bounds: ${start}`);
10337
+ if (start > end) throw new RangeError(`range start > end: ${start} > ${end}`);
10338
+ if (start >= totalSize) {
10339
+ throw new RangeError(`range start beyond file size: ${start} >= ${totalSize}`);
10340
+ }
10341
+ const clampedEnd = Math.min(end, totalSize - 1);
10342
+ return { sliceStart: start, sliceLen: clampedEnd - start + 1 };
10343
+ }
10344
+ async function readHeadBytes(handle, totalSize) {
10345
+ const head = Buffer.alloc(Math.min(12, totalSize));
10346
+ if (head.length > 0) {
10347
+ await handle.read(head, 0, head.length, 0);
10348
+ }
10349
+ return head;
10350
+ }
10351
+ async function readFullBody(handle, head, totalSize) {
10352
+ const bytes = Buffer.alloc(totalSize);
10353
+ if (totalSize === 0) return bytes;
10354
+ if (totalSize <= head.length) {
10355
+ head.copy(bytes, 0, 0, totalSize);
10356
+ return bytes;
10357
+ }
10358
+ head.copy(bytes, 0, 0, head.length);
10359
+ await handle.read(bytes, head.length, totalSize - head.length, head.length);
10360
+ return bytes;
10361
+ }
10362
+ async function readRangeBody(handle, sliceStart, sliceLen) {
10363
+ const bytes = Buffer.alloc(sliceLen);
10364
+ if (sliceLen > 0) {
10365
+ await handle.read(bytes, 0, sliceLen, sliceStart);
10366
+ }
10367
+ return bytes;
10368
+ }
9855
10369
  async function readDiskFile(realPath, opts) {
9856
10370
  const handle = await fs9.open(realPath, "r");
9857
10371
  try {
@@ -9859,43 +10373,9 @@ async function readDiskFile(realPath, opts) {
9859
10373
  const totalSize = st.size;
9860
10374
  const mtimeMs = st.mtimeMs;
9861
10375
  const range = opts?.range;
9862
- let sliceStart = 0;
9863
- let sliceLen = totalSize;
9864
- if (range) {
9865
- const { start, end } = range;
9866
- if (!Number.isFinite(start) || !Number.isFinite(end)) {
9867
- throw new RangeError(`invalid range: ${start}-${end}`);
9868
- }
9869
- if (start < 0) throw new RangeError(`range start out of bounds: ${start}`);
9870
- if (start > end) throw new RangeError(`range start > end: ${start} > ${end}`);
9871
- if (start >= totalSize) {
9872
- throw new RangeError(`range start beyond file size: ${start} >= ${totalSize}`);
9873
- }
9874
- const clampedEnd = Math.min(end, totalSize - 1);
9875
- sliceStart = start;
9876
- sliceLen = clampedEnd - start + 1;
9877
- }
9878
- const head = Buffer.alloc(Math.min(12, totalSize));
9879
- if (head.length > 0) {
9880
- await handle.read(head, 0, head.length, 0);
9881
- }
9882
- let bytes;
9883
- if (!range) {
9884
- bytes = Buffer.alloc(totalSize);
9885
- if (totalSize > 0) {
9886
- if (totalSize <= head.length) {
9887
- head.copy(bytes, 0, 0, totalSize);
9888
- } else {
9889
- head.copy(bytes, 0, 0, head.length);
9890
- await handle.read(bytes, head.length, totalSize - head.length, head.length);
9891
- }
9892
- }
9893
- } else {
9894
- bytes = Buffer.alloc(sliceLen);
9895
- if (sliceLen > 0) {
9896
- await handle.read(bytes, 0, sliceLen, sliceStart);
9897
- }
9898
- }
10376
+ const { sliceStart, sliceLen } = range ? resolveRange(range, totalSize) : { sliceStart: 0, sliceLen: totalSize };
10377
+ const head = await readHeadBytes(handle, totalSize);
10378
+ const bytes = range ? await readRangeBody(handle, sliceStart, sliceLen) : await readFullBody(handle, head, totalSize);
9899
10379
  return {
9900
10380
  bytes,
9901
10381
  mime: detectMime(realPath, head),
@@ -10069,12 +10549,12 @@ async function enforceSandbox(realPath) {
10069
10549
  throw new Error("sandbox: realPath escapes the user-data base via symlink");
10070
10550
  }
10071
10551
  break;
10072
- } catch (err) {
10073
- if (err.code === "ENOENT") {
10552
+ } catch (err2) {
10553
+ if (err2.code === "ENOENT") {
10074
10554
  probe = path21.dirname(probe);
10075
10555
  continue;
10076
10556
  }
10077
- throw err;
10557
+ throw err2;
10078
10558
  }
10079
10559
  }
10080
10560
  return normalized;
@@ -10246,6 +10726,11 @@ function setupSimulatorTempFiles(simSession) {
10246
10726
  // src/main/services/views/simulator-session-policy.ts
10247
10727
  import { session as session3 } from "electron";
10248
10728
  import { toDisposable as toDisposable7 } from "@dimina-kit/electron-deck/main";
10729
+ var CORS_OVERRIDE_HEADERS = /* @__PURE__ */ new Set([
10730
+ "access-control-allow-origin",
10731
+ "access-control-allow-headers",
10732
+ "access-control-allow-methods"
10733
+ ]);
10249
10734
  function applySimulatorWebRequestPolicy(simulatorSession) {
10250
10735
  simulatorSession.webRequest.onBeforeSendHeaders((details, callback) => {
10251
10736
  const forcedReferer = getSimulatorServicewechatReferer();
@@ -10256,6 +10741,9 @@ function applySimulatorWebRequestPolicy(simulatorSession) {
10256
10741
  });
10257
10742
  simulatorSession.webRequest.onHeadersReceived((details, callback) => {
10258
10743
  const headers = details.responseHeaders ?? {};
10744
+ for (const key of Object.keys(headers)) {
10745
+ if (CORS_OVERRIDE_HEADERS.has(key.toLowerCase())) delete headers[key];
10746
+ }
10259
10747
  headers["access-control-allow-origin"] = ["*"];
10260
10748
  headers["access-control-allow-headers"] = ["*"];
10261
10749
  headers["access-control-allow-methods"] = ["*"];
@@ -10330,8 +10818,8 @@ function serveStaticFile(res, root, pathname) {
10330
10818
  res.end("Forbidden");
10331
10819
  return;
10332
10820
  }
10333
- nodeFs2.stat(realFile, (err, stat) => {
10334
- if (err || !stat.isFile()) {
10821
+ nodeFs2.stat(realFile, (err2, stat) => {
10822
+ if (err2 || !stat.isFile()) {
10335
10823
  res.writeHead(404);
10336
10824
  res.end("Not Found");
10337
10825
  return;
@@ -10412,6 +10900,56 @@ function fsErrorStatus(e) {
10412
10900
  if (code === "ENOENT") return 404;
10413
10901
  return 500;
10414
10902
  }
10903
+ async function fsStat(ctx) {
10904
+ const st = await statWithin(ctx.projectRoot, ctx.rel);
10905
+ jsonRes(ctx.res, 200, { type: st.isDirectory() ? 2 : 1, size: st.size, ctime: st.ctimeMs, mtime: st.mtimeMs });
10906
+ }
10907
+ async function fsReaddir(ctx) {
10908
+ const entries = await readdirWithin(ctx.projectRoot, ctx.rel);
10909
+ jsonRes(ctx.res, 200, entries.map((e) => [e.name, e.isDirectory() ? 2 : 1]));
10910
+ }
10911
+ async function fsRead(ctx) {
10912
+ const buf = await readFileBufferWithin(ctx.projectRoot, ctx.rel);
10913
+ ctx.res.writeHead(200, { "Content-Type": "application/octet-stream", "Content-Length": buf.length });
10914
+ ctx.res.end(buf);
10915
+ }
10916
+ async function fsWrite(ctx) {
10917
+ let buf;
10918
+ try {
10919
+ buf = await readBody(ctx.req);
10920
+ } catch (e) {
10921
+ if (e instanceof BodyTooLargeError) return jsonRes(ctx.res, 413, { error: e.message });
10922
+ throw e;
10923
+ }
10924
+ await writeFileWithin(ctx.projectRoot, ctx.rel, buf);
10925
+ ctx.res.writeHead(204);
10926
+ ctx.res.end();
10927
+ }
10928
+ async function fsMkdir(ctx) {
10929
+ await mkdirWithin(ctx.projectRoot, ctx.rel);
10930
+ ctx.res.writeHead(204);
10931
+ ctx.res.end();
10932
+ }
10933
+ async function fsDelete(ctx) {
10934
+ await deleteWithin(ctx.projectRoot, ctx.rel);
10935
+ ctx.res.writeHead(204);
10936
+ ctx.res.end();
10937
+ }
10938
+ async function fsRename(ctx) {
10939
+ const toRel = (ctx.url.searchParams.get("to") ?? "").replace(/^\/+/, "");
10940
+ await renameWithin(ctx.projectRoot, ctx.rel, toRel);
10941
+ ctx.res.writeHead(204);
10942
+ ctx.res.end();
10943
+ }
10944
+ var FS_ACTIONS = {
10945
+ stat: fsStat,
10946
+ readdir: fsReaddir,
10947
+ read: fsRead,
10948
+ write: fsWrite,
10949
+ mkdir: fsMkdir,
10950
+ delete: fsDelete,
10951
+ rename: fsRename
10952
+ };
10415
10953
  async function handleFsRequest(req, res, projectRoot, url) {
10416
10954
  const action = url.pathname.slice("/__fs/".length);
10417
10955
  const rel = (url.searchParams.get("p") ?? "").replace(/^\/+/, "") || ".";
@@ -10421,51 +10959,12 @@ async function handleFsRequest(req, res, projectRoot, url) {
10421
10959
  return jsonRes(res, rejectStatus, { error: "mutating fs action requires a non-GET same-origin request" });
10422
10960
  }
10423
10961
  }
10962
+ const handler = FS_ACTIONS[action];
10963
+ if (!handler) return jsonRes(res, 404, { error: "unknown fs action: " + action });
10424
10964
  try {
10425
- if (action === "stat") {
10426
- const st = await statWithin(projectRoot, rel);
10427
- return jsonRes(res, 200, { type: st.isDirectory() ? 2 : 1, size: st.size, ctime: st.ctimeMs, mtime: st.mtimeMs });
10428
- }
10429
- if (action === "readdir") {
10430
- const entries = await readdirWithin(projectRoot, rel);
10431
- return jsonRes(res, 200, entries.map((e) => [e.name, e.isDirectory() ? 2 : 1]));
10432
- }
10433
- if (action === "read") {
10434
- const buf = await readFileBufferWithin(projectRoot, rel);
10435
- res.writeHead(200, { "Content-Type": "application/octet-stream", "Content-Length": buf.length });
10436
- return void res.end(buf);
10437
- }
10438
- if (action === "write") {
10439
- let buf;
10440
- try {
10441
- buf = await readBody(req);
10442
- } catch (e) {
10443
- if (e instanceof BodyTooLargeError) return jsonRes(res, 413, { error: e.message });
10444
- throw e;
10445
- }
10446
- await writeFileWithin(projectRoot, rel, buf);
10447
- res.writeHead(204);
10448
- return void res.end();
10449
- }
10450
- if (action === "mkdir") {
10451
- await mkdirWithin(projectRoot, rel);
10452
- res.writeHead(204);
10453
- return void res.end();
10454
- }
10455
- if (action === "delete") {
10456
- await deleteWithin(projectRoot, rel);
10457
- res.writeHead(204);
10458
- return void res.end();
10459
- }
10460
- if (action === "rename") {
10461
- const toRel = (url.searchParams.get("to") ?? "").replace(/^\/+/, "");
10462
- await renameWithin(projectRoot, rel, toRel);
10463
- res.writeHead(204);
10464
- return void res.end();
10465
- }
10466
- return jsonRes(res, 404, { error: "unknown fs action: " + action });
10965
+ await handler({ req, res, projectRoot, rel, url });
10467
10966
  } catch (e) {
10468
- return jsonRes(res, fsErrorStatus(e), { error: String(e.message), code: e.code });
10967
+ jsonRes(res, fsErrorStatus(e), { error: String(e.message), code: e.code });
10469
10968
  }
10470
10969
  }
10471
10970
  async function listFilesRecursive(dir, base = "") {
@@ -10598,8 +11097,8 @@ var UpdateManager = class {
10598
11097
  return { hasUpdate: true, info };
10599
11098
  }
10600
11099
  return { hasUpdate: false };
10601
- } catch (err) {
10602
- console.warn("[UpdateManager] check failed:", err);
11100
+ } catch (err2) {
11101
+ console.warn("[UpdateManager] check failed:", err2);
10603
11102
  return { hasUpdate: false };
10604
11103
  }
10605
11104
  }
@@ -10613,9 +11112,9 @@ var UpdateManager = class {
10613
11112
  });
10614
11113
  this.downloadedPath = filePath;
10615
11114
  return { success: true, filePath };
10616
- } catch (err) {
10617
- console.warn("[UpdateManager] download failed:", err);
10618
- return { success: false, error: String(err) };
11115
+ } catch (err2) {
11116
+ console.warn("[UpdateManager] download failed:", err2);
11117
+ return { success: false, error: String(err2) };
10619
11118
  }
10620
11119
  }
10621
11120
  install() {
@@ -10719,8 +11218,8 @@ function resolveModules(config) {
10719
11218
  }
10720
11219
  async function disposeContext(ctx) {
10721
11220
  await ctx.workspace.closeProject();
10722
- await ctx.registry.dispose().catch((err) => {
10723
- console.warn("[workbench] dispose registry encountered errors:", err);
11221
+ await ctx.registry.dispose().catch((err2) => {
11222
+ console.warn("[workbench] dispose registry encountered errors:", err2);
10724
11223
  });
10725
11224
  }
10726
11225
  function createConfiguredMainWindow(config, rendererDir2) {
@@ -10936,27 +11435,10 @@ async function createDevtoolsRuntime(config = {}) {
10936
11435
  appDataKeys: []
10937
11436
  };
10938
11437
  if (appId) {
10939
- try {
10940
- const info = await context.storageApi?.invoke(appId, "getStorageInfo", {});
10941
- if (info && typeof info === "object") {
10942
- const keys = info.keys;
10943
- if (Array.isArray(keys)) {
10944
- overview.storageKeys = keys.filter((key) => typeof key === "string");
10945
- overview.storageCount = keys.length;
10946
- }
10947
- }
10948
- } catch {
10949
- }
10950
- try {
10951
- const snapshot = context.appData?.snapshot?.(appId);
10952
- if (snapshot && typeof snapshot === "object") {
10953
- const entries = snapshot.entries;
10954
- if (entries && typeof entries === "object") {
10955
- overview.appDataKeys = Object.keys(entries);
10956
- }
10957
- }
10958
- } catch {
10959
- }
11438
+ const storage2 = await resolveNativeStorageOverview(context, appId);
11439
+ overview.storageKeys = storage2.storageKeys;
11440
+ overview.storageCount = storage2.storageCount;
11441
+ overview.appDataKeys = resolveNativeAppDataKeys(context, appId);
10960
11442
  }
10961
11443
  return overview;
10962
11444
  });
@@ -11084,7 +11566,7 @@ function launch(config = {}) {
11084
11566
  }
11085
11567
 
11086
11568
  // src/main/index.ts
11087
- launch().catch((err) => {
11088
- console.error("[devtools] fatal: launch() failed during boot", err);
11569
+ launch().catch((err2) => {
11570
+ console.error("[devtools] fatal: launch() failed during boot", err2);
11089
11571
  app16.exit(1);
11090
11572
  });