@dimina-kit/devtools 0.3.2-dev.20260524104239 → 0.3.2-dev.20260601115343

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 (174) hide show
  1. package/README.md +20 -7
  2. package/dist/main/api.d.ts +1 -1
  3. package/dist/main/app/app.js +30 -2
  4. package/dist/main/app/bootstrap.d.ts +9 -0
  5. package/dist/main/app/bootstrap.js +28 -1
  6. package/dist/main/index.bundle.js +1468 -496
  7. package/dist/main/ipc/project-fs.d.ts +57 -0
  8. package/dist/main/ipc/project-fs.js +448 -0
  9. package/dist/main/ipc/simulator-module.js +2 -0
  10. package/dist/main/ipc/views.d.ts +19 -0
  11. package/dist/main/ipc/views.js +27 -0
  12. package/dist/main/services/layout/index.d.ts +1 -1
  13. package/dist/main/services/layout/index.js +3 -3
  14. package/dist/main/services/simulator-temp-files/disk.d.ts +55 -0
  15. package/dist/main/services/simulator-temp-files/disk.js +215 -0
  16. package/dist/main/services/simulator-temp-files/fs-channels.d.ts +69 -0
  17. package/dist/main/services/simulator-temp-files/fs-channels.js +108 -0
  18. package/dist/main/services/simulator-temp-files/index.d.ts +33 -0
  19. package/dist/main/services/simulator-temp-files/index.js +160 -0
  20. package/dist/main/services/simulator-temp-files/request-handler.d.ts +30 -0
  21. package/dist/main/services/simulator-temp-files/request-handler.js +178 -0
  22. package/dist/main/services/simulator-temp-files/resolver.d.ts +22 -0
  23. package/dist/main/services/simulator-temp-files/resolver.js +22 -0
  24. package/dist/main/services/simulator-temp-files/store.d.ts +14 -0
  25. package/dist/main/services/simulator-temp-files/store.js +24 -0
  26. package/dist/main/services/toolbar/toolbar-store.d.ts +3 -3
  27. package/dist/main/services/toolbar/toolbar-store.js +18 -1
  28. package/dist/main/services/update/update-manager.d.ts +10 -0
  29. package/dist/main/services/update/update-manager.js +16 -3
  30. package/dist/main/services/views/view-manager.d.ts +17 -4
  31. package/dist/main/services/views/view-manager.js +71 -14
  32. package/dist/main/services/workbench-context.d.ts +6 -1
  33. package/dist/main/services/workspace/workspace-service.d.ts +8 -0
  34. package/dist/main/services/workspace/workspace-service.js +22 -4
  35. package/dist/main/utils/ipc-registry.d.ts +10 -1
  36. package/dist/main/utils/ipc-registry.js +43 -0
  37. package/dist/preload/runtime/temp-files.d.ts +8 -0
  38. package/dist/preload/runtime/temp-files.js +34 -0
  39. package/dist/preload/shared/expose.js +2 -2
  40. package/dist/preload/windows/main.cjs +4 -0
  41. package/dist/preload/windows/main.cjs.map +2 -2
  42. package/dist/preload/windows/simulator.js +40 -8
  43. package/dist/renderer/assets/abap-DiwvWnMr.js +2 -0
  44. package/dist/renderer/assets/apex-CmtZjKlf.js +2 -0
  45. package/dist/renderer/assets/azcli-DL2My_i-.js +2 -0
  46. package/dist/renderer/assets/bat-B-nC98wG.js +2 -0
  47. package/dist/renderer/assets/bicep-Ju5MwOgh.js +3 -0
  48. package/dist/renderer/assets/cameligo-8Eu1TyBr.js +2 -0
  49. package/dist/renderer/assets/chunk-DPI0Y_ll.js +1 -0
  50. package/dist/renderer/assets/clojure-u-RpMkH3.js +2 -0
  51. package/dist/renderer/assets/codicon-ngg6Pgfi.ttf +0 -0
  52. package/dist/renderer/assets/coffee-CdA7bbTe.js +2 -0
  53. package/dist/renderer/assets/constants-CLAQX6VZ.js +2 -0
  54. package/dist/renderer/assets/cpp-CzNFP8ks.js +2 -0
  55. package/dist/renderer/assets/csharp-j1LThmcE.js +2 -0
  56. package/dist/renderer/assets/csp-CLRC61y6.js +2 -0
  57. package/dist/renderer/assets/css-r6rC_7P2.js +4 -0
  58. package/dist/renderer/assets/css.worker-DyDu5ynT.js +90 -0
  59. package/dist/renderer/assets/cssMode-BCBgxrnA.js +2 -0
  60. package/dist/renderer/assets/cypher-CW08XVUh.js +2 -0
  61. package/dist/renderer/assets/dart-Cs9aL5T_.js +2 -0
  62. package/dist/renderer/assets/dockerfile-BWM0M184.js +2 -0
  63. package/dist/renderer/assets/ecl-MJJuer5P.js +2 -0
  64. package/dist/renderer/assets/editor-CWI9TnMM.css +1 -0
  65. package/dist/renderer/assets/editor.api2-BTaYmRq7.js +873 -0
  66. package/dist/renderer/assets/editor.worker-Dpt_xPrs.js +27 -0
  67. package/dist/renderer/assets/elixir-D2AIuXqn.js +2 -0
  68. package/dist/renderer/assets/flow9-B2H24giC.js +2 -0
  69. package/dist/renderer/assets/freemarker2-DGDGzimW.js +4 -0
  70. package/dist/renderer/assets/fsharp-CMk2OIJN.js +2 -0
  71. package/dist/renderer/assets/go-BrMkuJg0.js +2 -0
  72. package/dist/renderer/assets/graphql-PSR1UKGv.js +2 -0
  73. package/dist/renderer/assets/handlebars-bOHhw3EG.js +2 -0
  74. package/dist/renderer/assets/hcl-DAQrbDOW.js +2 -0
  75. package/dist/renderer/assets/html-BMyuX0_c.js +2 -0
  76. package/dist/renderer/assets/html.worker-IWNXhnIC.js +503 -0
  77. package/dist/renderer/assets/htmlMode-DR4kXJ7l.js +2 -0
  78. package/dist/renderer/assets/index-BDMxXUfn.js +50 -0
  79. package/dist/renderer/assets/ini-0TG5BxW0.js +2 -0
  80. package/dist/renderer/assets/input-CCYS8Ave.js +2 -0
  81. package/dist/renderer/assets/{ipc-transport-BzPzxPwf.js → ipc-transport-Bn4KXvhu.js} +5 -5
  82. package/dist/renderer/assets/ipc-transport-Cu7tubyg.css +1 -0
  83. package/dist/renderer/assets/java-rgorz17v.js +2 -0
  84. package/dist/renderer/assets/javascript-Bu-tl1P7.js +2 -0
  85. package/dist/renderer/assets/json.worker-c4h5s80L.js +59 -0
  86. package/dist/renderer/assets/jsonMode-MQV_ffLR.js +8 -0
  87. package/dist/renderer/assets/julia-C8VMdHm8.js +2 -0
  88. package/dist/renderer/assets/kotlin-AhhC4Qhm.js +2 -0
  89. package/dist/renderer/assets/less-Cgca25AP.js +3 -0
  90. package/dist/renderer/assets/lexon-D0GHdBaw.js +2 -0
  91. package/dist/renderer/assets/liquid-ClkfOvhB.js +2 -0
  92. package/dist/renderer/assets/lspLanguageFeatures-Bke_4Ie9.js +5 -0
  93. package/dist/renderer/assets/lua-DmRsNG-P.js +2 -0
  94. package/dist/renderer/assets/m3-BgL5dNKT.js +2 -0
  95. package/dist/renderer/assets/markdown-BuJfycGS.js +2 -0
  96. package/dist/renderer/assets/mdx-RZdUskPu.js +2 -0
  97. package/dist/renderer/assets/mips-C9m_93PR.js +2 -0
  98. package/dist/renderer/assets/monaco.contribution-B7rUMaEv.js +3 -0
  99. package/dist/renderer/assets/msdax-CpFHC9OI.js +2 -0
  100. package/dist/renderer/assets/mysql-qFvltsqN.js +2 -0
  101. package/dist/renderer/assets/objective-c-Bnmr858J.js +2 -0
  102. package/dist/renderer/assets/pascal-WP0_D5AO.js +2 -0
  103. package/dist/renderer/assets/pascaligo-Blom4Rij.js +2 -0
  104. package/dist/renderer/assets/perl-B-vk8g64.js +2 -0
  105. package/dist/renderer/assets/pgsql-Cgvz6v67.js +2 -0
  106. package/dist/renderer/assets/php-8a3Lrw9m.js +2 -0
  107. package/dist/renderer/assets/pla-DuFqEZ8V.js +2 -0
  108. package/dist/renderer/assets/popover-CH4rwpdt.js +2 -0
  109. package/dist/renderer/assets/postiats-DkLtSgkp.js +2 -0
  110. package/dist/renderer/assets/powerquery-BJ1aNepW.js +2 -0
  111. package/dist/renderer/assets/powershell-rE98k687.js +2 -0
  112. package/dist/renderer/assets/protobuf-CUheFacr.js +3 -0
  113. package/dist/renderer/assets/pug-LDcAMD8w.js +2 -0
  114. package/dist/renderer/assets/python-Cobrm_-8.js +2 -0
  115. package/dist/renderer/assets/qsharp-DUKSQoR1.js +2 -0
  116. package/dist/renderer/assets/r-D-QApv87.js +2 -0
  117. package/dist/renderer/assets/razor-BEUIQFNJ.js +2 -0
  118. package/dist/renderer/assets/redis-SXdDyWR9.js +2 -0
  119. package/dist/renderer/assets/redshift-Y6lsCryn.js +2 -0
  120. package/dist/renderer/assets/restructuredtext-edObr9a8.js +2 -0
  121. package/dist/renderer/assets/ruby-CNnUfF-8.js +2 -0
  122. package/dist/renderer/assets/rust-IHUZWzBr.js +2 -0
  123. package/dist/renderer/assets/sb-DrUvY44N.js +2 -0
  124. package/dist/renderer/assets/scala-B4hbXGLM.js +2 -0
  125. package/dist/renderer/assets/scheme-BGrd12j3.js +2 -0
  126. package/dist/renderer/assets/scss-x5G1ES4U.js +4 -0
  127. package/dist/renderer/assets/select-Ct4-sijm.js +2 -0
  128. package/dist/renderer/assets/settings-CMTB-t0x.js +2 -0
  129. package/dist/renderer/assets/{settings-api-BXCw9vOZ.js → settings-api-Csdngf9Z.js} +2 -2
  130. package/dist/renderer/assets/shell-DOehe2Y8.js +2 -0
  131. package/dist/renderer/assets/solidity-BeRvcwWV.js +2 -0
  132. package/dist/renderer/assets/sophia-DZbkUNjy.js +2 -0
  133. package/dist/renderer/assets/sparql-B7_oi5-h.js +2 -0
  134. package/dist/renderer/assets/sql-CTlsFWVE.js +2 -0
  135. package/dist/renderer/assets/st-DJVEJdPE.js +2 -0
  136. package/dist/renderer/assets/swift-CwhT3fYa.js +2 -0
  137. package/dist/renderer/assets/systemverilog-BQN63pkN.js +2 -0
  138. package/dist/renderer/assets/tcl-DqwfpskA.js +2 -0
  139. package/dist/renderer/assets/ts.worker-BwM5Ha3L.js +67720 -0
  140. package/dist/renderer/assets/tsMode-BHa7dl9v.js +12 -0
  141. package/dist/renderer/assets/twig-BiyenUgc.js +2 -0
  142. package/dist/renderer/assets/typescript-15aT0Yj8.js +2 -0
  143. package/dist/renderer/assets/typespec-CWOJribt.js +2 -0
  144. package/dist/renderer/assets/vb-Cq5F87m3.js +2 -0
  145. package/dist/renderer/assets/wgsl-BAvW2lVr.js +299 -0
  146. package/dist/renderer/assets/workbenchSettings-CODFCzOg.js +8 -0
  147. package/dist/renderer/assets/workers-CRBS0W-v.js +2 -0
  148. package/dist/renderer/assets/xml-bR2Zh-HV.js +2 -0
  149. package/dist/renderer/assets/yaml-Bdk54Qrb.js +2 -0
  150. package/dist/renderer/entries/main/index.html +29 -6
  151. package/dist/renderer/entries/popover/index.html +7 -6
  152. package/dist/renderer/entries/settings/index.html +6 -5
  153. package/dist/renderer/entries/workbench-settings/index.html +6 -5
  154. package/dist/shared/constants.d.ts +0 -4
  155. package/dist/shared/constants.js +0 -4
  156. package/dist/shared/ipc-channels.d.ts +24 -0
  157. package/dist/shared/ipc-channels.js +37 -0
  158. package/dist/shared/ipc-schemas.d.ts +6 -0
  159. package/dist/shared/ipc-schemas.js +15 -0
  160. package/dist/shared/types.d.ts +37 -0
  161. package/dist/shared/vpath.d.ts +42 -0
  162. package/dist/shared/vpath.js +105 -0
  163. package/dist/simulator/assets/simulator-B4e3c6_O.js +9 -0
  164. package/dist/simulator/simulator.html +1 -1
  165. package/package.json +5 -4
  166. package/dist/renderer/assets/constants-DI382mTP.js +0 -2
  167. package/dist/renderer/assets/index-4lkyF644.js +0 -46
  168. package/dist/renderer/assets/input-D4byFOLS.js +0 -2
  169. package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +0 -1
  170. package/dist/renderer/assets/popover-cxKrg_os.js +0 -2
  171. package/dist/renderer/assets/select-BgSkkdHW.js +0 -2
  172. package/dist/renderer/assets/settings-oD2K1jdT.js +0 -2
  173. package/dist/renderer/assets/workbenchSettings-CTNBpTft.js +0 -8
  174. package/dist/simulator/assets/simulator-DDRBJ6Wa.js +0 -9
@@ -1,5 +1,5 @@
1
1
  // src/main/app/bootstrap.ts
2
- import { app as app2 } from "electron";
2
+ import { app as app2, protocol } from "electron";
3
3
 
4
4
  // src/shared/constants.ts
5
5
  var DEFAULT_CDP_PORT = 9222;
@@ -63,6 +63,24 @@ function applyTheme(theme) {
63
63
  }
64
64
 
65
65
  // src/main/app/bootstrap.ts
66
+ var difileSchemeRegistered = false;
67
+ function registerDifileScheme() {
68
+ if (difileSchemeRegistered) return;
69
+ difileSchemeRegistered = true;
70
+ protocol.registerSchemesAsPrivileged([
71
+ {
72
+ scheme: "difile",
73
+ privileges: {
74
+ standard: true,
75
+ secure: true,
76
+ supportFetchAPI: true,
77
+ stream: true,
78
+ bypassCSP: true,
79
+ corsEnabled: true
80
+ }
81
+ }
82
+ ]);
83
+ }
66
84
  function setupCdpPort() {
67
85
  if (app2.commandLine.getSwitchValue("remote-debugging-port")) return;
68
86
  const envPort = process.env.DIMINA_DEBUG_PORT;
@@ -78,101 +96,675 @@ function setupCdpPort() {
78
96
  }
79
97
  }
80
98
 
99
+ // src/main/ipc/project-fs.ts
100
+ import fs2 from "node:fs/promises";
101
+ import nodeFs from "node:fs";
102
+ import path2 from "node:path";
103
+ import { z } from "zod";
104
+
105
+ // src/main/utils/ipc-registry.ts
106
+ import { ipcMain } from "electron";
107
+
108
+ // src/main/utils/disposable.ts
109
+ function toDisposable(fn) {
110
+ let done = false;
111
+ return {
112
+ dispose() {
113
+ if (done) return;
114
+ done = true;
115
+ return fn();
116
+ }
117
+ };
118
+ }
119
+ var DisposableRegistry = class {
120
+ entries = [];
121
+ _disposed = false;
122
+ /**
123
+ * Number of live entries. A wrapper's `dispose` splices its entry out and
124
+ * `disposeAll` clears the array, so `entries.length` is the live count.
125
+ */
126
+ get size() {
127
+ return this.entries.length;
128
+ }
129
+ add(d) {
130
+ if (this._disposed) {
131
+ throw new Error("cannot add to disposed registry");
132
+ }
133
+ const fn = typeof d === "function" ? d : () => d.dispose();
134
+ const entry = { fn, released: false };
135
+ this.entries.push(entry);
136
+ return {
137
+ dispose: () => {
138
+ if (entry.released) return;
139
+ entry.released = true;
140
+ const i = this.entries.indexOf(entry);
141
+ if (i >= 0) this.entries.splice(i, 1);
142
+ return fn();
143
+ }
144
+ };
145
+ }
146
+ async disposeAll() {
147
+ if (this._disposed) return;
148
+ this._disposed = true;
149
+ const items = this.entries.slice().reverse();
150
+ this.entries = [];
151
+ const errors = [];
152
+ for (const entry of items) {
153
+ if (entry.released) continue;
154
+ entry.released = true;
155
+ try {
156
+ await entry.fn();
157
+ } catch (e) {
158
+ errors.push(e);
159
+ }
160
+ }
161
+ if (errors.length > 0) {
162
+ throw new AggregateError(errors, "DisposableRegistry encountered errors during disposeAll");
163
+ }
164
+ }
165
+ dispose() {
166
+ return this.disposeAll();
167
+ }
168
+ };
169
+
170
+ // src/main/utils/ipc-schema.ts
171
+ var IpcValidationError = class extends Error {
172
+ channel;
173
+ paths;
174
+ constructor(channel, issues) {
175
+ const paths = issues.map((i) => i.path.map((p) => String(p)).join(".") || "<root>");
176
+ super(`Invalid IPC arguments for ${channel}`);
177
+ this.name = "IpcValidationError";
178
+ this.channel = channel;
179
+ this.paths = paths;
180
+ }
181
+ };
182
+ function validate(channel, schema, args) {
183
+ const r = schema.safeParse(args);
184
+ if (!r.success) {
185
+ console.warn("[ipc] schema reject", channel, r.error.issues);
186
+ throw new IpcValidationError(channel, r.error.issues);
187
+ }
188
+ return r.data;
189
+ }
190
+
191
+ // src/main/utils/logger.ts
192
+ var LEVEL_PRIORITY = {
193
+ debug: 0,
194
+ info: 1,
195
+ warn: 2,
196
+ error: 3
197
+ };
198
+ var LEVEL_PREFIX = {
199
+ debug: "[DEBUG]",
200
+ info: "[INFO]",
201
+ warn: "[WARN]",
202
+ error: "[ERROR]"
203
+ };
204
+ var currentLevel = "debug";
205
+ function shouldLog(level) {
206
+ return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];
207
+ }
208
+ function formatMessage(level, tag, message) {
209
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
210
+ return `${ts} ${LEVEL_PREFIX[level]} [${tag}] ${message}`;
211
+ }
212
+ function createLogger(tag) {
213
+ return {
214
+ debug(message, ...args) {
215
+ if (shouldLog("debug")) console.debug(formatMessage("debug", tag, message), ...args);
216
+ },
217
+ info(message, ...args) {
218
+ if (shouldLog("info")) console.info(formatMessage("info", tag, message), ...args);
219
+ },
220
+ warn(message, ...args) {
221
+ if (shouldLog("warn")) console.warn(formatMessage("warn", tag, message), ...args);
222
+ },
223
+ error(message, ...args) {
224
+ if (shouldLog("error")) console.error(formatMessage("error", tag, message), ...args);
225
+ }
226
+ };
227
+ }
228
+
229
+ // src/main/utils/ipc-registry.ts
230
+ var log = createLogger("ipc");
231
+ function reportListenerError(channel, err) {
232
+ if (err instanceof IpcValidationError) {
233
+ log.warn(`schema reject on '${channel}' at [${err.paths.join(", ")}]`);
234
+ return;
235
+ }
236
+ if (err instanceof Error) {
237
+ log.error(`listener threw on '${channel}': ${err.message}`, err.stack);
238
+ return;
239
+ }
240
+ log.error(`listener threw on '${channel}'`, err);
241
+ }
242
+ function isThenable(v) {
243
+ return v != null && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
244
+ }
245
+ function summarizeSender(sender) {
246
+ if (sender.isDestroyed()) return "<destroyed>";
247
+ const url = sender.getURL();
248
+ return `id=${sender.id} ${url.slice(0, 120)}`;
249
+ }
250
+ var IpcRegistry = class {
251
+ constructor(policy) {
252
+ this.policy = policy;
253
+ }
254
+ policy;
255
+ registry = new DisposableRegistry();
256
+ handle(channel, fn) {
257
+ const policy = this.policy;
258
+ const guarded = policy ? async (event, ...args) => {
259
+ const sender = event.sender;
260
+ if (!policy(sender)) {
261
+ console.warn(
262
+ `[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
263
+ );
264
+ throw new Error(`IPC sender rejected for channel ${channel}`);
265
+ }
266
+ return fn(event, ...args);
267
+ } : fn;
268
+ ipcMain.handle(channel, guarded);
269
+ this.registry.add(() => ipcMain.removeHandler(channel));
270
+ return this;
271
+ }
272
+ /**
273
+ * Register a SYNCHRONOUS `ipcRenderer.sendSync` handler. Unlike {@link on}
274
+ * (fire-and-forget), the renderer is blocked until we set `event.returnValue`,
275
+ * so `fn` MUST be synchronous and return the value to hand back. The sender
276
+ * policy gates it like {@link handle}; a rejected sender or a thrown error
277
+ * yields a structured `{ ok: false, code, message }` returnValue instead of
278
+ * the silent drop `on` uses, so the blocked renderer always gets an answer.
279
+ */
280
+ handleSync(channel, fn) {
281
+ const policy = this.policy;
282
+ const listener = (event, ...args) => {
283
+ try {
284
+ if (policy && !policy(event.sender)) {
285
+ console.warn(
286
+ `[ipc] sender rejected for channel '${channel}' (${summarizeSender(event.sender)})`
287
+ );
288
+ event.returnValue = {
289
+ ok: false,
290
+ code: "EREJECTED",
291
+ message: `IPC sender rejected for channel ${channel}`
292
+ };
293
+ return;
294
+ }
295
+ event.returnValue = fn(event, ...args);
296
+ } catch (err) {
297
+ reportListenerError(channel, err);
298
+ const code = err?.code;
299
+ event.returnValue = {
300
+ ok: false,
301
+ code: typeof code === "string" ? code : "EUNKNOWN",
302
+ message: err instanceof Error ? err.message : String(err)
303
+ };
304
+ }
305
+ };
306
+ ipcMain.on(channel, listener);
307
+ this.registry.add(() => {
308
+ ipcMain.removeListener(channel, listener);
309
+ });
310
+ return this;
311
+ }
312
+ on(channel, fn) {
313
+ const policy = this.policy;
314
+ const raw = fn;
315
+ const safeInvoke = (event, args) => {
316
+ try {
317
+ const ret = raw(event, ...args);
318
+ if (isThenable(ret)) {
319
+ Promise.resolve(ret).catch((err) => reportListenerError(channel, err));
320
+ }
321
+ } catch (err) {
322
+ reportListenerError(channel, err);
323
+ }
324
+ };
325
+ const guarded = policy ? (event, ...args) => {
326
+ const sender = event.sender;
327
+ if (!policy(sender)) {
328
+ console.warn(
329
+ `[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
330
+ );
331
+ return;
332
+ }
333
+ safeInvoke(event, args);
334
+ } : (event, ...args) => safeInvoke(event, args);
335
+ ipcMain.on(channel, guarded);
336
+ this.registry.add(() => {
337
+ ipcMain.removeListener(channel, guarded);
338
+ });
339
+ return this;
340
+ }
341
+ dispose() {
342
+ return this.registry.dispose();
343
+ }
344
+ };
345
+
346
+ // src/shared/ipc-channels.ts
347
+ var SimulatorChannel = {
348
+ Attach: "simulator:attach",
349
+ Detach: "simulator:detach",
350
+ Resize: "simulator:resize",
351
+ SetVisible: "simulator:setVisible",
352
+ Console: "simulator:console"
353
+ };
354
+ var SimulatorCustomApiChannel = {
355
+ List: "simulator:custom-apis:list",
356
+ Invoke: "simulator:custom-apis:invoke"
357
+ };
358
+ var SimulatorStorageChannel = {
359
+ GetSnapshot: "simulator:storage:snapshot",
360
+ GetActivePrefix: "simulator:storage:activePrefix",
361
+ Set: "simulator:storage:set",
362
+ Remove: "simulator:storage:remove",
363
+ Clear: "simulator:storage:clear",
364
+ ClearAll: "simulator:storage:clearAll",
365
+ Event: "simulator:storage:event"
366
+ };
367
+ var SimulatorElementChannel = {
368
+ Inspect: "simulator:element:inspect",
369
+ Clear: "simulator:element:clear"
370
+ };
371
+ var WorkbenchSettingsChannel = {
372
+ Get: "workbenchSettings:get",
373
+ Save: "workbenchSettings:save",
374
+ SetTheme: "workbenchSettings:setTheme",
375
+ GetCdpStatus: "workbenchSettings:getCdpStatus",
376
+ GetMcpStatus: "workbenchSettings:getMcpStatus",
377
+ SetVisible: "workbenchSettings:setVisible",
378
+ Init: "workbenchSettings:init"
379
+ };
380
+ var ProjectChannel = {
381
+ Open: "project:open",
382
+ Close: "project:close",
383
+ GetPages: "project:getPages",
384
+ GetCompileConfig: "project:getCompileConfig",
385
+ SaveCompileConfig: "project:saveCompileConfig",
386
+ Status: "project:status",
387
+ CaptureThumbnail: "project:captureThumbnail",
388
+ GetThumbnail: "project:getThumbnail"
389
+ };
390
+ var ProjectFsChannel = {
391
+ GetRoot: "project:fs:getRoot",
392
+ ReadFile: "project:fs:readFile",
393
+ WriteFile: "project:fs:writeFile",
394
+ /**
395
+ * Synchronous (blocking) write, used ONLY by the editor's `beforeunload`
396
+ * flush: a hard window/app close tears the renderer down before an async
397
+ * `WriteFile` IPC can round-trip, so the last in-debounce-window edit would
398
+ * be lost. `sendSync` blocks page teardown until the bytes hit disk. Runs the
399
+ * SAME sandbox as `WriteFile` (see project-fs `writeFileSync`).
400
+ */
401
+ WriteFileSync: "project:fs:writeFileSync",
402
+ ListFiles: "project:fs:listFiles"
403
+ };
404
+ var ProjectsChannel = {
405
+ List: "projects:list",
406
+ Add: "projects:add",
407
+ Remove: "projects:remove",
408
+ /** Phase 3: merged + sanitized template catalog for the create-project dialog. */
409
+ ListTemplates: "projects:listTemplates",
410
+ /** Phase 3: host-supplied create-project dialog hook (returns input or null). */
411
+ OpenCreateDialog: "projects:openCreateDialog",
412
+ /** Phase 3: server-side scaffold + register (delegates to create-project-service). */
413
+ Create: "projects:create",
414
+ /** Default values used to pre-fill the create-project dialog (baseDir). */
415
+ GetCreateDefaults: "projects:getCreateDefaults"
416
+ };
417
+ var DialogChannel = {
418
+ OpenDirectory: "dialog:openDirectory"
419
+ };
420
+ var PanelChannel = {
421
+ List: "panel:list",
422
+ Eval: "panel:eval",
423
+ Select: "panel:select",
424
+ SelectSimulator: "panel:selectSimulator"
425
+ };
426
+ var ViewChannel = {
427
+ /** Update the simulator DevTools view's bounds (or hide if w/h = 0). */
428
+ SimulatorDevtoolsBounds: "view:simulator:devtools-bounds"
429
+ };
430
+ var PopoverChannel = {
431
+ Show: "popover:show",
432
+ Hide: "popover:hide",
433
+ Relaunch: "popover:relaunch",
434
+ Closed: "popover:closed",
435
+ Init: "popover:init"
436
+ };
437
+ var ToolbarChannel = {
438
+ GetActions: "toolbar:getActions",
439
+ ActionsChanged: "toolbar:actionsChanged",
440
+ /** Invoke a toolbar action by id: `invoke(Invoke, actionId)`. */
441
+ Invoke: "toolbar:invoke"
442
+ };
443
+ var WindowChannel = {
444
+ NavigateBack: "window:navigateBack"
445
+ };
446
+ var AppChannel = {
447
+ GetPreloadPath: "app:getPreloadPath",
448
+ GetBranding: "app:getBranding",
449
+ GetHeaderHeight: "app:getHeaderHeight"
450
+ };
451
+ var AutomationChannel = {
452
+ GetPort: "automation:port"
453
+ };
454
+ var SettingsChannel = {
455
+ SetVisible: "settings:setVisible",
456
+ ConfigChanged: "settings:configChanged",
457
+ ProjectSettingsChanged: "settings:projectSettingsChanged",
458
+ Init: "settings:init",
459
+ Closed: "settings:closed",
460
+ Changed: "settings:changed"
461
+ };
462
+ var UpdateChannel = {
463
+ Check: "updates:check",
464
+ Download: "updates:download",
465
+ Install: "updates:install",
466
+ DownloadProgress: "updates:downloadProgress",
467
+ Available: "updates:available"
468
+ };
469
+
470
+ // src/main/ipc/project-fs.ts
471
+ var LIST_FILE_LIMIT = 5e3;
472
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
473
+ "node_modules",
474
+ ".git",
475
+ ".svn",
476
+ ".hg",
477
+ "dist",
478
+ "build",
479
+ ".next",
480
+ ".cache",
481
+ ".turbo",
482
+ ".parcel-cache",
483
+ ".nuxt",
484
+ ".output",
485
+ ".vite"
486
+ ]);
487
+ var PathArg = z.string().min(1);
488
+ var ReadFileSchema = z.tuple([PathArg]);
489
+ var WriteFileSchema = z.tuple([PathArg, z.string()]);
490
+ var ListFilesSchema = z.tuple([PathArg]);
491
+ function makeError(code, message) {
492
+ const err = new Error(message);
493
+ err.code = code;
494
+ return err;
495
+ }
496
+ function isWithin(parent, child) {
497
+ if (child === parent) return true;
498
+ const rel = path2.relative(parent, child);
499
+ return rel.length > 0 && !rel.startsWith(".." + path2.sep) && rel !== ".." && !path2.isAbsolute(rel);
500
+ }
501
+ function enforceWithinProjectRoot(abs, root) {
502
+ if (root === "") {
503
+ throw makeError("ENOACTIVE", "No active project \u2014 open a project before using the editor file system");
504
+ }
505
+ if (typeof abs !== "string" || abs.length === 0) {
506
+ throw makeError("EINVAL", "path must be a non-empty string");
507
+ }
508
+ if (abs.includes("\0") || root.includes("\0")) {
509
+ throw makeError("EINVAL", "path must not contain NUL bytes");
510
+ }
511
+ const resolvedRoot = path2.resolve(root);
512
+ const resolved = path2.resolve(abs);
513
+ if (!isWithin(resolvedRoot, resolved)) {
514
+ throw makeError("EACCES", `Path escapes project root: ${resolved}`);
515
+ }
516
+ return resolved;
517
+ }
518
+ function pickWriteRoot(absPath, roots) {
519
+ const resolved = path2.resolve(absPath);
520
+ if (roots.current !== "" && isWithin(path2.resolve(roots.current), resolved)) {
521
+ return roots.current;
522
+ }
523
+ if (roots.lastClosed !== "" && isWithin(path2.resolve(roots.lastClosed), resolved)) {
524
+ return roots.lastClosed;
525
+ }
526
+ if (roots.current === "" && roots.lastClosed === "") {
527
+ throw makeError("ENOACTIVE", "No active project \u2014 open a project before using the editor file system");
528
+ }
529
+ throw makeError("EACCES", `Path escapes project root: ${resolved}`);
530
+ }
531
+ async function resolveWithinProjectRoot(abs, root) {
532
+ const lexical = enforceWithinProjectRoot(abs, root);
533
+ const realRoot = await fs2.realpath(path2.resolve(root));
534
+ let realTarget;
535
+ try {
536
+ realTarget = await fs2.realpath(lexical);
537
+ } catch (err) {
538
+ if (err.code !== "ENOENT") throw err;
539
+ const parent = path2.dirname(lexical);
540
+ const realParent = await fs2.realpath(parent);
541
+ if (!isWithin(realRoot, realParent)) {
542
+ throw makeError("EACCES", `Path escapes project root: ${lexical}`);
543
+ }
544
+ return path2.join(realParent, path2.basename(lexical));
545
+ }
546
+ if (!isWithin(realRoot, realTarget)) {
547
+ throw makeError("EACCES", `Path escapes project root: ${realTarget}`);
548
+ }
549
+ return realTarget;
550
+ }
551
+ async function assertOpenedWithinRoot(safe, root) {
552
+ const realRoot = await fs2.realpath(path2.resolve(root));
553
+ const real = await fs2.realpath(safe);
554
+ if (!isWithin(realRoot, real)) {
555
+ throw makeError("EACCES", `Path escapes project root (mid-path race): ${real}`);
556
+ }
557
+ }
558
+ async function readFile(root, p) {
559
+ const safe = await resolveWithinProjectRoot(p, root);
560
+ const fh = await fs2.open(safe, fs2.constants.O_RDONLY | fs2.constants.O_NOFOLLOW);
561
+ try {
562
+ await assertOpenedWithinRoot(safe, root);
563
+ return await fh.readFile("utf8");
564
+ } finally {
565
+ await fh.close();
566
+ }
567
+ }
568
+ async function assertWritableAncestor(lexical, root) {
569
+ const realRoot = await fs2.realpath(path2.resolve(root));
570
+ let ancestor = path2.dirname(lexical);
571
+ for (; ; ) {
572
+ let realAncestor;
573
+ try {
574
+ realAncestor = await fs2.realpath(ancestor);
575
+ } catch (err) {
576
+ if (err.code !== "ENOENT") throw err;
577
+ const next = path2.dirname(ancestor);
578
+ if (next === ancestor) {
579
+ throw makeError("EACCES", `Path escapes project root: ${lexical}`);
580
+ }
581
+ ancestor = next;
582
+ continue;
583
+ }
584
+ if (!isWithin(realRoot, realAncestor)) {
585
+ throw makeError("EACCES", `Path escapes project root: ${lexical}`);
586
+ }
587
+ return;
588
+ }
589
+ }
590
+ async function writeFile(root, p, content) {
591
+ const lexical = enforceWithinProjectRoot(p, root);
592
+ await assertWritableAncestor(lexical, root);
593
+ await fs2.mkdir(path2.dirname(lexical), { recursive: true });
594
+ const safe = await resolveWithinProjectRoot(p, root);
595
+ const fh = await fs2.open(
596
+ safe,
597
+ fs2.constants.O_WRONLY | fs2.constants.O_CREAT | fs2.constants.O_NOFOLLOW,
598
+ 438
599
+ );
600
+ try {
601
+ await assertOpenedWithinRoot(safe, root);
602
+ const buf = Buffer.from(content, "utf8");
603
+ let off = 0;
604
+ while (off < buf.length) {
605
+ const { bytesWritten } = await fh.write(buf, off, buf.length - off, off);
606
+ if (bytesWritten <= 0) break;
607
+ off += bytesWritten;
608
+ }
609
+ await fh.truncate(buf.length);
610
+ } finally {
611
+ await fh.close();
612
+ }
613
+ }
614
+ function resolveWithinProjectRootSync(abs, root) {
615
+ const lexical = enforceWithinProjectRoot(abs, root);
616
+ const realRoot = nodeFs.realpathSync(path2.resolve(root));
617
+ let realTarget;
618
+ try {
619
+ realTarget = nodeFs.realpathSync(lexical);
620
+ } catch (err) {
621
+ if (err.code !== "ENOENT") throw err;
622
+ const parent = path2.dirname(lexical);
623
+ const realParent = nodeFs.realpathSync(parent);
624
+ if (!isWithin(realRoot, realParent)) {
625
+ throw makeError("EACCES", `Path escapes project root: ${lexical}`);
626
+ }
627
+ return path2.join(realParent, path2.basename(lexical));
628
+ }
629
+ if (!isWithin(realRoot, realTarget)) {
630
+ throw makeError("EACCES", `Path escapes project root: ${realTarget}`);
631
+ }
632
+ return realTarget;
633
+ }
634
+ function assertWritableAncestorSync(lexical, root) {
635
+ const realRoot = nodeFs.realpathSync(path2.resolve(root));
636
+ let ancestor = path2.dirname(lexical);
637
+ for (; ; ) {
638
+ let realAncestor;
639
+ try {
640
+ realAncestor = nodeFs.realpathSync(ancestor);
641
+ } catch (err) {
642
+ if (err.code !== "ENOENT") throw err;
643
+ const next = path2.dirname(ancestor);
644
+ if (next === ancestor) {
645
+ throw makeError("EACCES", `Path escapes project root: ${lexical}`);
646
+ }
647
+ ancestor = next;
648
+ continue;
649
+ }
650
+ if (!isWithin(realRoot, realAncestor)) {
651
+ throw makeError("EACCES", `Path escapes project root: ${lexical}`);
652
+ }
653
+ return;
654
+ }
655
+ }
656
+ function assertOpenedWithinRootSync(safe, root) {
657
+ const realRoot = nodeFs.realpathSync(path2.resolve(root));
658
+ const real = nodeFs.realpathSync(safe);
659
+ if (!isWithin(realRoot, real)) {
660
+ throw makeError("EACCES", `Path escapes project root (mid-path race): ${real}`);
661
+ }
662
+ }
663
+ function writeFileSync(root, p, content) {
664
+ const lexical = enforceWithinProjectRoot(p, root);
665
+ assertWritableAncestorSync(lexical, root);
666
+ nodeFs.mkdirSync(path2.dirname(lexical), { recursive: true });
667
+ const safe = resolveWithinProjectRootSync(p, root);
668
+ const fd = nodeFs.openSync(
669
+ safe,
670
+ nodeFs.constants.O_WRONLY | nodeFs.constants.O_CREAT | nodeFs.constants.O_NOFOLLOW,
671
+ 438
672
+ );
673
+ try {
674
+ assertOpenedWithinRootSync(safe, root);
675
+ const buf = Buffer.from(content, "utf8");
676
+ let off = 0;
677
+ while (off < buf.length) {
678
+ const bytesWritten = nodeFs.writeSync(fd, buf, off, buf.length - off, off);
679
+ if (bytesWritten <= 0) break;
680
+ off += bytesWritten;
681
+ }
682
+ nodeFs.ftruncateSync(fd, buf.length);
683
+ } finally {
684
+ nodeFs.closeSync(fd);
685
+ }
686
+ }
687
+ async function listFiles(root, rootArg) {
688
+ const safeRoot = await resolveWithinProjectRoot(rootArg, root);
689
+ const out = [];
690
+ const queue = [safeRoot];
691
+ while (queue.length > 0) {
692
+ if (out.length >= LIST_FILE_LIMIT) break;
693
+ const dir = queue.shift();
694
+ let entries;
695
+ try {
696
+ entries = await fs2.readdir(dir, { withFileTypes: true });
697
+ } catch (err) {
698
+ const code = err.code;
699
+ if (code === "EACCES" || code === "ENOENT" || code === "ENOTDIR") continue;
700
+ throw err;
701
+ }
702
+ for (const e of entries) {
703
+ if (out.length >= LIST_FILE_LIMIT) break;
704
+ const full = path2.join(dir, e.name);
705
+ if (e.isDirectory()) {
706
+ if (SKIP_DIRS.has(e.name)) continue;
707
+ queue.push(full);
708
+ continue;
709
+ }
710
+ if (!e.isFile()) continue;
711
+ out.push(path2.relative(safeRoot, full).split(path2.sep).join("/"));
712
+ }
713
+ }
714
+ return out;
715
+ }
716
+ function registerProjectFsIpc(ctx) {
717
+ const getRoot = () => ctx.workspace.getProjectPath();
718
+ const getLastClosedRoot = () => ctx.workspace.getLastClosedProjectPath();
719
+ return new IpcRegistry(ctx.senderPolicy).handle(ProjectFsChannel.GetRoot, () => getRoot()).handle(ProjectFsChannel.ReadFile, (_event, ...args) => {
720
+ const [p] = validate(ProjectFsChannel.ReadFile, ReadFileSchema, args);
721
+ return readFile(getRoot(), p);
722
+ }).handle(ProjectFsChannel.WriteFile, (_event, ...args) => {
723
+ const [p, content] = validate(ProjectFsChannel.WriteFile, WriteFileSchema, args);
724
+ const root = pickWriteRoot(p, { current: getRoot(), lastClosed: getLastClosedRoot() });
725
+ return writeFile(root, p, content);
726
+ }).handleSync(ProjectFsChannel.WriteFileSync, (_event, ...args) => {
727
+ const [p, content] = validate(ProjectFsChannel.WriteFileSync, WriteFileSchema, args);
728
+ const root = pickWriteRoot(p, { current: getRoot(), lastClosed: getLastClosedRoot() });
729
+ writeFileSync(root, p, content);
730
+ return { ok: true };
731
+ }).handle(ProjectFsChannel.ListFiles, (_event, ...args) => {
732
+ const [p] = validate(ProjectFsChannel.ListFiles, ListFilesSchema, args);
733
+ return listFiles(getRoot(), p);
734
+ });
735
+ }
736
+
81
737
  // src/main/app/app.ts
82
- import { app as app13, BrowserWindow as BrowserWindow4, nativeImage } from "electron";
83
- import fs7 from "fs";
84
- import path12 from "path";
738
+ import { app as app13, BrowserWindow as BrowserWindow4, nativeImage, session as session2 } from "electron";
739
+ import fs10 from "fs";
740
+ import path16 from "path";
85
741
 
86
742
  // src/main/utils/paths.ts
87
- import fs2 from "fs";
88
- import path2 from "path";
743
+ import fs3 from "fs";
744
+ import path3 from "path";
89
745
  import { fileURLToPath } from "url";
90
746
  function resolveDevtoolsPackageRoot() {
91
- let dir = path2.dirname(fileURLToPath(import.meta.url));
92
- const root = path2.parse(dir).root;
747
+ let dir = path3.dirname(fileURLToPath(import.meta.url));
748
+ const root = path3.parse(dir).root;
93
749
  while (dir !== root) {
94
- const pkgPath = path2.join(dir, "package.json");
750
+ const pkgPath = path3.join(dir, "package.json");
95
751
  try {
96
- const pkg2 = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
752
+ const pkg2 = JSON.parse(fs3.readFileSync(pkgPath, "utf8"));
97
753
  if (pkg2.name === "@dimina-kit/devtools") return dir;
98
754
  } catch {
99
755
  }
100
- dir = path2.dirname(dir);
756
+ dir = path3.dirname(dir);
101
757
  }
102
758
  throw new Error("@dimina-kit/devtools package root not found from " + fileURLToPath(import.meta.url));
103
759
  }
104
760
  var devtoolsPackageRoot = resolveDevtoolsPackageRoot();
105
- var rendererDir = path2.join(devtoolsPackageRoot, "dist/renderer");
106
- var defaultPreloadPath = path2.join(devtoolsPackageRoot, "dist/preload/windows/simulator.js");
107
- var mainPreloadPath = path2.join(devtoolsPackageRoot, "dist/preload/windows/main.cjs");
108
- var simulatorDir = path2.join(devtoolsPackageRoot, "dist/simulator");
761
+ var rendererDir = path3.join(devtoolsPackageRoot, "dist/renderer");
762
+ var defaultPreloadPath = path3.join(devtoolsPackageRoot, "dist/preload/windows/simulator.js");
763
+ var mainPreloadPath = path3.join(devtoolsPackageRoot, "dist/preload/windows/main.cjs");
764
+ var simulatorDir = path3.join(devtoolsPackageRoot, "dist/simulator");
109
765
 
110
766
  // src/main/utils/theme.ts
111
767
  import { BrowserWindow, nativeTheme as nativeTheme2 } from "electron";
112
-
113
- // src/main/utils/disposable.ts
114
- function toDisposable(fn) {
115
- let done = false;
116
- return {
117
- dispose() {
118
- if (done) return;
119
- done = true;
120
- return fn();
121
- }
122
- };
123
- }
124
- var DisposableRegistry = class {
125
- entries = [];
126
- _disposed = false;
127
- /**
128
- * Number of live entries. A wrapper's `dispose` splices its entry out and
129
- * `disposeAll` clears the array, so `entries.length` is the live count.
130
- */
131
- get size() {
132
- return this.entries.length;
133
- }
134
- add(d) {
135
- if (this._disposed) {
136
- throw new Error("cannot add to disposed registry");
137
- }
138
- const fn = typeof d === "function" ? d : () => d.dispose();
139
- const entry = { fn, released: false };
140
- this.entries.push(entry);
141
- return {
142
- dispose: () => {
143
- if (entry.released) return;
144
- entry.released = true;
145
- const i = this.entries.indexOf(entry);
146
- if (i >= 0) this.entries.splice(i, 1);
147
- return fn();
148
- }
149
- };
150
- }
151
- async disposeAll() {
152
- if (this._disposed) return;
153
- this._disposed = true;
154
- const items = this.entries.slice().reverse();
155
- this.entries = [];
156
- const errors = [];
157
- for (const entry of items) {
158
- if (entry.released) continue;
159
- entry.released = true;
160
- try {
161
- await entry.fn();
162
- } catch (e) {
163
- errors.push(e);
164
- }
165
- }
166
- if (errors.length > 0) {
167
- throw new AggregateError(errors, "DisposableRegistry encountered errors during disposeAll");
168
- }
169
- }
170
- dispose() {
171
- return this.disposeAll();
172
- }
173
- };
174
-
175
- // src/main/utils/theme.ts
176
768
  function themeBg() {
177
769
  return nativeTheme2.shouldUseDarkColors ? "#1a1a1a" : "#fafafa";
178
770
  }
@@ -205,7 +797,7 @@ function registerAppLifecycle() {
205
797
 
206
798
  // src/main/windows/main-window/create.ts
207
799
  import { app as app4, BrowserWindow as BrowserWindow2, View, session, shell as shell2 } from "electron";
208
- import path4 from "path";
800
+ import path5 from "path";
209
801
 
210
802
  // src/main/services/simulator/referer.ts
211
803
  var DEFAULT_VERSION = "develop";
@@ -228,7 +820,7 @@ function getSimulatorServicewechatReferer() {
228
820
 
229
821
  // src/main/windows/navigation-hardening.ts
230
822
  import { shell } from "electron";
231
- import path3 from "path";
823
+ import path4 from "path";
232
824
  import { pathToFileURL } from "url";
233
825
  function handleWindowOpenExternal(url) {
234
826
  try {
@@ -241,7 +833,7 @@ function handleWindowOpenExternal(url) {
241
833
  return { action: "deny" };
242
834
  }
243
835
  function applyNavigationHardening(wc, rendererDir2) {
244
- const rendererPrefix = pathToFileURL(rendererDir2 + path3.sep).href;
836
+ const rendererPrefix = pathToFileURL(rendererDir2 + path4.sep).href;
245
837
  wc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
246
838
  wc.on("will-navigate", (event, url) => {
247
839
  let parsed;
@@ -319,7 +911,7 @@ function createMainWindow(opts) {
319
911
  }
320
912
  }
321
913
  });
322
- const rendererDir2 = path4.dirname(opts.indexHtml);
914
+ const rendererDir2 = path5.dirname(opts.indexHtml);
323
915
  applyNavigationHardening(mainWindow.webContents, rendererDir2);
324
916
  mainWindow.webContents.on("will-attach-webview", (event, webPreferences, params) => {
325
917
  ;
@@ -408,13 +1000,13 @@ function createWorkbenchSenderPolicy(ctx) {
408
1000
  }
409
1001
 
410
1002
  // src/main/services/default-adapter.ts
411
- import path5 from "path";
1003
+ import path6 from "path";
412
1004
  import { app as app5 } from "electron";
413
1005
  var defaultAdapter = {
414
1006
  async openProject(opts) {
415
1007
  const diminaKit = await import("@dimina-kit/devkit");
416
1008
  const openProjectOpts = {
417
- outputDir: path5.join(app5.getPath("userData"), "dimina-fe-output"),
1009
+ outputDir: path6.join(app5.getPath("userData"), "dimina-fe-output"),
418
1010
  ...opts,
419
1011
  simulatorDir
420
1012
  };
@@ -422,112 +1014,6 @@ var defaultAdapter = {
422
1014
  }
423
1015
  };
424
1016
 
425
- // src/shared/ipc-channels.ts
426
- var SimulatorChannel = {
427
- Attach: "simulator:attach",
428
- Detach: "simulator:detach",
429
- Resize: "simulator:resize",
430
- SetVisible: "simulator:setVisible",
431
- Console: "simulator:console"
432
- };
433
- var SimulatorCustomApiChannel = {
434
- List: "simulator:custom-apis:list",
435
- Invoke: "simulator:custom-apis:invoke"
436
- };
437
- var SimulatorStorageChannel = {
438
- GetSnapshot: "simulator:storage:snapshot",
439
- GetActivePrefix: "simulator:storage:activePrefix",
440
- Set: "simulator:storage:set",
441
- Remove: "simulator:storage:remove",
442
- Clear: "simulator:storage:clear",
443
- ClearAll: "simulator:storage:clearAll",
444
- Event: "simulator:storage:event"
445
- };
446
- var SimulatorElementChannel = {
447
- Inspect: "simulator:element:inspect",
448
- Clear: "simulator:element:clear"
449
- };
450
- var WorkbenchSettingsChannel = {
451
- Get: "workbenchSettings:get",
452
- Save: "workbenchSettings:save",
453
- SetTheme: "workbenchSettings:setTheme",
454
- GetCdpStatus: "workbenchSettings:getCdpStatus",
455
- GetMcpStatus: "workbenchSettings:getMcpStatus",
456
- SetVisible: "workbenchSettings:setVisible",
457
- Init: "workbenchSettings:init"
458
- };
459
- var ProjectChannel = {
460
- Open: "project:open",
461
- Close: "project:close",
462
- GetPages: "project:getPages",
463
- GetCompileConfig: "project:getCompileConfig",
464
- SaveCompileConfig: "project:saveCompileConfig",
465
- Status: "project:status",
466
- CaptureThumbnail: "project:captureThumbnail",
467
- GetThumbnail: "project:getThumbnail"
468
- };
469
- var ProjectsChannel = {
470
- List: "projects:list",
471
- Add: "projects:add",
472
- Remove: "projects:remove",
473
- /** Phase 3: merged + sanitized template catalog for the create-project dialog. */
474
- ListTemplates: "projects:listTemplates",
475
- /** Phase 3: host-supplied create-project dialog hook (returns input or null). */
476
- OpenCreateDialog: "projects:openCreateDialog",
477
- /** Phase 3: server-side scaffold + register (delegates to create-project-service). */
478
- Create: "projects:create",
479
- /** Default values used to pre-fill the create-project dialog (baseDir). */
480
- GetCreateDefaults: "projects:getCreateDefaults"
481
- };
482
- var DialogChannel = {
483
- OpenDirectory: "dialog:openDirectory"
484
- };
485
- var PanelChannel = {
486
- List: "panel:list",
487
- Eval: "panel:eval",
488
- Select: "panel:select",
489
- SelectSimulator: "panel:selectSimulator"
490
- };
491
- var PopoverChannel = {
492
- Show: "popover:show",
493
- Hide: "popover:hide",
494
- Relaunch: "popover:relaunch",
495
- Closed: "popover:closed",
496
- Init: "popover:init"
497
- };
498
- var ToolbarChannel = {
499
- GetActions: "toolbar:getActions",
500
- ActionsChanged: "toolbar:actionsChanged",
501
- /** Invoke a toolbar action by id: `invoke(Invoke, actionId)`. */
502
- Invoke: "toolbar:invoke"
503
- };
504
- var WindowChannel = {
505
- NavigateBack: "window:navigateBack"
506
- };
507
- var AppChannel = {
508
- GetPreloadPath: "app:getPreloadPath",
509
- GetBranding: "app:getBranding",
510
- GetHeaderHeight: "app:getHeaderHeight"
511
- };
512
- var AutomationChannel = {
513
- GetPort: "automation:port"
514
- };
515
- var SettingsChannel = {
516
- SetVisible: "settings:setVisible",
517
- ConfigChanged: "settings:configChanged",
518
- ProjectSettingsChanged: "settings:projectSettingsChanged",
519
- Init: "settings:init",
520
- Closed: "settings:closed",
521
- Changed: "settings:changed"
522
- };
523
- var UpdateChannel = {
524
- Check: "updates:check",
525
- Download: "updates:download",
526
- Install: "updates:install",
527
- DownloadProgress: "updates:downloadProgress",
528
- Available: "updates:available"
529
- };
530
-
531
1017
  // src/main/services/notifications/renderer-notifier.ts
532
1018
  function liveWebContents(wc) {
533
1019
  if (!wc) return null;
@@ -584,7 +1070,7 @@ function createRendererNotifier(ctx) {
584
1070
 
585
1071
  // src/main/services/views/view-manager.ts
586
1072
  import { WebContentsView as WebContentsView2, webContents } from "electron";
587
- import path6 from "path";
1073
+ import path7 from "path";
588
1074
 
589
1075
  // src/main/services/layout/index.ts
590
1076
  var SPLITTER_W = 4;
@@ -608,7 +1094,7 @@ function computeSettingsBounds(contentWidth, contentHeight, headerHeight) {
608
1094
  return {
609
1095
  x: Math.max(0, contentWidth - SETTINGS_W),
610
1096
  y: headerHeight,
611
- width: SETTINGS_W,
1097
+ width: Math.min(SETTINGS_W, Math.max(1, contentWidth)),
612
1098
  height: Math.max(1, contentHeight - headerHeight)
613
1099
  };
614
1100
  }
@@ -616,7 +1102,7 @@ function computePopoverBounds(contentWidth, contentHeight, headerHeight) {
616
1102
  return {
617
1103
  x: 0,
618
1104
  y: headerHeight,
619
- width: contentWidth,
1105
+ width: Math.max(1, contentWidth),
620
1106
  height: contentHeight - headerHeight
621
1107
  };
622
1108
  }
@@ -631,6 +1117,7 @@ function createViewManager(ctx) {
631
1117
  let popoverView = null;
632
1118
  let lastSimWidth = 375;
633
1119
  let simulatorWebContentsId = null;
1120
+ let simulatorBoundsOverride = null;
634
1121
  function destroyViewInternal(view) {
635
1122
  if (!view) return;
636
1123
  if (!ctx.windows.mainWindow.isDestroyed()) {
@@ -648,8 +1135,37 @@ function createViewManager(ctx) {
648
1135
  }
649
1136
  function applySimulatorBounds(simWidth) {
650
1137
  if (!simulatorView || ctx.windows.mainWindow.isDestroyed()) return;
1138
+ if (simulatorBoundsOverride) {
1139
+ simulatorView.setBounds(simulatorBoundsOverride);
1140
+ return;
1141
+ }
651
1142
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
652
- simulatorView.setBounds(computeSimulatorBounds(w, h, simWidth, headerHeight));
1143
+ simulatorView.setBounds(
1144
+ computeSimulatorBounds(w, h, simWidth, headerHeight)
1145
+ );
1146
+ }
1147
+ function isHidden(b) {
1148
+ return b.width <= 0 || b.height <= 0;
1149
+ }
1150
+ function setSimulatorDevtoolsBounds(bounds) {
1151
+ simulatorBoundsOverride = bounds;
1152
+ if (!simulatorView || simulatorView.webContents.isDestroyed()) return;
1153
+ if (ctx.windows.mainWindow.isDestroyed()) return;
1154
+ if (isHidden(bounds)) {
1155
+ if (simulatorViewAdded) {
1156
+ try {
1157
+ ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
1158
+ } catch {
1159
+ }
1160
+ simulatorViewAdded = false;
1161
+ }
1162
+ return;
1163
+ }
1164
+ if (!simulatorViewAdded) {
1165
+ ctx.windows.mainWindow.contentView.addChildView(simulatorView);
1166
+ simulatorViewAdded = true;
1167
+ }
1168
+ simulatorView.setBounds(bounds);
653
1169
  }
654
1170
  function applySettingsBounds() {
655
1171
  if (!settingsView || ctx.windows.mainWindow.isDestroyed()) return;
@@ -690,25 +1206,35 @@ function createViewManager(ctx) {
690
1206
  let tries = 0
691
1207
  const timer = setInterval(() => {
692
1208
  tries++
693
- const tabs = document.querySelectorAll('[role="tab"], .tabbed-pane-header-tab')
694
- for (const tab of tabs) {
695
- const label = (tab.getAttribute('aria-label') || tab.textContent || '').trim().toLowerCase()
696
- if (label === 'console' || label.startsWith('console')) {
697
- tab.click()
1209
+ try {
1210
+ const UI = globalThis.UI
1211
+ const vm = UI && UI.ViewManager && typeof UI.ViewManager.instance === 'function'
1212
+ ? UI.ViewManager.instance()
1213
+ : null
1214
+ if (vm && typeof vm.showView === 'function') {
1215
+ vm.showView('console')
698
1216
  clearInterval(timer)
699
1217
  return
700
1218
  }
701
- }
702
- if (tries > 40) clearInterval(timer)
703
- }, 100)
1219
+ } catch {}
1220
+ if (tries > 80) clearInterval(timer)
1221
+ }, 50)
704
1222
  })()
705
1223
  `).catch(() => {
706
1224
  });
707
1225
  });
708
1226
  if (getDefaultTab(ctx) === "simulator") {
709
- ctx.windows.mainWindow.contentView.addChildView(simulatorView);
710
- simulatorViewAdded = true;
711
- applySimulatorBounds(simWidth);
1227
+ if (simulatorBoundsOverride) {
1228
+ if (!isHidden(simulatorBoundsOverride)) {
1229
+ ctx.windows.mainWindow.contentView.addChildView(simulatorView);
1230
+ simulatorViewAdded = true;
1231
+ simulatorView.setBounds(simulatorBoundsOverride);
1232
+ }
1233
+ } else {
1234
+ ctx.windows.mainWindow.contentView.addChildView(simulatorView);
1235
+ simulatorViewAdded = true;
1236
+ applySimulatorBounds(simWidth);
1237
+ }
712
1238
  }
713
1239
  }
714
1240
  function detachSimulator() {
@@ -720,6 +1246,7 @@ function createViewManager(ctx) {
720
1246
  simulatorView = null;
721
1247
  simulatorViewAdded = false;
722
1248
  simulatorWebContentsId = null;
1249
+ simulatorBoundsOverride = null;
723
1250
  }
724
1251
  function showSimulator(simWidth) {
725
1252
  lastSimWidth = simWidth;
@@ -752,7 +1279,7 @@ function createViewManager(ctx) {
752
1279
  });
753
1280
  applyNavigationHardening(settingsView.webContents, ctx.rendererDir);
754
1281
  await settingsView.webContents.loadFile(
755
- path6.join(ctx.rendererDir, "entries/settings/index.html")
1282
+ path7.join(ctx.rendererDir, "entries/settings/index.html")
756
1283
  );
757
1284
  }
758
1285
  if (!settingsViewAdded) {
@@ -789,7 +1316,7 @@ function createViewManager(ctx) {
789
1316
  ctx.notify.popoverInit(popover, data);
790
1317
  });
791
1318
  popover.webContents.loadFile(
792
- path6.join(ctx.rendererDir, "entries/popover/index.html")
1319
+ path7.join(ctx.rendererDir, "entries/popover/index.html")
793
1320
  );
794
1321
  }
795
1322
  function hidePopover() {
@@ -860,7 +1387,8 @@ function createViewManager(ctx) {
860
1387
  return popoverView.webContents.id;
861
1388
  },
862
1389
  resize,
863
- setVisible
1390
+ setVisible,
1391
+ setSimulatorDevtoolsBounds
864
1392
  };
865
1393
  }
866
1394
 
@@ -892,61 +1420,21 @@ function createWindowService(mainWindow) {
892
1420
 
893
1421
  // src/main/services/projects/project-repository.ts
894
1422
  import { app as app6 } from "electron";
895
- import path7 from "path";
896
- import fs3 from "fs";
897
-
898
- // src/main/utils/logger.ts
899
- var LEVEL_PRIORITY = {
900
- debug: 0,
901
- info: 1,
902
- warn: 2,
903
- error: 3
904
- };
905
- var LEVEL_PREFIX = {
906
- debug: "[DEBUG]",
907
- info: "[INFO]",
908
- warn: "[WARN]",
909
- error: "[ERROR]"
910
- };
911
- var currentLevel = "debug";
912
- function shouldLog(level) {
913
- return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];
914
- }
915
- function formatMessage(level, tag, message) {
916
- const ts = (/* @__PURE__ */ new Date()).toISOString();
917
- return `${ts} ${LEVEL_PREFIX[level]} [${tag}] ${message}`;
918
- }
919
- function createLogger(tag) {
920
- return {
921
- debug(message, ...args) {
922
- if (shouldLog("debug")) console.debug(formatMessage("debug", tag, message), ...args);
923
- },
924
- info(message, ...args) {
925
- if (shouldLog("info")) console.info(formatMessage("info", tag, message), ...args);
926
- },
927
- warn(message, ...args) {
928
- if (shouldLog("warn")) console.warn(formatMessage("warn", tag, message), ...args);
929
- },
930
- error(message, ...args) {
931
- if (shouldLog("error")) console.error(formatMessage("error", tag, message), ...args);
932
- }
933
- };
934
- }
935
-
936
- // src/main/services/projects/project-repository.ts
937
- var log = createLogger("projects");
1423
+ import path8 from "path";
1424
+ import fs4 from "fs";
1425
+ var log2 = createLogger("projects");
938
1426
  function getProjectsFile() {
939
- return path7.join(app6.getPath("userData"), "dimina-projects.json");
1427
+ return path8.join(app6.getPath("userData"), "dimina-projects.json");
940
1428
  }
941
1429
  function load() {
942
1430
  try {
943
- return JSON.parse(fs3.readFileSync(getProjectsFile(), "utf-8"));
1431
+ return JSON.parse(fs4.readFileSync(getProjectsFile(), "utf-8"));
944
1432
  } catch {
945
1433
  return [];
946
1434
  }
947
1435
  }
948
1436
  function save(projects) {
949
- fs3.writeFileSync(getProjectsFile(), JSON.stringify(projects, null, 2));
1437
+ fs4.writeFileSync(getProjectsFile(), JSON.stringify(projects, null, 2));
950
1438
  }
951
1439
  function listProjects() {
952
1440
  return load();
@@ -955,20 +1443,20 @@ function validateProjectDir(dirPath) {
955
1443
  if (!dirPath) {
956
1444
  return "\u5C0F\u7A0B\u5E8F\u76EE\u5F55\u8DEF\u5F84\u4E3A\u7A7A\uFF0C\u8BF7\u9009\u62E9\u5305\u542B\u5C0F\u7A0B\u5E8F\u6E90\u7801\u7684\u76EE\u5F55";
957
1445
  }
958
- if (!fs3.existsSync(dirPath)) {
1446
+ if (!fs4.existsSync(dirPath)) {
959
1447
  return `\u5C0F\u7A0B\u5E8F\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${dirPath}`;
960
1448
  }
961
- if (!fs3.existsSync(path7.join(dirPath, "app.json"))) {
962
- const configPath = path7.join(dirPath, "project.config.json");
963
- if (fs3.existsSync(configPath)) {
1449
+ if (!fs4.existsSync(path8.join(dirPath, "app.json"))) {
1450
+ const configPath = path8.join(dirPath, "project.config.json");
1451
+ if (fs4.existsSync(configPath)) {
964
1452
  try {
965
- const cfg = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1453
+ const cfg = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
966
1454
  if (cfg.miniprogramRoot) {
967
- const resolvedRoot = path7.resolve(dirPath, cfg.miniprogramRoot);
1455
+ const resolvedRoot = path8.resolve(dirPath, cfg.miniprogramRoot);
968
1456
  return `\u8BE5\u76EE\u5F55\u7F3A\u5C11 app.json\uFF0Cproject.config.json \u4E2D\u6307\u5B9A\u4E86 miniprogramRoot: "${cfg.miniprogramRoot}"\uFF0C\u8BF7\u5BFC\u5165 ${resolvedRoot}`;
969
1457
  }
970
1458
  } catch (err) {
971
- log.warn("Failed to parse project.config.json", err);
1459
+ log2.warn("Failed to parse project.config.json", err);
972
1460
  }
973
1461
  }
974
1462
  return "\u8BE5\u76EE\u5F55\u7F3A\u5C11 app.json\uFF0C\u8BF7\u9009\u62E9\u5305\u542B\u5C0F\u7A0B\u5E8F\u6E90\u7801\u7684\u76EE\u5F55";
@@ -977,15 +1465,15 @@ function validateProjectDir(dirPath) {
977
1465
  }
978
1466
  function addProject(dirPath) {
979
1467
  const projects = load();
980
- let name = path7.basename(dirPath);
1468
+ let name = path8.basename(dirPath);
981
1469
  try {
982
- const configPath = path7.join(dirPath, "project.config.json");
983
- if (fs3.existsSync(configPath)) {
984
- const cfg = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1470
+ const configPath = path8.join(dirPath, "project.config.json");
1471
+ if (fs4.existsSync(configPath)) {
1472
+ const cfg = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
985
1473
  if (cfg.projectname) name = cfg.projectname;
986
1474
  }
987
1475
  } catch (err) {
988
- log.warn("Failed to read project name from config", err);
1476
+ log2.warn("Failed to read project name from config", err);
989
1477
  }
990
1478
  const project = { name, path: dirPath, lastOpened: null };
991
1479
  const idx = projects.findIndex((p) => p.path === dirPath);
@@ -1018,10 +1506,10 @@ function getCompileConfig(dirPath) {
1018
1506
  };
1019
1507
  }
1020
1508
  function getProjectPages(dirPath) {
1021
- const appJsonPath = path7.join(dirPath, "app.json");
1509
+ const appJsonPath = path8.join(dirPath, "app.json");
1022
1510
  try {
1023
1511
  const appJson = JSON.parse(
1024
- fs3.readFileSync(appJsonPath, "utf-8")
1512
+ fs4.readFileSync(appJsonPath, "utf-8")
1025
1513
  );
1026
1514
  return {
1027
1515
  pages: appJson.pages || [],
@@ -1044,8 +1532,8 @@ function getProjectSettings(projectPath) {
1044
1532
  return { uploadWithSourceMap: false };
1045
1533
  }
1046
1534
  try {
1047
- const configPath = path7.join(projectPath, "project.config.json");
1048
- const config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1535
+ const configPath = path8.join(projectPath, "project.config.json");
1536
+ const config = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
1049
1537
  return {
1050
1538
  uploadWithSourceMap: !!config.setting?.uploadWithSourceMap
1051
1539
  };
@@ -1055,17 +1543,17 @@ function getProjectSettings(projectPath) {
1055
1543
  }
1056
1544
  function updateProjectSettings(projectPath, patch) {
1057
1545
  if (!projectPath) return;
1058
- const configPath = path7.join(projectPath, "project.config.json");
1546
+ const configPath = path8.join(projectPath, "project.config.json");
1059
1547
  let config = {};
1060
1548
  try {
1061
- config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1549
+ config = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
1062
1550
  } catch {
1063
1551
  }
1064
1552
  const nextSetting = {
1065
1553
  ...config.setting ?? {},
1066
1554
  ...patch.uploadWithSourceMap === void 0 ? {} : { uploadWithSourceMap: patch.uploadWithSourceMap }
1067
1555
  };
1068
- fs3.writeFileSync(
1556
+ fs4.writeFileSync(
1069
1557
  configPath,
1070
1558
  JSON.stringify({ ...config, setting: nextSetting }, null, 2)
1071
1559
  );
@@ -1082,6 +1570,7 @@ var DEFAULT_COMPILE_CONFIG = {
1082
1570
  function createWorkspaceService(ctx) {
1083
1571
  let currentSession = null;
1084
1572
  let currentProjectPath = "";
1573
+ let lastClosedProjectPath = "";
1085
1574
  function sendStatus(status2, message, hotReload) {
1086
1575
  ctx.notify.projectStatus(hotReload ? { status: status2, message, hotReload: true } : { status: status2, message });
1087
1576
  }
@@ -1094,11 +1583,11 @@ function createWorkspaceService(ctx) {
1094
1583
  }
1095
1584
  async function disposeSession() {
1096
1585
  if (!currentSession) return;
1097
- const session2 = currentSession;
1586
+ const session3 = currentSession;
1098
1587
  currentSession = null;
1099
1588
  try {
1100
1589
  await Promise.race([
1101
- session2.close(),
1590
+ session3.close(),
1102
1591
  new Promise(
1103
1592
  (_, reject) => setTimeout(() => reject(new Error("session close timed out")), 5e3)
1104
1593
  )
@@ -1107,8 +1596,8 @@ function createWorkspaceService(ctx) {
1107
1596
  console.error("[workspace] session close failed:", err);
1108
1597
  }
1109
1598
  }
1110
- function applyRefererFromSession(session2) {
1111
- const appInfo = session2.appInfo;
1599
+ function applyRefererFromSession(session3) {
1600
+ const appInfo = session3.appInfo;
1112
1601
  if (appInfo && typeof appInfo.appId === "string" && appInfo.appId.length > 0) {
1113
1602
  setSimulatorServicewechatReferer(
1114
1603
  appInfo.appId,
@@ -1128,20 +1617,27 @@ function createWorkspaceService(ctx) {
1128
1617
  return projects.some((p) => p.path === dirPath);
1129
1618
  },
1130
1619
  validateProjectDir: async (dirPath) => provider.validateProjectDir ? provider.validateProjectDir(dirPath) : null,
1131
- async openProject(projectPath) {
1132
- clearSimulatorServicewechatReferer();
1133
- await disposeSession();
1134
- currentProjectPath = "";
1620
+ async openProject(projectPath) {
1135
1621
  const dirError = provider.validateProjectDir ? await provider.validateProjectDir(projectPath) : null;
1136
1622
  if (dirError) {
1137
1623
  sendStatus("error", dirError);
1138
1624
  return { success: false, error: dirError };
1139
1625
  }
1626
+ const gate = ctx.beforeOpenProject ? await ctx.beforeOpenProject(projectPath) : void 0;
1627
+ if (gate && !gate.allow) {
1628
+ const message = gate.userMessage ?? gate.reason;
1629
+ sendStatus("error", message);
1630
+ return { success: false, error: message };
1631
+ }
1632
+ clearSimulatorServicewechatReferer();
1633
+ await disposeSession();
1634
+ currentProjectPath = "";
1635
+ lastClosedProjectPath = "";
1140
1636
  sendStatus("compiling", "\u6B63\u5728\u7F16\u8BD1...");
1141
1637
  const { compile } = loadWorkbenchSettings();
1142
- let session2;
1638
+ let session3;
1143
1639
  try {
1144
- session2 = await ctx.adapter.openProject({
1640
+ session3 = await ctx.adapter.openProject({
1145
1641
  projectPath,
1146
1642
  sourcemap: true,
1147
1643
  watch: compile.watch,
@@ -1153,27 +1649,29 @@ function createWorkspaceService(ctx) {
1153
1649
  sendStatus("error", String(err));
1154
1650
  return { success: false, error: String(err) };
1155
1651
  }
1156
- currentSession = session2;
1652
+ currentSession = session3;
1157
1653
  currentProjectPath = projectPath;
1158
1654
  bestEffort("updateLastOpened", () => {
1159
1655
  if (provider.updateLastOpened) provider.updateLastOpened(projectPath);
1160
1656
  });
1161
- bestEffort("sendStatus", () => sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210"));
1162
- bestEffort("applyReferer", () => applyRefererFromSession(session2));
1657
+ bestEffort("sendStatus", () => sendStatus("ready", gate?.statusMessage ?? "\u7F16\u8BD1\u5B8C\u6210"));
1658
+ bestEffort("applyReferer", () => applyRefererFromSession(session3));
1163
1659
  return {
1164
1660
  success: true,
1165
- port: session2.port,
1166
- appInfo: session2.appInfo
1661
+ port: session3.port,
1662
+ appInfo: session3.appInfo
1167
1663
  };
1168
1664
  },
1169
1665
  async closeProject() {
1170
1666
  clearSimulatorServicewechatReferer();
1171
1667
  await disposeSession();
1668
+ if (currentProjectPath !== "") lastClosedProjectPath = currentProjectPath;
1172
1669
  currentProjectPath = "";
1173
1670
  ctx.views.disposeAll();
1174
1671
  },
1175
1672
  getSession: () => currentSession,
1176
1673
  getProjectPath: () => currentProjectPath,
1674
+ getLastClosedProjectPath: () => lastClosedProjectPath,
1177
1675
  hasActiveSession: () => currentSession !== null,
1178
1676
  async captureThumbnail(projectPath) {
1179
1677
  const wc = ctx.views.getSimulatorWebContents();
@@ -1213,29 +1711,29 @@ function createWorkspaceService(ctx) {
1213
1711
 
1214
1712
  // src/main/services/projects/thumbnail.ts
1215
1713
  import { createHash } from "crypto";
1216
- import fs4 from "fs";
1217
- import path8 from "path";
1714
+ import fs5 from "fs";
1715
+ import path9 from "path";
1218
1716
  import { app as app7 } from "electron";
1219
1717
  function getThumbnailDir() {
1220
- return path8.join(app7.getPath("userData"), "thumbnails");
1718
+ return path9.join(app7.getPath("userData"), "thumbnails");
1221
1719
  }
1222
1720
  function hashProjectPath(projectPath) {
1223
1721
  return createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
1224
1722
  }
1225
1723
  function getThumbnailPath(projectPath) {
1226
- return path8.join(getThumbnailDir(), `${hashProjectPath(projectPath)}.png`);
1724
+ return path9.join(getThumbnailDir(), `${hashProjectPath(projectPath)}.png`);
1227
1725
  }
1228
1726
  var DATA_URL_PNG_PREFIX = "data:image/png;base64,";
1229
1727
  function saveThumbnailFromDataUrl(projectPath, imageDataUrl) {
1230
1728
  if (!imageDataUrl.startsWith(DATA_URL_PNG_PREFIX)) return;
1231
1729
  const png = Buffer.from(imageDataUrl.slice(DATA_URL_PNG_PREFIX.length), "base64");
1232
- fs4.mkdirSync(getThumbnailDir(), { recursive: true });
1233
- fs4.writeFileSync(getThumbnailPath(projectPath), png);
1730
+ fs5.mkdirSync(getThumbnailDir(), { recursive: true });
1731
+ fs5.writeFileSync(getThumbnailPath(projectPath), png);
1234
1732
  }
1235
1733
  function loadThumbnail(projectPath) {
1236
1734
  const filePath = getThumbnailPath(projectPath);
1237
1735
  try {
1238
- const buf = fs4.readFileSync(filePath);
1736
+ const buf = fs5.readFileSync(filePath);
1239
1737
  return `${DATA_URL_PNG_PREFIX}${buf.toString("base64")}`;
1240
1738
  } catch {
1241
1739
  return null;
@@ -1282,6 +1780,18 @@ function createSimulatorApiRegistry() {
1282
1780
  }
1283
1781
 
1284
1782
  // src/main/services/toolbar/toolbar-store.ts
1783
+ function toPublicAction(action) {
1784
+ const projected = {
1785
+ id: action.id,
1786
+ label: action.label
1787
+ };
1788
+ if (action.kind) projected.kind = action.kind;
1789
+ if (action.placement) projected.placement = action.placement;
1790
+ if (action.icon) projected.icon = action.icon;
1791
+ if (action.displayInitial) projected.displayInitial = action.displayInitial;
1792
+ if (action.avatarUrl) projected.avatarUrl = action.avatarUrl;
1793
+ return projected;
1794
+ }
1285
1795
  function createToolbarStore() {
1286
1796
  let actions = [];
1287
1797
  return {
@@ -1296,7 +1806,7 @@ function createToolbarStore() {
1296
1806
  actions = next.slice();
1297
1807
  },
1298
1808
  list() {
1299
- return actions.map(({ id, label }) => ({ id, label }));
1809
+ return actions.map(toPublicAction);
1300
1810
  },
1301
1811
  getHandler(id) {
1302
1812
  return actions.find((a) => a.id === id)?.handler;
@@ -1331,20 +1841,20 @@ function sanitizeTemplates(templates) {
1331
1841
  }
1332
1842
 
1333
1843
  // src/main/services/projects/builtin-templates.ts
1334
- import path9 from "node:path";
1335
- var TEMPLATES_DIR = path9.join(devtoolsPackageRoot, "templates");
1844
+ import path10 from "node:path";
1845
+ var TEMPLATES_DIR = path10.join(devtoolsPackageRoot, "templates");
1336
1846
  var BUILTIN_TEMPLATES = [
1337
1847
  {
1338
1848
  id: "blank",
1339
1849
  name: "Blank",
1340
1850
  description: "\u6700\u5C0F\u9AA8\u67B6\uFF1Aapp.* + \u4E00\u4E2A index \u9875\u9762",
1341
- source: { type: "directory", path: path9.join(TEMPLATES_DIR, "blank") }
1851
+ source: { type: "directory", path: path10.join(TEMPLATES_DIR, "blank") }
1342
1852
  },
1343
1853
  {
1344
1854
  id: "taro-todo",
1345
1855
  name: "Taro Todo",
1346
1856
  description: "Taro \u7F16\u8BD1\u4EA7\u7269\u7684 Todo \u793A\u4F8B",
1347
- source: { type: "directory", path: path9.join(TEMPLATES_DIR, "taro-todo") }
1857
+ source: { type: "directory", path: path10.join(TEMPLATES_DIR, "taro-todo") }
1348
1858
  }
1349
1859
  ];
1350
1860
 
@@ -1465,113 +1975,10 @@ function installAppMenu(ctx) {
1465
1975
  }
1466
1976
 
1467
1977
  // src/main/ipc/app.ts
1468
- import fs5 from "fs";
1469
-
1470
- // src/main/utils/ipc-registry.ts
1471
- import { ipcMain } from "electron";
1472
-
1473
- // src/main/utils/ipc-schema.ts
1474
- var IpcValidationError = class extends Error {
1475
- channel;
1476
- paths;
1477
- constructor(channel, issues) {
1478
- const paths = issues.map((i) => i.path.map((p) => String(p)).join(".") || "<root>");
1479
- super(`Invalid IPC arguments for ${channel}`);
1480
- this.name = "IpcValidationError";
1481
- this.channel = channel;
1482
- this.paths = paths;
1483
- }
1484
- };
1485
- function validate(channel, schema, args) {
1486
- const r = schema.safeParse(args);
1487
- if (!r.success) {
1488
- console.warn("[ipc] schema reject", channel, r.error.issues);
1489
- throw new IpcValidationError(channel, r.error.issues);
1490
- }
1491
- return r.data;
1492
- }
1493
-
1494
- // src/main/utils/ipc-registry.ts
1495
- var log2 = createLogger("ipc");
1496
- function reportListenerError(channel, err) {
1497
- if (err instanceof IpcValidationError) {
1498
- log2.warn(`schema reject on '${channel}' at [${err.paths.join(", ")}]`);
1499
- return;
1500
- }
1501
- if (err instanceof Error) {
1502
- log2.error(`listener threw on '${channel}': ${err.message}`, err.stack);
1503
- return;
1504
- }
1505
- log2.error(`listener threw on '${channel}'`, err);
1506
- }
1507
- function isThenable(v) {
1508
- return v != null && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
1509
- }
1510
- function summarizeSender(sender) {
1511
- if (sender.isDestroyed()) return "<destroyed>";
1512
- const url = sender.getURL();
1513
- return `id=${sender.id} ${url.slice(0, 120)}`;
1514
- }
1515
- var IpcRegistry = class {
1516
- constructor(policy) {
1517
- this.policy = policy;
1518
- }
1519
- policy;
1520
- registry = new DisposableRegistry();
1521
- handle(channel, fn) {
1522
- const policy = this.policy;
1523
- const guarded = policy ? async (event, ...args) => {
1524
- const sender = event.sender;
1525
- if (!policy(sender)) {
1526
- console.warn(
1527
- `[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
1528
- );
1529
- throw new Error(`IPC sender rejected for channel ${channel}`);
1530
- }
1531
- return fn(event, ...args);
1532
- } : fn;
1533
- ipcMain.handle(channel, guarded);
1534
- this.registry.add(() => ipcMain.removeHandler(channel));
1535
- return this;
1536
- }
1537
- on(channel, fn) {
1538
- const policy = this.policy;
1539
- const raw = fn;
1540
- const safeInvoke = (event, args) => {
1541
- try {
1542
- const ret = raw(event, ...args);
1543
- if (isThenable(ret)) {
1544
- Promise.resolve(ret).catch((err) => reportListenerError(channel, err));
1545
- }
1546
- } catch (err) {
1547
- reportListenerError(channel, err);
1548
- }
1549
- };
1550
- const guarded = policy ? (event, ...args) => {
1551
- const sender = event.sender;
1552
- if (!policy(sender)) {
1553
- console.warn(
1554
- `[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
1555
- );
1556
- return;
1557
- }
1558
- safeInvoke(event, args);
1559
- } : (event, ...args) => safeInvoke(event, args);
1560
- ipcMain.on(channel, guarded);
1561
- this.registry.add(() => {
1562
- ipcMain.removeListener(channel, guarded);
1563
- });
1564
- return this;
1565
- }
1566
- dispose() {
1567
- return this.registry.dispose();
1568
- }
1569
- };
1570
-
1571
- // src/main/ipc/app.ts
1978
+ import fs6 from "fs";
1572
1979
  function registerAppIpc(ctx) {
1573
1980
  return new IpcRegistry(ctx.senderPolicy).handle(AppChannel.GetPreloadPath, () => {
1574
- return `file://${fs5.realpathSync(ctx.preloadPath)}`;
1981
+ return `file://${fs6.realpathSync(ctx.preloadPath)}`;
1575
1982
  }).handle(AppChannel.GetBranding, async () => {
1576
1983
  if (ctx.brandingProvider) return ctx.brandingProvider();
1577
1984
  return { appName: ctx.appName };
@@ -1581,71 +1988,80 @@ function registerAppIpc(ctx) {
1581
1988
  }
1582
1989
 
1583
1990
  // src/shared/ipc-schemas.ts
1584
- import { z } from "zod";
1585
- var AbsolutePath = z.string().min(1).refine(
1991
+ import { z as z2 } from "zod";
1992
+ var AbsolutePath = z2.string().min(1).refine(
1586
1993
  (p) => p.startsWith("/") || /^[a-zA-Z]:\\/.test(p) || /^[a-zA-Z]:\//.test(p),
1587
1994
  "must be an absolute path"
1588
1995
  );
1589
- var PanelEvalSchema = z.tuple([z.string().min(1).max(5e4)]);
1590
- var ProjectsAddSchema = z.tuple([AbsolutePath]);
1591
- var ProjectOpenSchema = z.tuple([AbsolutePath]);
1592
- var CompileConfigShape = z.looseObject({
1593
- startPage: z.string().optional(),
1594
- scene: z.number().optional(),
1595
- queryParams: z.array(z.looseObject({ key: z.string(), value: z.string() })).optional()
1996
+ var PanelEvalSchema = z2.tuple([z2.string().min(1).max(5e4)]);
1997
+ var ProjectsAddSchema = z2.tuple([AbsolutePath]);
1998
+ var ProjectOpenSchema = z2.tuple([AbsolutePath]);
1999
+ var CompileConfigShape = z2.looseObject({
2000
+ startPage: z2.string().optional(),
2001
+ scene: z2.number().optional(),
2002
+ queryParams: z2.array(z2.looseObject({ key: z2.string(), value: z2.string() })).optional()
1596
2003
  });
1597
- var ProjectSaveCompileConfigSchema = z.tuple([
2004
+ var ProjectSaveCompileConfigSchema = z2.tuple([
1598
2005
  AbsolutePath,
1599
2006
  CompileConfigShape
1600
2007
  ]);
1601
- var PopoverShowSchema = z.tuple([z.looseObject({})]);
1602
- var SimWidth = z.number().int().min(100).max(2e3);
1603
- var SimulatorAttachSchema = z.tuple([
1604
- z.number().int().positive(),
2008
+ var PopoverShowSchema = z2.tuple([z2.looseObject({})]);
2009
+ var SimWidth = z2.number().int().min(100).max(2e3);
2010
+ var SimulatorAttachSchema = z2.tuple([
2011
+ z2.number().int().positive(),
1605
2012
  SimWidth
1606
2013
  ]);
1607
- var SimulatorResizeSchema = z.tuple([SimWidth]);
1608
- var SimulatorSetVisibleSchema = z.tuple([z.boolean(), SimWidth]);
1609
- var SimulatorCustomApiInvokeSchema = z.tuple([
1610
- z.string().min(1).max(256),
1611
- z.unknown()
2014
+ var SimulatorResizeSchema = z2.tuple([SimWidth]);
2015
+ var NonNegInt = z2.number().int().min(0).max(1e5);
2016
+ var ViewBoundsSchema = z2.tuple([
2017
+ z2.object({
2018
+ x: NonNegInt,
2019
+ y: NonNegInt,
2020
+ width: NonNegInt,
2021
+ height: NonNegInt
2022
+ })
2023
+ ]);
2024
+ var SimulatorSetVisibleSchema = z2.tuple([z2.boolean(), SimWidth]);
2025
+ var SimulatorCustomApiInvokeSchema = z2.tuple([
2026
+ z2.string().min(1).max(256),
2027
+ z2.unknown()
1612
2028
  ]);
1613
- var ToolbarInvokeSchema = z.tuple([z.string().min(1).max(256)]);
1614
- var ProjectGetPagesSchema = z.tuple([AbsolutePath]);
1615
- var ProjectGetCompileConfigSchema = z.tuple([AbsolutePath]);
1616
- var ProjectsRemoveSchema = z.tuple([AbsolutePath]);
1617
- var WorkbenchSettingsSaveSchema = z.tuple([
1618
- z.looseObject({
1619
- cdp: z.looseObject({
1620
- enabled: z.boolean(),
1621
- port: z.number().int().min(0).max(65535)
2029
+ var ToolbarInvokeSchema = z2.tuple([z2.string().min(1).max(256)]);
2030
+ var ProjectGetPagesSchema = z2.tuple([AbsolutePath]);
2031
+ var ProjectGetCompileConfigSchema = z2.tuple([AbsolutePath]);
2032
+ var ProjectsRemoveSchema = z2.tuple([AbsolutePath]);
2033
+ var WorkbenchSettingsSaveSchema = z2.tuple([
2034
+ z2.looseObject({
2035
+ cdp: z2.looseObject({
2036
+ enabled: z2.boolean(),
2037
+ port: z2.number().int().min(0).max(65535)
1622
2038
  }),
1623
- mcp: z.looseObject({
1624
- enabled: z.boolean(),
1625
- port: z.number().int().min(0).max(65535)
2039
+ mcp: z2.looseObject({
2040
+ enabled: z2.boolean(),
2041
+ port: z2.number().int().min(0).max(65535)
1626
2042
  }),
1627
- compile: z.looseObject({
1628
- watch: z.boolean()
2043
+ compile: z2.looseObject({
2044
+ watch: z2.boolean()
1629
2045
  }),
1630
- theme: z.enum(["system", "light", "dark"]),
1631
- lastCreateBaseDir: z.union([z.string(), z.null()])
2046
+ theme: z2.enum(["system", "light", "dark"]),
2047
+ lastCreateBaseDir: z2.union([z2.string(), z2.null()])
1632
2048
  })
1633
2049
  ]);
1634
- var WorkbenchSettingsSetThemeSchema = z.tuple([
1635
- z.enum(["system", "light", "dark"])
2050
+ var WorkbenchSettingsSetThemeSchema = z2.tuple([
2051
+ z2.enum(["system", "light", "dark"])
1636
2052
  ]);
1637
- var WorkbenchSettingsSetVisibleSchema = z.tuple([z.boolean()]);
1638
- var SettingsSetVisibleSchema = z.tuple([z.boolean()]);
1639
- var SettingsConfigChangedSchema = z.tuple([CompileConfigShape]);
1640
- var PopoverRelaunchSchema = z.tuple([CompileConfigShape]);
1641
- var SettingsProjectSettingsChangedSchema = z.tuple([
1642
- z.looseObject({
1643
- uploadWithSourceMap: z.boolean().optional()
2053
+ var WorkbenchSettingsSetVisibleSchema = z2.tuple([z2.boolean()]);
2054
+ var SettingsSetVisibleSchema = z2.tuple([z2.boolean()]);
2055
+ var SettingsConfigChangedSchema = z2.tuple([CompileConfigShape]);
2056
+ var PopoverRelaunchSchema = z2.tuple([CompileConfigShape]);
2057
+ var SettingsProjectSettingsChangedSchema = z2.tuple([
2058
+ z2.looseObject({
2059
+ uploadWithSourceMap: z2.boolean().optional()
1644
2060
  })
1645
2061
  ]);
1646
- var PanelSelectSchema = z.tuple([z.string().min(1).max(200)]);
1647
- var ProjectCaptureThumbnailSchema = z.tuple([AbsolutePath]);
1648
- var ProjectGetThumbnailSchema = z.tuple([AbsolutePath]);
2062
+ var PanelSelectSchema = z2.tuple([z2.string().min(1).max(200)]);
2063
+ var ProjectCaptureThumbnailSchema = z2.tuple([AbsolutePath]);
2064
+ var ProjectGetThumbnailSchema = z2.tuple([AbsolutePath]);
1649
2065
 
1650
2066
  // src/main/ipc/simulator.ts
1651
2067
  function registerSimulatorIpc(ctx) {
@@ -1718,11 +2134,11 @@ var popoverModule = {
1718
2134
 
1719
2135
  // src/main/ipc/projects.ts
1720
2136
  import { app as app8, dialog } from "electron";
1721
- import path11 from "path";
2137
+ import path12 from "path";
1722
2138
 
1723
2139
  // src/main/services/projects/create-project-service.ts
1724
- import fs6 from "node:fs";
1725
- import path10 from "node:path";
2140
+ import fs7 from "node:fs";
2141
+ import path11 from "node:path";
1726
2142
  var DEFAULT_TEMPLATE_ID = "blank";
1727
2143
  async function createProject(input, ctx) {
1728
2144
  const name = (input.name ?? "").trim();
@@ -1733,12 +2149,12 @@ async function createProject(input, ctx) {
1733
2149
  if (!target || typeof target !== "string") {
1734
2150
  throw new Error("Project path is required");
1735
2151
  }
1736
- if (fs6.existsSync(target)) {
1737
- const stat = fs6.statSync(target);
2152
+ if (fs7.existsSync(target)) {
2153
+ const stat = fs7.statSync(target);
1738
2154
  if (!stat.isDirectory()) {
1739
2155
  throw new Error(`Project path exists and is not a directory: ${target}`);
1740
2156
  }
1741
- const entries = fs6.readdirSync(target);
2157
+ const entries = fs7.readdirSync(target);
1742
2158
  if (entries.length > 0) {
1743
2159
  throw new Error(
1744
2160
  `Project path is not empty (refusing to overwrite): ${target}`
@@ -1755,31 +2171,31 @@ async function createProject(input, ctx) {
1755
2171
  `Template '${templateId}' has neither a source directory nor a generate function`
1756
2172
  );
1757
2173
  }
1758
- fs6.mkdirSync(target, { recursive: true });
2174
+ fs7.mkdirSync(target, { recursive: true });
1759
2175
  if (template.generate) {
1760
2176
  await template.generate(target, { name });
1761
2177
  } else if (template.source) {
1762
- if (!fs6.existsSync(template.source.path)) {
2178
+ if (!fs7.existsSync(template.source.path)) {
1763
2179
  throw new Error(
1764
2180
  `Template source missing on disk: ${template.source.path}`
1765
2181
  );
1766
2182
  }
1767
- fs6.cpSync(template.source.path, target, {
2183
+ fs7.cpSync(template.source.path, target, {
1768
2184
  recursive: true,
1769
2185
  force: true
1770
2186
  });
1771
2187
  }
1772
- const cfgPath = path10.join(target, "project.config.json");
2188
+ const cfgPath = path11.join(target, "project.config.json");
1773
2189
  let cfg = {};
1774
- if (fs6.existsSync(cfgPath)) {
2190
+ if (fs7.existsSync(cfgPath)) {
1775
2191
  try {
1776
- cfg = JSON.parse(fs6.readFileSync(cfgPath, "utf-8"));
2192
+ cfg = JSON.parse(fs7.readFileSync(cfgPath, "utf-8"));
1777
2193
  } catch {
1778
2194
  cfg = {};
1779
2195
  }
1780
2196
  }
1781
2197
  cfg.projectname = name;
1782
- fs6.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
2198
+ fs7.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
1783
2199
  const created = await ctx.projectsProvider.addProject(target);
1784
2200
  return created;
1785
2201
  }
@@ -1855,7 +2271,7 @@ function registerProjectsIpc(ctx) {
1855
2271
  }
1856
2272
  try {
1857
2273
  const settings = loadWorkbenchSettings();
1858
- const newBase = path11.dirname(input.path);
2274
+ const newBase = path12.dirname(input.path);
1859
2275
  if (newBase && settings.lastCreateBaseDir !== newBase) {
1860
2276
  saveWorkbenchSettings({ ...settings, lastCreateBaseDir: newBase });
1861
2277
  }
@@ -2049,6 +2465,18 @@ function registerToolbarIpc(ctx) {
2049
2465
  });
2050
2466
  }
2051
2467
 
2468
+ // src/main/ipc/views.ts
2469
+ function registerViewsIpc(ctx) {
2470
+ return new IpcRegistry(ctx.senderPolicy).handle(ViewChannel.SimulatorDevtoolsBounds, (_event, ...args) => {
2471
+ const [bounds] = validate(
2472
+ ViewChannel.SimulatorDevtoolsBounds,
2473
+ ViewBoundsSchema,
2474
+ args
2475
+ );
2476
+ ctx.views.setSimulatorDevtoolsBounds(bounds);
2477
+ });
2478
+ }
2479
+
2052
2480
  // src/main/ipc/simulator-module.ts
2053
2481
  var simulatorModule = {
2054
2482
  setup: (ctx) => {
@@ -2056,6 +2484,7 @@ var simulatorModule = {
2056
2484
  reg.add(registerSimulatorIpc(ctx));
2057
2485
  reg.add(registerPanelsIpc(ctx));
2058
2486
  reg.add(registerToolbarIpc(ctx));
2487
+ reg.add(registerViewsIpc(ctx));
2059
2488
  return reg;
2060
2489
  }
2061
2490
  };
@@ -2749,7 +3178,7 @@ function getClient(kind) {
2749
3178
  }
2750
3179
 
2751
3180
  // src/main/services/mcp/tool-registry.ts
2752
- import { z as z2 } from "zod";
3181
+ import { z as z3 } from "zod";
2753
3182
  function registerCommonTargetTools(server, kind) {
2754
3183
  const descriptions = kind === "simulator" ? {
2755
3184
  screenshot: "Take a screenshot of the simulator webview",
@@ -2770,9 +3199,9 @@ function registerCommonTargetTools(server, kind) {
2770
3199
  return { content: [{ type: "image", data, mimeType: "image/png" }] };
2771
3200
  });
2772
3201
  server.tool(`${kind}_console_logs`, descriptions.consoleLogs, {
2773
- limit: z2.number().optional().default(50).describe("Maximum number of log entries"),
2774
- level: z2.enum(["error", "warning", "warn", "info", "log", "debug"]).optional().describe("Only return entries with this level"),
2775
- sinceTimestamp: z2.string().optional().describe("ISO timestamp; only return entries at or after this time")
3202
+ limit: z3.number().optional().default(50).describe("Maximum number of log entries"),
3203
+ level: z3.enum(["error", "warning", "warn", "info", "log", "debug"]).optional().describe("Only return entries with this level"),
3204
+ sinceTimestamp: z3.string().optional().describe("ISO timestamp; only return entries at or after this time")
2776
3205
  }, async ({ limit, level, sinceTimestamp }) => {
2777
3206
  getClient(kind);
2778
3207
  let entries = getTargetState(kind).consoleLogs;
@@ -2787,7 +3216,7 @@ function registerCommonTargetTools(server, kind) {
2787
3216
  });
2788
3217
  if (kind !== "workbench") {
2789
3218
  server.tool(`${kind}_evaluate`, descriptions.evaluate, {
2790
- expression: z2.string().describe("JavaScript expression to evaluate")
3219
+ expression: z3.string().describe("JavaScript expression to evaluate")
2791
3220
  }, async ({ expression }) => {
2792
3221
  const c = getClient(kind);
2793
3222
  const result = await c.Runtime.evaluate({ expression, returnByValue: true, awaitPromise: true });
@@ -2800,15 +3229,15 @@ ${JSON.stringify(result.exceptionDetails, null, 2)}` }], isError: true };
2800
3229
  });
2801
3230
  }
2802
3231
  server.tool(`${kind}_get_dom`, descriptions.getDom, {
2803
- depth: z2.number().optional().default(3).describe("Maximum depth")
3232
+ depth: z3.number().optional().default(3).describe("Maximum depth")
2804
3233
  }, async ({ depth }) => {
2805
3234
  const c = getClient(kind);
2806
3235
  const { root } = await c.DOM.getDocument({ depth });
2807
3236
  return { content: [{ type: "text", text: JSON.stringify(root, null, 2) }] };
2808
3237
  });
2809
3238
  server.tool(`${kind}_network_log`, descriptions.networkLog, {
2810
- limit: z2.number().optional().default(20).describe("Maximum number of entries"),
2811
- minStatus: z2.number().optional().describe("Only return entries with status >= minStatus (status 0 = failed before response is included when minStatus >= 400)")
3239
+ limit: z3.number().optional().default(20).describe("Maximum number of entries"),
3240
+ minStatus: z3.number().optional().describe("Only return entries with status >= minStatus (status 0 = failed before response is included when minStatus >= 400)")
2812
3241
  }, async ({ limit, minStatus }) => {
2813
3242
  getClient(kind);
2814
3243
  let entries = getTargetState(kind).networkRequests;
@@ -3051,11 +3480,11 @@ function registerContextTools(server) {
3051
3480
  }
3052
3481
 
3053
3482
  // src/main/services/mcp/tools/simulator-tools.ts
3054
- import { z as z3 } from "zod";
3483
+ import { z as z4 } from "zod";
3055
3484
  function registerSimulatorTools(server) {
3056
3485
  server.tool("simulator_navigate", "Navigate the simulator to a URL, or reload the current page", {
3057
- url: z3.string().optional().describe("URL to navigate to"),
3058
- reload: z3.boolean().optional().describe("If true, reload current page instead of navigating")
3486
+ url: z4.string().optional().describe("URL to navigate to"),
3487
+ reload: z4.boolean().optional().describe("If true, reload current page instead of navigating")
3059
3488
  }, async ({ url, reload }) => {
3060
3489
  const c = getClient("simulator");
3061
3490
  if (reload) {
@@ -3070,15 +3499,15 @@ function registerSimulatorTools(server) {
3070
3499
  return { content: [{ type: "text", text: "Error: either url or reload is required" }], isError: true };
3071
3500
  });
3072
3501
  server.tool("simulator_input", "Dispatch input to the simulator. `tap_coord` requires x and y. `tap_selector` requires selector and optional nth. `type` requires selector and text. `scroll` requires x, y, deltaX, and deltaY. `key` is best-effort for common keys such as Enter, Escape, Tab, Arrow* and Space.", {
3073
- action: z3.enum(["tap_coord", "tap_selector", "type", "scroll", "key"]).describe("Input action to dispatch"),
3074
- x: z3.number().optional().describe("Viewport x coordinate for `tap_coord` or `scroll`"),
3075
- y: z3.number().optional().describe("Viewport y coordinate for `tap_coord` or `scroll`"),
3076
- selector: z3.string().optional().describe("CSS selector for `tap_selector` or `type`"),
3077
- nth: z3.number().int().optional().describe("Zero-based index when selector matches multiple elements; defaults to 0"),
3078
- text: z3.string().optional().describe("Text to insert for `type`"),
3079
- key: z3.string().optional().describe("Best-effort keyboard key name for `key`; common keys such as Enter, Escape, Tab, Arrow* and Space work best"),
3080
- deltaX: z3.number().optional().describe("Horizontal wheel delta for `scroll`"),
3081
- deltaY: z3.number().optional().describe("Vertical wheel delta for `scroll`")
3502
+ action: z4.enum(["tap_coord", "tap_selector", "type", "scroll", "key"]).describe("Input action to dispatch"),
3503
+ x: z4.number().optional().describe("Viewport x coordinate for `tap_coord` or `scroll`"),
3504
+ y: z4.number().optional().describe("Viewport y coordinate for `tap_coord` or `scroll`"),
3505
+ selector: z4.string().optional().describe("CSS selector for `tap_selector` or `type`"),
3506
+ nth: z4.number().int().optional().describe("Zero-based index when selector matches multiple elements; defaults to 0"),
3507
+ text: z4.string().optional().describe("Text to insert for `type`"),
3508
+ key: z4.string().optional().describe("Best-effort keyboard key name for `key`; common keys such as Enter, Escape, Tab, Arrow* and Space work best"),
3509
+ deltaX: z4.number().optional().describe("Horizontal wheel delta for `scroll`"),
3510
+ deltaY: z4.number().optional().describe("Vertical wheel delta for `scroll`")
3082
3511
  }, async ({ action, x, y, selector, nth, text, key, deltaX, deltaY }) => {
3083
3512
  const c = getClient("simulator");
3084
3513
  const err = (msg) => ({ content: [{ type: "text", text: `Error: ${msg}` }], isError: true });
@@ -3532,6 +3961,527 @@ async function clearElementInspection() {
3532
3961
  }
3533
3962
  }
3534
3963
 
3964
+ // src/main/services/simulator-temp-files/store.ts
3965
+ function registerTempFile(store, path18, mime, bytes) {
3966
+ const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
3967
+ store.delete(path18);
3968
+ store.set(path18, { bytes: buf, mime });
3969
+ }
3970
+ function revokeTempFile(store, path18) {
3971
+ store.delete(path18);
3972
+ }
3973
+ function revokeAllTempFiles(store) {
3974
+ store.clear();
3975
+ }
3976
+
3977
+ // src/shared/vpath.ts
3978
+ import os from "node:os";
3979
+ import path13 from "node:path";
3980
+ var DIFILE_PREFIX = "difile://";
3981
+ function sandboxBase() {
3982
+ const env = typeof process !== "undefined" && process.env && process.env.DIMINA_HOME || "";
3983
+ if (env) {
3984
+ return path13.join(env, "files");
3985
+ }
3986
+ return path13.join(os.homedir(), ".dimina", "files");
3987
+ }
3988
+ function resolveVPath(url) {
3989
+ if (typeof url !== "string") return null;
3990
+ if (!url.startsWith(DIFILE_PREFIX)) return null;
3991
+ const raw = url.slice(DIFILE_PREFIX.length);
3992
+ if (raw.length === 0) return null;
3993
+ let decoded;
3994
+ try {
3995
+ decoded = decodeURIComponent(raw);
3996
+ } catch {
3997
+ return null;
3998
+ }
3999
+ if (decoded.includes("\0")) return null;
4000
+ decoded = decoded.replace(/^[/\\]+/, "");
4001
+ if (decoded.length === 0) return null;
4002
+ const segments = decoded.split(/[/\\]+/);
4003
+ if (segments.some((s) => s === ".." || s === ".")) return null;
4004
+ let kind;
4005
+ let writable;
4006
+ if (decoded.startsWith("_tmp/")) {
4007
+ kind = "tmp";
4008
+ writable = false;
4009
+ } else if (decoded.startsWith("_store/")) {
4010
+ kind = "store";
4011
+ writable = false;
4012
+ } else {
4013
+ kind = "usr";
4014
+ writable = true;
4015
+ }
4016
+ if (kind === "tmp") {
4017
+ return { kind, writable };
4018
+ }
4019
+ const base = sandboxBase();
4020
+ const joined = path13.join(base, decoded);
4021
+ const normalized = path13.normalize(joined);
4022
+ if (normalized !== base && !normalized.startsWith(base + path13.sep)) return null;
4023
+ return { kind, writable, realPath: normalized };
4024
+ }
4025
+
4026
+ // src/main/services/simulator-temp-files/disk.ts
4027
+ import fs8 from "node:fs/promises";
4028
+ import path14 from "node:path";
4029
+ var EXT_MIME = {
4030
+ ".txt": "text/plain",
4031
+ ".log": "text/plain",
4032
+ ".md": "text/markdown",
4033
+ ".html": "text/html",
4034
+ ".htm": "text/html",
4035
+ ".css": "text/css",
4036
+ ".csv": "text/csv",
4037
+ ".json": "application/json",
4038
+ ".xml": "application/xml",
4039
+ ".js": "application/javascript",
4040
+ ".mjs": "application/javascript",
4041
+ ".svg": "image/svg+xml",
4042
+ ".png": "image/png",
4043
+ ".jpg": "image/jpeg",
4044
+ ".jpeg": "image/jpeg",
4045
+ ".gif": "image/gif",
4046
+ ".webp": "image/webp",
4047
+ ".bmp": "image/bmp",
4048
+ ".ico": "image/x-icon",
4049
+ ".mp4": "video/mp4",
4050
+ ".m4v": "video/mp4",
4051
+ ".webm": "video/webm",
4052
+ ".mov": "video/quicktime",
4053
+ ".mp3": "audio/mpeg",
4054
+ ".wav": "audio/wav",
4055
+ ".ogg": "audio/ogg",
4056
+ ".pdf": "application/pdf",
4057
+ ".zip": "application/zip"
4058
+ };
4059
+ function sniffMime(head) {
4060
+ 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) {
4061
+ return "image/png";
4062
+ }
4063
+ if (head.length >= 3 && head[0] === 255 && head[1] === 216 && head[2] === 255) {
4064
+ return "image/jpeg";
4065
+ }
4066
+ 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) {
4067
+ return "image/gif";
4068
+ }
4069
+ 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) {
4070
+ return "image/webp";
4071
+ }
4072
+ if (head.length >= 12 && head[4] === 102 && head[5] === 116 && head[6] === 121 && head[7] === 112) {
4073
+ return "video/mp4";
4074
+ }
4075
+ if (head.length >= 4 && head[0] === 37 && head[1] === 80 && head[2] === 68 && head[3] === 70) {
4076
+ return "application/pdf";
4077
+ }
4078
+ if (head.length >= 2 && head[0] === 66 && head[1] === 77) {
4079
+ return "image/bmp";
4080
+ }
4081
+ if (head.length >= 4 && head[0] === 80 && head[1] === 75 && head[2] === 3 && head[3] === 4) {
4082
+ return "application/zip";
4083
+ }
4084
+ if (head.length >= 3 && head[0] === 73 && head[1] === 68 && head[2] === 51) {
4085
+ return "audio/mpeg";
4086
+ }
4087
+ if (head.length >= 4 && head[0] === 79 && head[1] === 103 && head[2] === 103 && head[3] === 83) {
4088
+ return "audio/ogg";
4089
+ }
4090
+ return null;
4091
+ }
4092
+ function extMime(realPath) {
4093
+ const ext = path14.extname(realPath).toLowerCase();
4094
+ if (!ext) return null;
4095
+ return EXT_MIME[ext] ?? null;
4096
+ }
4097
+ function detectMime(realPath, head) {
4098
+ const sniffed = sniffMime(head);
4099
+ if (sniffed) return sniffed;
4100
+ const byExt = extMime(realPath);
4101
+ if (byExt) return byExt;
4102
+ return "application/octet-stream";
4103
+ }
4104
+ function etagOf(mtimeMs, size) {
4105
+ return `W/"${Math.floor(mtimeMs)}-${size}"`;
4106
+ }
4107
+ async function readDiskFile(realPath, opts) {
4108
+ const handle = await fs8.open(realPath, "r");
4109
+ try {
4110
+ const st = await handle.stat();
4111
+ const totalSize = st.size;
4112
+ const mtimeMs = st.mtimeMs;
4113
+ const range = opts?.range;
4114
+ let sliceStart = 0;
4115
+ let sliceLen = totalSize;
4116
+ if (range) {
4117
+ const { start, end } = range;
4118
+ if (!Number.isFinite(start) || !Number.isFinite(end)) {
4119
+ throw new RangeError(`invalid range: ${start}-${end}`);
4120
+ }
4121
+ if (start < 0) throw new RangeError(`range start out of bounds: ${start}`);
4122
+ if (start > end) throw new RangeError(`range start > end: ${start} > ${end}`);
4123
+ if (start >= totalSize) {
4124
+ throw new RangeError(`range start beyond file size: ${start} >= ${totalSize}`);
4125
+ }
4126
+ const clampedEnd = Math.min(end, totalSize - 1);
4127
+ sliceStart = start;
4128
+ sliceLen = clampedEnd - start + 1;
4129
+ }
4130
+ const head = Buffer.alloc(Math.min(12, totalSize));
4131
+ if (head.length > 0) {
4132
+ await handle.read(head, 0, head.length, 0);
4133
+ }
4134
+ let bytes;
4135
+ if (!range) {
4136
+ bytes = Buffer.alloc(totalSize);
4137
+ if (totalSize > 0) {
4138
+ if (totalSize <= head.length) {
4139
+ head.copy(bytes, 0, 0, totalSize);
4140
+ } else {
4141
+ head.copy(bytes, 0, 0, head.length);
4142
+ await handle.read(bytes, head.length, totalSize - head.length, head.length);
4143
+ }
4144
+ }
4145
+ } else {
4146
+ bytes = Buffer.alloc(sliceLen);
4147
+ if (sliceLen > 0) {
4148
+ await handle.read(bytes, 0, sliceLen, sliceStart);
4149
+ }
4150
+ }
4151
+ return {
4152
+ bytes,
4153
+ mime: detectMime(realPath, head),
4154
+ etag: etagOf(mtimeMs, totalSize),
4155
+ totalSize
4156
+ };
4157
+ } finally {
4158
+ await handle.close();
4159
+ }
4160
+ }
4161
+ async function writeDiskFile(realPath, bytes) {
4162
+ const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
4163
+ await fs8.mkdir(path14.dirname(realPath), { recursive: true });
4164
+ await fs8.writeFile(realPath, buf);
4165
+ }
4166
+ async function readDiskDir(realPath) {
4167
+ const entries = await fs8.readdir(realPath);
4168
+ return entries;
4169
+ }
4170
+ async function statDiskFile(realPath) {
4171
+ const st = await fs8.stat(realPath);
4172
+ return {
4173
+ size: st.size,
4174
+ mtime: Math.floor(st.mtimeMs),
4175
+ mode: st.mode,
4176
+ isFile: st.isFile(),
4177
+ isDirectory: st.isDirectory()
4178
+ };
4179
+ }
4180
+
4181
+ // src/main/services/simulator-temp-files/request-handler.ts
4182
+ var CACHE_CONTROL = "public, max-age=31536000, immutable";
4183
+ function getHeader(headers, name) {
4184
+ if (!headers) return void 0;
4185
+ const lc = name.toLowerCase();
4186
+ for (const k of Object.keys(headers)) {
4187
+ if (k.toLowerCase() === lc) return headers[k];
4188
+ }
4189
+ return void 0;
4190
+ }
4191
+ function etagsMatch(a, b) {
4192
+ if (!a) return false;
4193
+ const stripped = (s) => s.startsWith("W/") ? s.slice(2) : s;
4194
+ return stripped(a.trim()) === stripped(b);
4195
+ }
4196
+ function parseRange(header, totalSize) {
4197
+ if (!header) return null;
4198
+ const m = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
4199
+ if (!m) return null;
4200
+ const startStr = m[1];
4201
+ const endStr = m[2];
4202
+ if (startStr === "" && endStr === "") return null;
4203
+ if (startStr === "") {
4204
+ const n = Number(endStr);
4205
+ if (!Number.isFinite(n) || n <= 0) return null;
4206
+ const start2 = Math.max(0, totalSize - n);
4207
+ return { start: start2, end: totalSize - 1 };
4208
+ }
4209
+ const start = Number(startStr);
4210
+ if (!Number.isFinite(start) || start < 0) return null;
4211
+ const end = endStr === "" ? totalSize - 1 : Number(endStr);
4212
+ if (!Number.isFinite(end)) return null;
4213
+ return { start, end };
4214
+ }
4215
+ function notFound() {
4216
+ return new Response(null, { status: 404 });
4217
+ }
4218
+ function notModified(etag) {
4219
+ return new Response(null, {
4220
+ status: 304,
4221
+ headers: {
4222
+ ETag: etag,
4223
+ "Cache-Control": CACHE_CONTROL,
4224
+ "Access-Control-Allow-Origin": "*"
4225
+ }
4226
+ });
4227
+ }
4228
+ function bufferToBody(bytes) {
4229
+ const out = new ArrayBuffer(bytes.byteLength);
4230
+ new Uint8Array(out).set(bytes);
4231
+ return out;
4232
+ }
4233
+ function tempBody(bytes, mime) {
4234
+ return new Response(bufferToBody(bytes), {
4235
+ status: 200,
4236
+ headers: {
4237
+ "Content-Type": mime,
4238
+ "Cache-Control": CACHE_CONTROL,
4239
+ "Access-Control-Allow-Origin": "*"
4240
+ }
4241
+ });
4242
+ }
4243
+ async function handleDifileRequest(ctx, req) {
4244
+ const v = resolveVPath(req.url);
4245
+ if (!v) return notFound();
4246
+ if (v.kind === "tmp") {
4247
+ const record = ctx.tempStore.get(req.url);
4248
+ if (!record) return notFound();
4249
+ return tempBody(record.bytes, record.mime || "application/octet-stream");
4250
+ }
4251
+ if (!v.realPath) return notFound();
4252
+ try {
4253
+ const probe = await readDiskFile(v.realPath);
4254
+ const etag = probe.etag;
4255
+ const totalSize = probe.totalSize;
4256
+ const mime = probe.mime;
4257
+ const ifNoneMatch = getHeader(req.headers, "If-None-Match");
4258
+ if (etagsMatch(ifNoneMatch, etag)) {
4259
+ return notModified(etag);
4260
+ }
4261
+ const rangeHeader = getHeader(req.headers, "Range");
4262
+ const range = parseRange(rangeHeader, totalSize);
4263
+ if (range) {
4264
+ if (range.start < 0 || range.start >= totalSize || range.end < range.start) {
4265
+ return new Response(null, {
4266
+ status: 416,
4267
+ headers: {
4268
+ "Content-Range": `bytes */${totalSize}`,
4269
+ "Access-Control-Allow-Origin": "*"
4270
+ }
4271
+ });
4272
+ }
4273
+ const clampedEnd = Math.min(range.end, totalSize - 1);
4274
+ const sliced = await readDiskFile(v.realPath, { range: { start: range.start, end: clampedEnd } });
4275
+ const { bytes } = sliced;
4276
+ const lastByte = range.start + bytes.length - 1;
4277
+ return new Response(bufferToBody(bytes), {
4278
+ status: 206,
4279
+ headers: {
4280
+ "Content-Type": mime,
4281
+ "Content-Length": String(bytes.length),
4282
+ "Content-Range": `bytes ${range.start}-${lastByte}/${totalSize}`,
4283
+ "Cache-Control": CACHE_CONTROL,
4284
+ ETag: etag,
4285
+ "Access-Control-Allow-Origin": "*"
4286
+ }
4287
+ });
4288
+ }
4289
+ return new Response(bufferToBody(probe.bytes), {
4290
+ status: 200,
4291
+ headers: {
4292
+ "Content-Type": mime,
4293
+ "Cache-Control": CACHE_CONTROL,
4294
+ ETag: etag,
4295
+ "Access-Control-Allow-Origin": "*"
4296
+ }
4297
+ });
4298
+ } catch {
4299
+ return notFound();
4300
+ }
4301
+ }
4302
+
4303
+ // src/main/services/simulator-temp-files/fs-channels.ts
4304
+ import fs9 from "node:fs/promises";
4305
+ import path15 from "node:path";
4306
+ async function enforceSandbox(realPath) {
4307
+ if (typeof realPath !== "string" || realPath.length === 0) {
4308
+ throw new Error("sandbox: realPath must be a non-empty string");
4309
+ }
4310
+ const base = sandboxBase();
4311
+ const baseReal = await fs9.realpath(base).catch(() => base);
4312
+ const normalized = path15.normalize(realPath);
4313
+ if (normalized !== base && !normalized.startsWith(base + path15.sep)) {
4314
+ throw new Error("sandbox: realPath escapes the user-data base");
4315
+ }
4316
+ let probe = normalized;
4317
+ while (probe !== path15.parse(probe).root) {
4318
+ try {
4319
+ const resolved = await fs9.realpath(probe);
4320
+ if (resolved !== baseReal && !resolved.startsWith(baseReal + path15.sep)) {
4321
+ throw new Error("sandbox: realPath escapes the user-data base via symlink");
4322
+ }
4323
+ break;
4324
+ } catch (err) {
4325
+ if (err.code === "ENOENT") {
4326
+ probe = path15.dirname(probe);
4327
+ continue;
4328
+ }
4329
+ throw err;
4330
+ }
4331
+ }
4332
+ return normalized;
4333
+ }
4334
+ async function handleFsRead(req) {
4335
+ const safe = await enforceSandbox(req.realPath);
4336
+ const result = await readDiskFile(safe, req.range ? { range: req.range } : void 0);
4337
+ return {
4338
+ bytes: result.bytes,
4339
+ mime: result.mime,
4340
+ etag: result.etag,
4341
+ totalSize: result.totalSize
4342
+ };
4343
+ }
4344
+ async function handleFsWrite(req) {
4345
+ const safe = await enforceSandbox(req.realPath);
4346
+ await writeDiskFile(safe, req.bytes);
4347
+ return { ok: true };
4348
+ }
4349
+ async function handleFsStat(req) {
4350
+ const safe = await enforceSandbox(req.realPath);
4351
+ return statDiskFile(safe);
4352
+ }
4353
+ async function handleFsReaddir(req) {
4354
+ const safe = await enforceSandbox(req.realPath);
4355
+ return readDiskDir(safe);
4356
+ }
4357
+ async function handleFsUnlink(req) {
4358
+ const safe = await enforceSandbox(req.realPath);
4359
+ await fs9.unlink(safe);
4360
+ return { ok: true };
4361
+ }
4362
+ async function handleFsMkdir(req) {
4363
+ const safe = await enforceSandbox(req.realPath);
4364
+ await fs9.mkdir(safe, { recursive: !!req.recursive });
4365
+ return { ok: true };
4366
+ }
4367
+
4368
+ // src/main/services/simulator-temp-files/index.ts
4369
+ var MAX_STORE_ENTRIES = 200;
4370
+ var PENDING_TIMEOUT_MS = 500;
4371
+ function enforceStoreCap(store) {
4372
+ while (store.size > MAX_STORE_ENTRIES) {
4373
+ const next = store.keys().next();
4374
+ if (next.done) break;
4375
+ store.delete(next.value);
4376
+ }
4377
+ }
4378
+ function setupSimulatorTempFiles(simSession) {
4379
+ const store = /* @__PURE__ */ new Map();
4380
+ const pendingWaiters = /* @__PURE__ */ new Map();
4381
+ let disposed = false;
4382
+ function drainWaiters(path18) {
4383
+ const list = pendingWaiters.get(path18);
4384
+ if (!list) return;
4385
+ pendingWaiters.delete(path18);
4386
+ for (const fn of list) fn();
4387
+ }
4388
+ function drainAllWaiters() {
4389
+ const lists = Array.from(pendingWaiters.values());
4390
+ pendingWaiters.clear();
4391
+ for (const list of lists) for (const fn of list) fn();
4392
+ }
4393
+ const simulatorOnlyPolicy = (sender) => !sender.isDestroyed() && sender.session === simSession;
4394
+ const registry = new IpcRegistry(simulatorOnlyPolicy);
4395
+ registry.on("simulator:temp-file:write", (_event, payload) => {
4396
+ if (disposed) return;
4397
+ const { path: path18, mime, bytes } = payload;
4398
+ registerTempFile(store, path18, mime, bytes);
4399
+ enforceStoreCap(store);
4400
+ drainWaiters(path18);
4401
+ });
4402
+ registry.on("simulator:temp-file:revoke", (_event, payload) => {
4403
+ if (disposed) return;
4404
+ const { path: path18 } = payload;
4405
+ revokeTempFile(store, path18);
4406
+ });
4407
+ registry.on("simulator:temp-file:revoke-all", () => {
4408
+ if (disposed) return;
4409
+ revokeAllTempFiles(store);
4410
+ });
4411
+ try {
4412
+ simSession.protocol.unhandle("difile");
4413
+ } catch {
4414
+ }
4415
+ simSession.protocol.handle("difile", async (req) => {
4416
+ const url = req.url;
4417
+ const headers = {};
4418
+ try {
4419
+ req.headers.forEach((v, k) => {
4420
+ headers[k] = v;
4421
+ });
4422
+ } catch {
4423
+ }
4424
+ const ctx = { tempStore: store };
4425
+ let res = await handleDifileRequest(ctx, { url, headers });
4426
+ if (res.status === 404 && !disposed && url.startsWith("difile://_tmp/")) {
4427
+ await new Promise((resolve) => {
4428
+ let timer = null;
4429
+ const notify = () => {
4430
+ if (timer) clearTimeout(timer);
4431
+ resolve();
4432
+ };
4433
+ const list = pendingWaiters.get(url) ?? /* @__PURE__ */ new Set();
4434
+ list.add(notify);
4435
+ pendingWaiters.set(url, list);
4436
+ timer = setTimeout(() => {
4437
+ const cur = pendingWaiters.get(url);
4438
+ if (cur) {
4439
+ cur.delete(notify);
4440
+ if (cur.size === 0) pendingWaiters.delete(url);
4441
+ }
4442
+ resolve();
4443
+ }, PENDING_TIMEOUT_MS);
4444
+ });
4445
+ res = await handleDifileRequest(ctx, { url, headers });
4446
+ }
4447
+ return res;
4448
+ });
4449
+ registry.handle(
4450
+ "simulator:fs:read",
4451
+ (_event, payload) => handleFsRead(payload)
4452
+ );
4453
+ registry.handle(
4454
+ "simulator:fs:write",
4455
+ (_event, payload) => handleFsWrite(payload)
4456
+ );
4457
+ registry.handle(
4458
+ "simulator:fs:stat",
4459
+ (_event, payload) => handleFsStat(payload)
4460
+ );
4461
+ registry.handle(
4462
+ "simulator:fs:readdir",
4463
+ (_event, payload) => handleFsReaddir(payload)
4464
+ );
4465
+ registry.handle(
4466
+ "simulator:fs:unlink",
4467
+ (_event, payload) => handleFsUnlink(payload)
4468
+ );
4469
+ registry.handle(
4470
+ "simulator:fs:mkdir",
4471
+ (_event, payload) => handleFsMkdir(payload)
4472
+ );
4473
+ return toDisposable(async () => {
4474
+ disposed = true;
4475
+ drainAllWaiters();
4476
+ store.clear();
4477
+ try {
4478
+ simSession.protocol.unhandle("difile");
4479
+ } catch {
4480
+ }
4481
+ await registry.dispose();
4482
+ });
4483
+ }
4484
+
3535
4485
  // src/main/services/update/update-manager.ts
3536
4486
  import { app as app11, shell as shell3 } from "electron";
3537
4487
  var UpdateManager = class {
@@ -3540,6 +4490,16 @@ var UpdateManager = class {
3540
4490
  getCurrentVersion;
3541
4491
  latestUpdate = null;
3542
4492
  downloadedPath = null;
4493
+ /**
4494
+ * Whether this instance has already emitted `Available` once. The periodic
4495
+ * check fires forever (interval default 1h), and downstream UI (qdmp shell
4496
+ * renders a native toast / Notification) is not idempotent — every event
4497
+ * stacks. We dedupe at the source: at most one Available per instance
4498
+ * lifetime, regardless of how the upstream `info.version` shifts. The user
4499
+ * already knows "an update exists"; tracking minor version bumps mid-
4500
+ * session is not worth duplicating the toast.
4501
+ */
4502
+ hasNotified = false;
3543
4503
  timer = null;
3544
4504
  initialTimer = null;
3545
4505
  ipc;
@@ -3613,9 +4573,10 @@ var UpdateManager = class {
3613
4573
  }
3614
4574
  async checkAndNotify() {
3615
4575
  const result = await this.check();
3616
- if (result.hasUpdate && result.info) {
3617
- this.mainWindow.webContents.send(UpdateChannel.Available, result.info);
3618
- }
4576
+ if (!result.hasUpdate || !result.info) return;
4577
+ if (this.hasNotified) return;
4578
+ this.hasNotified = true;
4579
+ this.mainWindow.webContents.send(UpdateChannel.Available, result.info);
3619
4580
  }
3620
4581
  };
3621
4582
 
@@ -3690,7 +4651,7 @@ async function disposeContext(ctx) {
3690
4651
  function createConfiguredMainWindow(config, rendererDir2) {
3691
4652
  const mainWindow = createMainWindow({
3692
4653
  title: config.appName ?? "Dimina DevTools",
3693
- indexHtml: path12.join(rendererDir2, "entries/main/index.html"),
4654
+ indexHtml: path16.join(rendererDir2, "entries/main/index.html"),
3694
4655
  width: config.window?.width,
3695
4656
  height: config.window?.height,
3696
4657
  minWidth: config.window?.minWidth,
@@ -3788,7 +4749,7 @@ function enableDevRendererAutoReload(rendererDir2) {
3788
4749
  });
3789
4750
  }
3790
4751
  let reloadTimer = null;
3791
- const watcher = fs7.watch(rendererDir2, { recursive: true }, () => {
4752
+ const watcher = fs10.watch(rendererDir2, { recursive: true }, () => {
3792
4753
  if (reloadTimer) clearTimeout(reloadTimer);
3793
4754
  reloadTimer = setTimeout(() => {
3794
4755
  for (const win of BrowserWindow4.getAllWindows()) {
@@ -3807,7 +4768,12 @@ function enableDevRendererAutoReload(rendererDir2) {
3807
4768
  function createWorkbenchApp(config = {}) {
3808
4769
  let setupPromise = null;
3809
4770
  let appEventsRegistered = false;
4771
+ try {
4772
+ app13.setName(config.appName ?? "Dimina DevTools");
4773
+ } catch {
4774
+ }
3810
4775
  setupCdpPort();
4776
+ registerDifileScheme();
3811
4777
  async function setup() {
3812
4778
  if (setupPromise) return setupPromise;
3813
4779
  setupPromise = app13.whenReady().then(async () => {
@@ -3816,8 +4782,11 @@ function createWorkbenchApp(config = {}) {
3816
4782
  const mainWindow = createConfiguredMainWindow(config, rendererDir2);
3817
4783
  const context = createContext(config, mainWindow, rendererDir2);
3818
4784
  context.registry.add(registerAppIpc(context));
4785
+ context.registry.add(registerProjectFsIpc(context));
3819
4786
  context.registry.add(installThemeBackgroundSync());
3820
4787
  registerBuiltinModules(config, context);
4788
+ const simSession = session2.fromPartition("persist:simulator");
4789
+ context.registry.add(setupSimulatorTempFiles(simSession));
3821
4790
  installMenu(config, mainWindow, context);
3822
4791
  const hostIpc = new IpcRegistry(context.senderPolicy);
3823
4792
  context.registry.add(hostIpc);
@@ -3838,6 +4807,9 @@ function createWorkbenchApp(config = {}) {
3838
4807
  },
3839
4808
  dispose: () => disposeContext(context)
3840
4809
  };
4810
+ if (config.onBeforeOpenProject) {
4811
+ context.beforeOpenProject = (projectPath) => config.onBeforeOpenProject({ projectPath, instance });
4812
+ }
3841
4813
  if (config.onSetup) {
3842
4814
  await config.onSetup(instance);
3843
4815
  }
@@ -3864,8 +4836,8 @@ function createWorkbenchApp(config = {}) {
3864
4836
  // prefixes; this callback lets the storage panel filter the CDP
3865
4837
  // snapshot/event stream to the active appId.
3866
4838
  getActiveAppId: () => {
3867
- const session2 = context.workspace.getSession();
3868
- const appInfo = session2?.appInfo;
4839
+ const session3 = context.workspace.getSession();
4840
+ const appInfo = session3?.appInfo;
3869
4841
  return appInfo?.appId ?? null;
3870
4842
  }
3871
4843
  }));
@@ -3890,7 +4862,7 @@ function createWorkbenchApp(config = {}) {
3890
4862
 
3891
4863
  // src/main/windows/settings-window/create.ts
3892
4864
  import { BrowserWindow as BrowserWindow5 } from "electron";
3893
- import path13 from "path";
4865
+ import path17 from "path";
3894
4866
  async function createSettingsWindow(parent, rendererDir2) {
3895
4867
  const win = new BrowserWindow5({
3896
4868
  width: 420,
@@ -3908,7 +4880,7 @@ async function createSettingsWindow(parent, rendererDir2) {
3908
4880
  }
3909
4881
  });
3910
4882
  applyNavigationHardening(win.webContents, rendererDir2);
3911
- await win.loadFile(path13.join(rendererDir2, "entries/workbench-settings/index.html"));
4883
+ await win.loadFile(path17.join(rendererDir2, "entries/workbench-settings/index.html"));
3912
4884
  return win;
3913
4885
  }
3914
4886