@dimina-kit/devtools 0.3.1 → 0.3.2-dev.20260522085907

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 (190) hide show
  1. package/README.md +297 -133
  2. package/dist/main/api.d.ts +6 -2
  3. package/dist/main/api.js +5 -2
  4. package/dist/main/app/app.d.ts +1 -1
  5. package/dist/main/app/app.js +84 -36
  6. package/dist/main/app/bootstrap.js +2 -1
  7. package/dist/main/app/launch.d.ts +2 -2
  8. package/dist/main/app/launch.js +10 -8
  9. package/dist/main/index.bundle.js +1567 -477
  10. package/dist/main/ipc/app.d.ts +2 -1
  11. package/dist/main/ipc/app.js +5 -4
  12. package/dist/main/ipc/index.d.ts +5 -4
  13. package/dist/main/ipc/index.js +5 -4
  14. package/dist/main/ipc/panels.d.ts +2 -1
  15. package/dist/main/ipc/panels.js +16 -13
  16. package/dist/main/ipc/popover.d.ts +4 -1
  17. package/dist/main/ipc/popover.js +15 -5
  18. package/dist/main/ipc/projects.d.ts +6 -1
  19. package/dist/main/ipc/projects.js +127 -6
  20. package/dist/main/ipc/session.d.ts +4 -1
  21. package/dist/main/ipc/session.js +34 -6
  22. package/dist/main/ipc/settings.d.ts +4 -1
  23. package/dist/main/ipc/settings.js +51 -23
  24. package/dist/main/ipc/simulator-module.d.ts +8 -0
  25. package/dist/main/ipc/simulator-module.js +19 -0
  26. package/dist/main/ipc/simulator.d.ts +2 -1
  27. package/dist/main/ipc/simulator.js +23 -18
  28. package/dist/main/ipc/toolbar.d.ts +2 -1
  29. package/dist/main/ipc/toolbar.js +3 -2
  30. package/dist/main/menu/index.js +11 -0
  31. package/dist/main/services/automation/exec.d.ts +2 -0
  32. package/dist/main/services/automation/exec.js +7 -9
  33. package/dist/main/services/automation/handlers/app.js +37 -51
  34. package/dist/main/services/automation/handlers/element.js +27 -41
  35. package/dist/main/services/automation/index.js +88 -13
  36. package/dist/main/services/default-adapter.js +3 -3
  37. package/dist/main/services/mcp/index.d.ts +1 -0
  38. package/dist/main/services/mcp/index.js +1 -0
  39. package/dist/main/services/mcp/server.d.ts +2 -1
  40. package/dist/main/services/mcp/server.js +10 -0
  41. package/dist/main/services/mcp/status.d.ts +10 -0
  42. package/dist/main/services/mcp/status.js +14 -0
  43. package/dist/main/services/mcp/target-manager.js +2 -1
  44. package/dist/main/services/module.d.ts +18 -0
  45. package/dist/main/services/module.js +2 -0
  46. package/dist/main/services/notifications/renderer-notifier.d.ts +5 -3
  47. package/dist/main/services/notifications/renderer-notifier.js +3 -6
  48. package/dist/main/services/projects/builtin-templates.d.ts +3 -0
  49. package/dist/main/services/projects/builtin-templates.js +24 -0
  50. package/dist/main/services/projects/create-project-service.d.ts +7 -0
  51. package/dist/main/services/projects/create-project-service.js +84 -0
  52. package/dist/main/services/projects/index.d.ts +7 -0
  53. package/dist/main/services/projects/index.js +9 -0
  54. package/dist/main/services/projects/local-provider.d.ts +3 -0
  55. package/dist/main/services/projects/local-provider.js +25 -0
  56. package/dist/main/services/projects/project-repository.d.ts +1 -1
  57. package/dist/main/services/projects/project-repository.js +3 -0
  58. package/dist/main/services/projects/templates.d.ts +21 -0
  59. package/dist/main/services/projects/templates.js +39 -0
  60. package/dist/main/services/projects/thumbnail.d.ts +8 -0
  61. package/dist/main/services/projects/thumbnail.js +37 -0
  62. package/dist/main/services/projects/types.d.ts +118 -0
  63. package/dist/main/services/projects/types.js +19 -0
  64. package/dist/main/services/settings/index.d.ts +11 -0
  65. package/dist/main/services/settings/index.js +18 -2
  66. package/dist/main/services/simulator/custom-apis.d.ts +10 -0
  67. package/dist/main/services/simulator/custom-apis.js +26 -0
  68. package/dist/main/services/simulator-storage/index.d.ts +22 -1
  69. package/dist/main/services/simulator-storage/index.js +334 -97
  70. package/dist/main/services/update/update-manager.d.ts +16 -1
  71. package/dist/main/services/update/update-manager.js +18 -15
  72. package/dist/main/services/views/view-manager.d.ts +7 -7
  73. package/dist/main/services/views/view-manager.js +53 -29
  74. package/dist/main/services/window-service.d.ts +24 -0
  75. package/dist/main/services/window-service.js +26 -0
  76. package/dist/main/services/workbench-context.d.ts +49 -2
  77. package/dist/main/services/workbench-context.js +15 -2
  78. package/dist/main/services/workspace/workspace-service.d.ts +9 -6
  79. package/dist/main/services/workspace/workspace-service.js +71 -11
  80. package/dist/main/simulator-apis.d.ts +23 -0
  81. package/dist/main/simulator-apis.js +24 -0
  82. package/dist/main/utils/disposable.d.ts +13 -0
  83. package/dist/main/utils/disposable.js +60 -0
  84. package/dist/main/utils/ipc-registry.d.ts +39 -0
  85. package/dist/main/utils/ipc-registry.js +109 -0
  86. package/dist/main/utils/ipc-schema.d.ts +28 -0
  87. package/dist/main/utils/ipc-schema.js +38 -0
  88. package/dist/main/utils/paths.d.ts +6 -0
  89. package/dist/main/utils/paths.js +6 -0
  90. package/dist/main/utils/sender-policy.d.ts +25 -0
  91. package/dist/main/utils/sender-policy.js +44 -0
  92. package/dist/main/utils/theme.d.ts +24 -0
  93. package/dist/main/utils/theme.js +38 -1
  94. package/dist/main/windows/main-window/create.d.ts +6 -0
  95. package/dist/main/windows/main-window/create.js +66 -10
  96. package/dist/main/windows/main-window/events.d.ts +2 -1
  97. package/dist/main/windows/main-window/events.js +25 -2
  98. package/dist/main/windows/navigation-hardening.d.ts +24 -0
  99. package/dist/main/windows/navigation-hardening.js +55 -0
  100. package/dist/main/windows/settings-window/create.js +11 -1
  101. package/dist/preload/index.d.ts +22 -0
  102. package/dist/preload/index.js +19 -0
  103. package/dist/preload/instrumentation/app-data.d.ts +32 -2
  104. package/dist/preload/instrumentation/app-data.js +253 -79
  105. package/dist/preload/instrumentation/index.d.ts +2 -2
  106. package/dist/preload/instrumentation/index.js +2 -2
  107. package/dist/preload/instrumentation/wxml.d.ts +16 -4
  108. package/dist/preload/instrumentation/wxml.js +188 -178
  109. package/dist/preload/miniapp-snapshot/host.d.ts +12 -0
  110. package/dist/preload/miniapp-snapshot/host.js +117 -0
  111. package/dist/preload/miniapp-snapshot/types.d.ts +62 -0
  112. package/dist/preload/miniapp-snapshot/types.js +15 -0
  113. package/dist/preload/runtime/bridge.js +5 -24
  114. package/dist/preload/runtime/custom-apis.d.ts +6 -0
  115. package/dist/preload/runtime/custom-apis.js +43 -0
  116. package/dist/preload/runtime/host.d.ts +1 -0
  117. package/dist/preload/runtime/host.js +13 -0
  118. package/dist/preload/shared/expose.d.ts +15 -0
  119. package/dist/preload/shared/expose.js +33 -0
  120. package/dist/preload/shared/page-iframe.d.ts +3 -0
  121. package/dist/preload/shared/page-iframe.js +6 -0
  122. package/dist/preload/shared/types.d.ts +2 -0
  123. package/dist/preload/windows/main.cjs +47 -0
  124. package/dist/preload/windows/main.cjs.map +7 -0
  125. package/dist/preload/windows/simulator.js +501 -272
  126. package/dist/renderer/assets/constants-DI382mTP.js +2 -0
  127. package/dist/renderer/assets/index-D8VFYBh-.js +46 -0
  128. package/dist/renderer/assets/{input-DxfYnzX_.js → input-CoWcfa8u.js} +2 -2
  129. package/dist/renderer/assets/ipc-transport-Cd2xPrsa.js +9 -0
  130. package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +1 -0
  131. package/dist/renderer/assets/popover-Nq5gT9DO.js +2 -0
  132. package/dist/renderer/assets/select-By5KH-UG.js +2 -0
  133. package/dist/renderer/assets/{settings-DteGraEU.js → settings-Dj_N70qR.js} +2 -2
  134. package/dist/renderer/assets/settings-api-Do0_Smq1.js +2 -0
  135. package/dist/renderer/assets/workbenchSettings-CazSLGYt.js +8 -0
  136. package/dist/renderer/entries/main/index.html +13 -5
  137. package/dist/renderer/entries/popover/index.html +8 -5
  138. package/dist/renderer/entries/settings/index.html +7 -5
  139. package/dist/renderer/entries/workbench-settings/index.html +7 -4
  140. package/dist/shared/ipc-channels.d.ts +46 -13
  141. package/dist/shared/ipc-channels.js +60 -14
  142. package/dist/shared/ipc-schemas.d.ts +118 -0
  143. package/dist/shared/ipc-schemas.js +133 -0
  144. package/dist/shared/simulator-route.d.ts +72 -0
  145. package/dist/shared/simulator-route.js +169 -0
  146. package/dist/shared/types.d.ts +82 -8
  147. package/dist/simulator/assets/simulator-DJCozrU5.js +9 -0
  148. package/dist/simulator/simulator.html +6 -43
  149. package/package.json +18 -29
  150. package/templates/blank/app.js +6 -0
  151. package/templates/blank/app.json +11 -0
  152. package/templates/blank/app.wxss +5 -0
  153. package/templates/blank/pages/index/index.js +6 -0
  154. package/templates/blank/pages/index/index.json +3 -0
  155. package/templates/blank/pages/index/index.wxml +3 -0
  156. package/templates/blank/pages/index/index.wxss +11 -0
  157. package/templates/blank/project.config.json +13 -0
  158. package/templates/blank/sitemap.json +9 -0
  159. package/templates/taro-todo/app.js +1 -0
  160. package/templates/taro-todo/app.json +1 -0
  161. package/templates/taro-todo/app.wxss +1 -0
  162. package/templates/taro-todo/base.wxml +851 -0
  163. package/templates/taro-todo/common.js +1 -0
  164. package/templates/taro-todo/comp.js +1 -0
  165. package/templates/taro-todo/comp.json +1 -0
  166. package/templates/taro-todo/comp.wxml +2 -0
  167. package/templates/taro-todo/custom-wrapper.js +1 -0
  168. package/templates/taro-todo/custom-wrapper.json +1 -0
  169. package/templates/taro-todo/custom-wrapper.wxml +4 -0
  170. package/templates/taro-todo/pages/index/index.js +2 -0
  171. package/templates/taro-todo/pages/index/index.json +1 -0
  172. package/templates/taro-todo/pages/index/index.wxml +2 -0
  173. package/templates/taro-todo/pages/index/index.wxss +1 -0
  174. package/templates/taro-todo/project.config.json +44 -0
  175. package/templates/taro-todo/runtime.js +1 -0
  176. package/templates/taro-todo/taro.js +2 -0
  177. package/templates/taro-todo/utils.wxs +39 -0
  178. package/templates/taro-todo/vendors.js +2 -0
  179. package/dist/preload/instrumentation/disposable.d.ts +0 -11
  180. package/dist/preload/instrumentation/disposable.js +0 -15
  181. package/dist/preload/windows/main.d.ts +0 -6
  182. package/dist/preload/windows/main.js +0 -6
  183. package/dist/renderer/assets/index-D1p6crtf.js +0 -46
  184. package/dist/renderer/assets/ipc-transport-ClIvDcYY.js +0 -9
  185. package/dist/renderer/assets/ipc-transport-EiWxu_hG.css +0 -1
  186. package/dist/renderer/assets/popover-CtXcBJtM.js +0 -2
  187. package/dist/renderer/assets/select-DMfp49f1.js +0 -2
  188. package/dist/renderer/assets/settings-api-CY84UMpN.js +0 -2
  189. package/dist/renderer/assets/workbenchSettings-DjELLnSJ.js +0 -8
  190. package/dist/simulator/assets/simulator-C1eHXnuq.js +0 -9
@@ -1,6 +1,10 @@
1
1
  // src/main/app/bootstrap.ts
2
2
  import { app as app2 } from "electron";
3
3
 
4
+ // src/shared/constants.ts
5
+ var DEFAULT_CDP_PORT = 9222;
6
+ var DEFAULT_SCENE = 1001;
7
+
4
8
  // src/main/services/settings/index.ts
5
9
  import { app, nativeTheme } from "electron";
6
10
  import fs from "fs";
@@ -8,13 +12,17 @@ import path from "path";
8
12
  var DEFAULTS = {
9
13
  cdp: {
10
14
  enabled: false,
11
- port: 9222
15
+ port: DEFAULT_CDP_PORT
12
16
  },
13
17
  mcp: {
14
18
  enabled: false,
15
19
  port: 7789
16
20
  },
17
- theme: "system"
21
+ compile: {
22
+ watch: true
23
+ },
24
+ theme: "system",
25
+ lastCreateBaseDir: null
18
26
  };
19
27
  function getSettingsFile() {
20
28
  return path.join(app.getPath("userData"), "dimina-workbench-settings.json");
@@ -32,10 +40,19 @@ function loadWorkbenchSettings() {
32
40
  enabled: data.mcp?.enabled ?? DEFAULTS.mcp.enabled,
33
41
  port: data.mcp?.port ?? DEFAULTS.mcp.port
34
42
  },
35
- theme
43
+ compile: {
44
+ watch: data.compile?.watch ?? DEFAULTS.compile.watch
45
+ },
46
+ theme,
47
+ lastCreateBaseDir: typeof data.lastCreateBaseDir === "string" ? data.lastCreateBaseDir : DEFAULTS.lastCreateBaseDir
36
48
  };
37
49
  } catch {
38
- return { ...DEFAULTS, cdp: { ...DEFAULTS.cdp }, mcp: { ...DEFAULTS.mcp } };
50
+ return {
51
+ ...DEFAULTS,
52
+ cdp: { ...DEFAULTS.cdp },
53
+ mcp: { ...DEFAULTS.mcp },
54
+ compile: { ...DEFAULTS.compile }
55
+ };
39
56
  }
40
57
  }
41
58
  function saveWorkbenchSettings(settings) {
@@ -57,14 +74,14 @@ function setupCdpPort() {
57
74
  if (settings.cdp.enabled || settings.mcp.enabled) {
58
75
  app2.commandLine.appendSwitch("remote-debugging-port", String(settings.cdp.port));
59
76
  } else if (!app2.isPackaged) {
60
- app2.commandLine.appendSwitch("remote-debugging-port", String(settings.cdp.port || 9222));
77
+ app2.commandLine.appendSwitch("remote-debugging-port", String(settings.cdp.port || DEFAULT_CDP_PORT));
61
78
  }
62
79
  }
63
80
 
64
81
  // src/main/app/app.ts
65
- import { app as app11, BrowserWindow as BrowserWindow3, nativeImage } from "electron";
66
- import fs5 from "fs";
67
- import path6 from "path";
82
+ import { app as app13, BrowserWindow as BrowserWindow4, nativeImage } from "electron";
83
+ import fs7 from "fs";
84
+ import path12 from "path";
68
85
 
69
86
  // src/main/utils/paths.ts
70
87
  import fs2 from "fs";
@@ -87,8 +104,86 @@ function resolveDevtoolsPackageRoot() {
87
104
  var devtoolsPackageRoot = resolveDevtoolsPackageRoot();
88
105
  var rendererDir = path2.join(devtoolsPackageRoot, "dist/renderer");
89
106
  var defaultPreloadPath = path2.join(devtoolsPackageRoot, "dist/preload/windows/simulator.js");
107
+ var mainPreloadPath = path2.join(devtoolsPackageRoot, "dist/preload/windows/main.cjs");
90
108
  var simulatorDir = path2.join(devtoolsPackageRoot, "dist/simulator");
91
109
 
110
+ // src/main/utils/theme.ts
111
+ 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
+ add(d) {
128
+ if (this._disposed) {
129
+ throw new Error("cannot add to disposed registry");
130
+ }
131
+ const fn = typeof d === "function" ? d : () => d.dispose();
132
+ const entry = { fn, released: false };
133
+ this.entries.push(entry);
134
+ return {
135
+ dispose: () => {
136
+ if (entry.released) return;
137
+ entry.released = true;
138
+ const i = this.entries.indexOf(entry);
139
+ if (i >= 0) this.entries.splice(i, 1);
140
+ return fn();
141
+ }
142
+ };
143
+ }
144
+ async disposeAll() {
145
+ if (this._disposed) return;
146
+ this._disposed = true;
147
+ const items = this.entries.slice().reverse();
148
+ this.entries = [];
149
+ const errors = [];
150
+ for (const entry of items) {
151
+ if (entry.released) continue;
152
+ entry.released = true;
153
+ try {
154
+ await entry.fn();
155
+ } catch (e) {
156
+ errors.push(e);
157
+ }
158
+ }
159
+ if (errors.length > 0) {
160
+ throw new AggregateError(errors, "DisposableRegistry encountered errors during disposeAll");
161
+ }
162
+ }
163
+ dispose() {
164
+ return this.disposeAll();
165
+ }
166
+ };
167
+
168
+ // src/main/utils/theme.ts
169
+ function themeBg() {
170
+ return nativeTheme2.shouldUseDarkColors ? "#1a1a1a" : "#fafafa";
171
+ }
172
+ function installThemeBackgroundSync() {
173
+ const apply = () => {
174
+ const bg = themeBg();
175
+ for (const win of BrowserWindow.getAllWindows()) {
176
+ if (!win.isDestroyed()) {
177
+ win.setBackgroundColor(bg);
178
+ }
179
+ }
180
+ };
181
+ nativeTheme2.on("updated", apply);
182
+ return toDisposable(() => {
183
+ nativeTheme2.removeListener("updated", apply);
184
+ });
185
+ }
186
+
92
187
  // src/main/app/lifecycle.ts
93
188
  import { app as app3, globalShortcut } from "electron";
94
189
  function registerAppLifecycle() {
@@ -102,7 +197,8 @@ function registerAppLifecycle() {
102
197
  }
103
198
 
104
199
  // src/main/windows/main-window/create.ts
105
- import { app as app4, BrowserWindow, View, globalShortcut as globalShortcut2, session } from "electron";
200
+ import { app as app4, BrowserWindow as BrowserWindow2, View, session, shell as shell2 } from "electron";
201
+ import path4 from "path";
106
202
 
107
203
  // src/main/services/simulator/referer.ts
108
204
  var DEFAULT_VERSION = "develop";
@@ -123,18 +219,51 @@ function getSimulatorServicewechatReferer() {
123
219
  return cachedRefererUrl;
124
220
  }
125
221
 
126
- // src/main/utils/theme.ts
127
- import { nativeTheme as nativeTheme2 } from "electron";
128
- function themeBg() {
129
- return nativeTheme2.shouldUseDarkColors ? "#1a1a1a" : "#fafafa";
222
+ // src/main/windows/navigation-hardening.ts
223
+ import { shell } from "electron";
224
+ import path3 from "path";
225
+ import { pathToFileURL } from "url";
226
+ function handleWindowOpenExternal(url) {
227
+ try {
228
+ const u = new URL(url);
229
+ if (u.protocol === "http:" || u.protocol === "https:") {
230
+ void shell.openExternal(url);
231
+ }
232
+ } catch {
233
+ }
234
+ return { action: "deny" };
235
+ }
236
+ function applyNavigationHardening(wc, rendererDir2) {
237
+ const rendererPrefix = pathToFileURL(rendererDir2 + path3.sep).href;
238
+ wc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
239
+ wc.on("will-navigate", (event, url) => {
240
+ let parsed;
241
+ try {
242
+ parsed = new URL(url);
243
+ } catch {
244
+ event.preventDefault();
245
+ return;
246
+ }
247
+ if (parsed.protocol === "file:" && parsed.href.startsWith(rendererPrefix)) {
248
+ return;
249
+ }
250
+ event.preventDefault();
251
+ if (parsed.protocol === "http:" || parsed.protocol === "https:") {
252
+ void shell.openExternal(url);
253
+ }
254
+ });
130
255
  }
131
256
 
132
257
  // src/main/windows/main-window/create.ts
133
258
  var simulatorSessionConfigured = false;
134
- function configureSimulatorSession() {
259
+ function configureSimulatorSession(simulatorPreloadPath) {
135
260
  if (simulatorSessionConfigured) return;
136
261
  simulatorSessionConfigured = true;
137
262
  const simulatorSession = session.fromPartition("persist:simulator");
263
+ simulatorSession.registerPreloadScript({
264
+ type: "frame",
265
+ filePath: simulatorPreloadPath
266
+ });
138
267
  simulatorSession.webRequest.onBeforeSendHeaders((details, callback) => {
139
268
  const forcedReferer = getSimulatorServicewechatReferer();
140
269
  if (forcedReferer) {
@@ -148,13 +277,13 @@ function configureSimulatorSession() {
148
277
  headers["access-control-allow-headers"] = ["*"];
149
278
  headers["access-control-allow-methods"] = ["*"];
150
279
  headers["Cross-Origin-Opener-Policy"] = ["same-origin"];
151
- headers["Cross-Origin-Embedder-Policy"] = ["require-corp"];
280
+ headers["Cross-Origin-Embedder-Policy"] = ["credentialless"];
152
281
  callback({ responseHeaders: headers });
153
282
  });
154
283
  }
155
284
  function createMainWindow(opts) {
156
- configureSimulatorSession();
157
- const mainWindow = new BrowserWindow({
285
+ configureSimulatorSession(opts.simulatorPreloadPath);
286
+ const mainWindow = new BrowserWindow2({
158
287
  width: opts.width ?? 1280,
159
288
  height: opts.height ?? 980,
160
289
  minWidth: opts.minWidth ?? 1e3,
@@ -163,8 +292,13 @@ function createMainWindow(opts) {
163
292
  show: false,
164
293
  backgroundColor: themeBg(),
165
294
  webPreferences: {
166
- nodeIntegration: true,
167
- contextIsolation: false,
295
+ nodeIntegration: false,
296
+ contextIsolation: true,
297
+ // `sandbox: false` is required so the preload bundle can `require('electron')`
298
+ // to access contextBridge/ipcRenderer. The renderer itself runs isolated.
299
+ sandbox: false,
300
+ preload: mainPreloadPath,
301
+ // The simulator <webview> still relies on the webview tag; keep it enabled.
168
302
  webviewTag: true
169
303
  }
170
304
  });
@@ -178,22 +312,45 @@ function createMainWindow(opts) {
178
312
  }
179
313
  }
180
314
  });
181
- mainWindow.webContents.on("will-attach-webview", (_event, webPreferences) => {
315
+ const rendererDir2 = path4.dirname(opts.indexHtml);
316
+ applyNavigationHardening(mainWindow.webContents, rendererDir2);
317
+ mainWindow.webContents.on("will-attach-webview", (event, webPreferences, params) => {
318
+ ;
319
+ webPreferences.partition = "persist:simulator";
320
+ params.partition = "persist:simulator";
182
321
  webPreferences.contextIsolation = false;
322
+ void event;
323
+ });
324
+ mainWindow.webContents.on("did-attach-webview", (_event, webviewWebContents) => {
325
+ webviewWebContents.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
326
+ webviewWebContents.on("will-navigate", (e, url) => {
327
+ try {
328
+ const u = new URL(url);
329
+ if (u.protocol === "about:") return;
330
+ if ((u.protocol === "http:" || u.protocol === "https:") && (u.hostname === "localhost" || u.hostname === "127.0.0.1")) {
331
+ return;
332
+ }
333
+ e.preventDefault();
334
+ if (u.protocol === "http:" || u.protocol === "https:") {
335
+ void shell2.openExternal(url);
336
+ }
337
+ } catch {
338
+ e.preventDefault();
339
+ }
340
+ });
183
341
  });
184
342
  mainWindow.loadFile(opts.indexHtml);
185
343
  const container = new View();
186
344
  const mainWebView = mainWindow.contentView;
187
345
  container.addChildView(mainWebView);
188
346
  mainWindow.contentView = container;
189
- globalShortcut2.register("CommandOrControl+Shift+I", () => {
190
- mainWindow.webContents.openDevTools({ mode: "detach" });
191
- });
192
347
  return mainWindow;
193
348
  }
194
349
 
195
350
  // src/main/windows/main-window/events.ts
351
+ import { globalShortcut as globalShortcut2 } from "electron";
196
352
  function wireMainWindowEvents(win, state = {}) {
353
+ const registry = new DisposableRegistry();
197
354
  const mainWebView = win.contentView.children[0];
198
355
  const resizeMainWebView = () => {
199
356
  if (mainWebView) {
@@ -204,25 +361,56 @@ function wireMainWindowEvents(win, state = {}) {
204
361
  };
205
362
  resizeMainWebView();
206
363
  win.on("resize", resizeMainWebView);
364
+ registry.add(toDisposable(() => {
365
+ win.removeListener("resize", resizeMainWebView);
366
+ }));
207
367
  if (state.onClose) {
208
- win.on("close", (event) => {
368
+ const onCloseHandler = (event) => {
209
369
  void state.onClose?.(event);
210
- });
370
+ };
371
+ win.on("close", onCloseHandler);
372
+ registry.add(toDisposable(() => {
373
+ win.removeListener("close", onCloseHandler);
374
+ }));
375
+ }
376
+ const devToolsAccelerator = "CommandOrControl+Shift+I";
377
+ const registered = globalShortcut2.register(devToolsAccelerator, () => {
378
+ win.webContents.openDevTools({ mode: "detach" });
379
+ });
380
+ if (registered) {
381
+ registry.add(toDisposable(() => {
382
+ globalShortcut2.unregister(devToolsAccelerator);
383
+ }));
211
384
  }
385
+ return registry;
386
+ }
387
+
388
+ // src/main/utils/sender-policy.ts
389
+ function createWorkbenchSenderPolicy(ctx) {
390
+ return (sender) => {
391
+ if (sender.isDestroyed()) return false;
392
+ if (ctx.windows.isMainSender(sender.id)) return true;
393
+ if (ctx.windows.isSettingsWindowSender(sender.id)) return true;
394
+ const settingsViewId = ctx.views.getSettingsWebContentsId();
395
+ if (settingsViewId != null && sender.id === settingsViewId) return true;
396
+ const popoverViewId = ctx.views.getPopoverWebContentsId();
397
+ if (popoverViewId != null && sender.id === popoverViewId) return true;
398
+ return false;
399
+ };
212
400
  }
213
401
 
214
402
  // src/main/services/default-adapter.ts
215
- import path3 from "path";
403
+ import path5 from "path";
216
404
  import { app as app5 } from "electron";
217
405
  var defaultAdapter = {
218
406
  async openProject(opts) {
219
407
  const diminaKit = await import("@dimina-kit/devkit");
220
- const openProject = diminaKit.openProject;
221
- return openProject({
222
- outputDir: path3.join(app5.getPath("userData"), "dimina-fe-output"),
408
+ const openProjectOpts = {
409
+ outputDir: path5.join(app5.getPath("userData"), "dimina-fe-output"),
223
410
  ...opts,
224
411
  simulatorDir
225
- });
412
+ };
413
+ return diminaKit.openProject(openProjectOpts);
226
414
  }
227
415
  };
228
416
 
@@ -232,29 +420,31 @@ var SimulatorChannel = {
232
420
  Detach: "simulator:detach",
233
421
  Resize: "simulator:resize",
234
422
  SetVisible: "simulator:setVisible",
235
- Console: "simulator:console",
236
- Wxml: "simulator:wxml",
237
- AppData: "simulator:appdata",
238
- AppDataAll: "simulator:appdata-all"
423
+ Console: "simulator:console"
424
+ };
425
+ var SimulatorCustomApiChannel = {
426
+ List: "simulator:custom-apis:list",
427
+ Invoke: "simulator:custom-apis:invoke"
239
428
  };
240
429
  var SimulatorStorageChannel = {
241
430
  GetSnapshot: "simulator:storage:snapshot",
431
+ GetActivePrefix: "simulator:storage:activePrefix",
432
+ Set: "simulator:storage:set",
433
+ Remove: "simulator:storage:remove",
434
+ Clear: "simulator:storage:clear",
435
+ ClearAll: "simulator:storage:clearAll",
242
436
  Event: "simulator:storage:event"
243
437
  };
244
438
  var SimulatorElementChannel = {
245
439
  Inspect: "simulator:element:inspect",
246
440
  Clear: "simulator:element:clear"
247
441
  };
248
- var WorkbenchChannel = {
249
- GetPanelConfig: "workbench:getPanelConfig",
250
- GetApiNamespaces: "workbench:getApiNamespaces",
251
- Reset: "workbench:reset"
252
- };
253
442
  var WorkbenchSettingsChannel = {
254
443
  Get: "workbenchSettings:get",
255
444
  Save: "workbenchSettings:save",
256
445
  SetTheme: "workbenchSettings:setTheme",
257
446
  GetCdpStatus: "workbenchSettings:getCdpStatus",
447
+ GetMcpStatus: "workbenchSettings:getMcpStatus",
258
448
  SetVisible: "workbenchSettings:setVisible",
259
449
  Init: "workbenchSettings:init"
260
450
  };
@@ -264,12 +454,22 @@ var ProjectChannel = {
264
454
  GetPages: "project:getPages",
265
455
  GetCompileConfig: "project:getCompileConfig",
266
456
  SaveCompileConfig: "project:saveCompileConfig",
267
- Status: "project:status"
457
+ Status: "project:status",
458
+ CaptureThumbnail: "project:captureThumbnail",
459
+ GetThumbnail: "project:getThumbnail"
268
460
  };
269
461
  var ProjectsChannel = {
270
462
  List: "projects:list",
271
463
  Add: "projects:add",
272
- Remove: "projects:remove"
464
+ Remove: "projects:remove",
465
+ /** Phase 3: merged + sanitized template catalog for the create-project dialog. */
466
+ ListTemplates: "projects:listTemplates",
467
+ /** Phase 3: host-supplied create-project dialog hook (returns input or null). */
468
+ OpenCreateDialog: "projects:openCreateDialog",
469
+ /** Phase 3: server-side scaffold + register (delegates to create-project-service). */
470
+ Create: "projects:create",
471
+ /** Default values used to pre-fill the create-project dialog (baseDir). */
472
+ GetCreateDefaults: "projects:getCreateDefaults"
273
473
  };
274
474
  var DialogChannel = {
275
475
  OpenDirectory: "dialog:openDirectory"
@@ -311,6 +511,13 @@ var SettingsChannel = {
311
511
  Closed: "settings:closed",
312
512
  Changed: "settings:changed"
313
513
  };
514
+ var UpdateChannel = {
515
+ Check: "updates:check",
516
+ Download: "updates:download",
517
+ Install: "updates:install",
518
+ DownloadProgress: "updates:downloadProgress",
519
+ Available: "updates:available"
520
+ };
314
521
 
315
522
  // src/main/services/notifications/renderer-notifier.ts
316
523
  function liveWebContents(wc) {
@@ -320,8 +527,8 @@ function liveWebContents(wc) {
320
527
  }
321
528
  function createRendererNotifier(ctx) {
322
529
  function sendToMain(channel, ...args) {
323
- if (ctx.mainWindow.isDestroyed()) return;
324
- const wc = liveWebContents(ctx.mainWindow.webContents);
530
+ if (ctx.windows.mainWindow.isDestroyed()) return;
531
+ const wc = liveWebContents(ctx.windows.mainWindow.webContents);
325
532
  if (!wc) return;
326
533
  wc.send(channel, ...args);
327
534
  }
@@ -344,9 +551,6 @@ function createRendererNotifier(ctx) {
344
551
  popoverRelaunch(config) {
345
552
  sendToMain(PopoverChannel.Relaunch, config);
346
553
  },
347
- workbenchReset() {
348
- sendToMain(WorkbenchChannel.Reset);
349
- },
350
554
  toolbarActionsChanged() {
351
555
  sendToMain(ToolbarChannel.ActionsChanged);
352
556
  },
@@ -371,7 +575,7 @@ function createRendererNotifier(ctx) {
371
575
 
372
576
  // src/main/services/views/view-manager.ts
373
577
  import { WebContentsView as WebContentsView2, webContents } from "electron";
374
- import path4 from "path";
578
+ import path6 from "path";
375
579
 
376
580
  // src/main/services/layout/index.ts
377
581
  var HEADER_H = 40;
@@ -420,9 +624,9 @@ function createViewManager(ctx) {
420
624
  let simulatorWebContentsId = null;
421
625
  function destroyViewInternal(view) {
422
626
  if (!view) return;
423
- if (!ctx.mainWindow.isDestroyed()) {
627
+ if (!ctx.windows.mainWindow.isDestroyed()) {
424
628
  try {
425
- ctx.mainWindow.contentView.removeChildView(view);
629
+ ctx.windows.mainWindow.contentView.removeChildView(view);
426
630
  } catch {
427
631
  }
428
632
  }
@@ -434,18 +638,18 @@ function createViewManager(ctx) {
434
638
  }
435
639
  }
436
640
  function applySimulatorBounds(simWidth) {
437
- if (!simulatorView || ctx.mainWindow.isDestroyed()) return;
438
- const [w = 0, h = 0] = ctx.mainWindow.getContentSize();
641
+ if (!simulatorView || ctx.windows.mainWindow.isDestroyed()) return;
642
+ const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
439
643
  simulatorView.setBounds(computeSimulatorBounds(w, h, simWidth));
440
644
  }
441
645
  function applySettingsBounds() {
442
- if (!settingsView || ctx.mainWindow.isDestroyed()) return;
443
- const [w = 0, h = 0] = ctx.mainWindow.getContentSize();
646
+ if (!settingsView || ctx.windows.mainWindow.isDestroyed()) return;
647
+ const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
444
648
  settingsView.setBounds(computeSettingsBounds(w, h));
445
649
  }
446
650
  function applyPopoverBounds() {
447
- if (!popoverView || ctx.mainWindow.isDestroyed()) return;
448
- const [w = 0, h = 0] = ctx.mainWindow.getContentSize();
651
+ if (!popoverView || ctx.windows.mainWindow.isDestroyed()) return;
652
+ const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
449
653
  popoverView.setBounds(computePopoverBounds(w, h));
450
654
  }
451
655
  function attachSimulator(simWcId, simWidth) {
@@ -493,7 +697,7 @@ function createViewManager(ctx) {
493
697
  });
494
698
  });
495
699
  if (getDefaultTab(ctx) === "simulator") {
496
- ctx.mainWindow.contentView.addChildView(simulatorView);
700
+ ctx.windows.mainWindow.contentView.addChildView(simulatorView);
497
701
  simulatorViewAdded = true;
498
702
  applySimulatorBounds(simWidth);
499
703
  }
@@ -512,7 +716,7 @@ function createViewManager(ctx) {
512
716
  lastSimWidth = simWidth;
513
717
  if (!simulatorView) return;
514
718
  if (!simulatorViewAdded) {
515
- ctx.mainWindow.contentView.addChildView(simulatorView);
719
+ ctx.windows.mainWindow.contentView.addChildView(simulatorView);
516
720
  simulatorViewAdded = true;
517
721
  }
518
722
  applySimulatorBounds(simWidth);
@@ -520,7 +724,7 @@ function createViewManager(ctx) {
520
724
  function hideSimulator() {
521
725
  if (simulatorView && simulatorViewAdded) {
522
726
  try {
523
- ctx.mainWindow.contentView.removeChildView(simulatorView);
727
+ ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
524
728
  } catch (e) {
525
729
  console.error("[workbench] hideSimulator error", e);
526
730
  }
@@ -530,14 +734,20 @@ function createViewManager(ctx) {
530
734
  async function showSettings() {
531
735
  if (!settingsView) {
532
736
  settingsView = new WebContentsView2({
533
- webPreferences: { nodeIntegration: true, contextIsolation: false }
737
+ webPreferences: {
738
+ nodeIntegration: false,
739
+ contextIsolation: true,
740
+ sandbox: false,
741
+ preload: mainPreloadPath
742
+ }
534
743
  });
744
+ applyNavigationHardening(settingsView.webContents, ctx.rendererDir);
535
745
  await settingsView.webContents.loadFile(
536
- path4.join(ctx.rendererDir, "entries/settings/index.html")
746
+ path6.join(ctx.rendererDir, "entries/settings/index.html")
537
747
  );
538
748
  }
539
749
  if (!settingsViewAdded) {
540
- ctx.mainWindow.contentView.addChildView(settingsView);
750
+ ctx.windows.mainWindow.contentView.addChildView(settingsView);
541
751
  settingsViewAdded = true;
542
752
  }
543
753
  applySettingsBounds();
@@ -545,7 +755,7 @@ function createViewManager(ctx) {
545
755
  function hideSettings() {
546
756
  if (settingsView && settingsViewAdded) {
547
757
  try {
548
- ctx.mainWindow.contentView.removeChildView(settingsView);
758
+ ctx.windows.mainWindow.contentView.removeChildView(settingsView);
549
759
  } catch {
550
760
  }
551
761
  settingsViewAdded = false;
@@ -554,17 +764,23 @@ function createViewManager(ctx) {
554
764
  function showPopover(data) {
555
765
  hidePopover();
556
766
  const popover = new WebContentsView2({
557
- webPreferences: { nodeIntegration: true, contextIsolation: false }
767
+ webPreferences: {
768
+ nodeIntegration: false,
769
+ contextIsolation: true,
770
+ sandbox: false,
771
+ preload: mainPreloadPath
772
+ }
558
773
  });
774
+ applyNavigationHardening(popover.webContents, ctx.rendererDir);
559
775
  popoverView = popover;
560
776
  popover.setBackgroundColor("#00000000");
561
- ctx.mainWindow.contentView.addChildView(popover);
777
+ ctx.windows.mainWindow.contentView.addChildView(popover);
562
778
  applyPopoverBounds();
563
779
  popover.webContents.once("did-finish-load", () => {
564
780
  ctx.notify.popoverInit(popover, data);
565
781
  });
566
782
  popover.webContents.loadFile(
567
- path4.join(ctx.rendererDir, "entries/popover/index.html")
783
+ path6.join(ctx.rendererDir, "entries/popover/index.html")
568
784
  );
569
785
  }
570
786
  function hidePopover() {
@@ -585,14 +801,6 @@ function createViewManager(ctx) {
585
801
  function disposeAll() {
586
802
  detachSimulator();
587
803
  }
588
- function logChildViews(_label) {
589
- }
590
- function positionSimulator(simWidth) {
591
- applySimulatorBounds(simWidth);
592
- }
593
- function positionSettings() {
594
- applySettingsBounds();
595
- }
596
804
  function resize(simWidth) {
597
805
  lastSimWidth = simWidth;
598
806
  if (simulatorViewAdded) applySimulatorBounds(simWidth);
@@ -618,10 +826,12 @@ function createViewManager(ctx) {
618
826
  hidePopover,
619
827
  repositionAll,
620
828
  disposeAll,
621
- logChildViews,
622
- positionSimulator,
623
- positionSettings,
624
829
  getSimulatorWebContentsId: () => simulatorWebContentsId,
830
+ getSimulatorWebContents: () => {
831
+ if (simulatorWebContentsId == null) return null;
832
+ const wc = webContents.fromId(simulatorWebContentsId);
833
+ return wc && !wc.isDestroyed() ? wc : null;
834
+ },
625
835
  getLastSimWidth: () => lastSimWidth,
626
836
  isSimulatorAdded: () => simulatorViewAdded,
627
837
  hasSimulatorView: () => simulatorView !== null,
@@ -630,19 +840,52 @@ function createViewManager(ctx) {
630
840
  if (settingsView.webContents.isDestroyed()) return null;
631
841
  return settingsView.webContents;
632
842
  },
843
+ getSettingsWebContentsId: () => {
844
+ if (!settingsView) return null;
845
+ if (settingsView.webContents.isDestroyed()) return null;
846
+ return settingsView.webContents.id;
847
+ },
848
+ getPopoverWebContentsId: () => {
849
+ if (!popoverView) return null;
850
+ if (popoverView.webContents.isDestroyed()) return null;
851
+ return popoverView.webContents.id;
852
+ },
633
853
  resize,
634
854
  setVisible
635
855
  };
636
856
  }
637
857
 
858
+ // src/main/services/window-service.ts
859
+ function createWindowService(mainWindow) {
860
+ let settingsWin = null;
861
+ return {
862
+ get mainWindow() {
863
+ return mainWindow;
864
+ },
865
+ get settingsWindow() {
866
+ return settingsWin;
867
+ },
868
+ setSettingsWindow(win) {
869
+ settingsWin = win;
870
+ },
871
+ closeSettingsWindow() {
872
+ if (settingsWin && !settingsWin.isDestroyed()) settingsWin.close();
873
+ settingsWin = null;
874
+ },
875
+ isMainSender(id) {
876
+ return !mainWindow.isDestroyed() && mainWindow.webContents.id === id;
877
+ },
878
+ isSettingsWindowSender(id) {
879
+ return !!settingsWin && !settingsWin.isDestroyed() && settingsWin.webContents.id === id;
880
+ }
881
+ };
882
+ }
883
+
638
884
  // src/main/services/projects/project-repository.ts
639
885
  import { app as app6 } from "electron";
640
- import path5 from "path";
886
+ import path7 from "path";
641
887
  import fs3 from "fs";
642
888
 
643
- // src/shared/constants.ts
644
- var DEFAULT_SCENE = 1001;
645
-
646
889
  // src/main/utils/logger.ts
647
890
  var LEVEL_PRIORITY = {
648
891
  debug: 0,
@@ -684,7 +927,7 @@ function createLogger(tag) {
684
927
  // src/main/services/projects/project-repository.ts
685
928
  var log = createLogger("projects");
686
929
  function getProjectsFile() {
687
- return path5.join(app6.getPath("userData"), "dimina-projects.json");
930
+ return path7.join(app6.getPath("userData"), "dimina-projects.json");
688
931
  }
689
932
  function load() {
690
933
  try {
@@ -706,13 +949,13 @@ function validateProjectDir(dirPath) {
706
949
  if (!fs3.existsSync(dirPath)) {
707
950
  return `\u5C0F\u7A0B\u5E8F\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${dirPath}`;
708
951
  }
709
- if (!fs3.existsSync(path5.join(dirPath, "app.json"))) {
710
- const configPath = path5.join(dirPath, "project.config.json");
952
+ if (!fs3.existsSync(path7.join(dirPath, "app.json"))) {
953
+ const configPath = path7.join(dirPath, "project.config.json");
711
954
  if (fs3.existsSync(configPath)) {
712
955
  try {
713
956
  const cfg = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
714
957
  if (cfg.miniprogramRoot) {
715
- const resolvedRoot = path5.resolve(dirPath, cfg.miniprogramRoot);
958
+ const resolvedRoot = path7.resolve(dirPath, cfg.miniprogramRoot);
716
959
  return `\u8BE5\u76EE\u5F55\u7F3A\u5C11 app.json\uFF0Cproject.config.json \u4E2D\u6307\u5B9A\u4E86 miniprogramRoot: "${cfg.miniprogramRoot}"\uFF0C\u8BF7\u5BFC\u5165 ${resolvedRoot}`;
717
960
  }
718
961
  } catch (err) {
@@ -725,9 +968,9 @@ function validateProjectDir(dirPath) {
725
968
  }
726
969
  function addProject(dirPath) {
727
970
  const projects = load();
728
- let name = path5.basename(dirPath);
971
+ let name = path7.basename(dirPath);
729
972
  try {
730
- const configPath = path5.join(dirPath, "project.config.json");
973
+ const configPath = path7.join(dirPath, "project.config.json");
731
974
  if (fs3.existsSync(configPath)) {
732
975
  const cfg = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
733
976
  if (cfg.projectname) name = cfg.projectname;
@@ -766,7 +1009,7 @@ function getCompileConfig(dirPath) {
766
1009
  };
767
1010
  }
768
1011
  function getProjectPages(dirPath) {
769
- const appJsonPath = path5.join(dirPath, "app.json");
1012
+ const appJsonPath = path7.join(dirPath, "app.json");
770
1013
  try {
771
1014
  const appJson = JSON.parse(
772
1015
  fs3.readFileSync(appJsonPath, "utf-8")
@@ -792,7 +1035,7 @@ function getProjectSettings(projectPath) {
792
1035
  return { uploadWithSourceMap: false };
793
1036
  }
794
1037
  try {
795
- const configPath = path5.join(projectPath, "project.config.json");
1038
+ const configPath = path7.join(projectPath, "project.config.json");
796
1039
  const config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
797
1040
  return {
798
1041
  uploadWithSourceMap: !!config.setting?.uploadWithSourceMap
@@ -803,7 +1046,7 @@ function getProjectSettings(projectPath) {
803
1046
  }
804
1047
  function updateProjectSettings(projectPath, patch) {
805
1048
  if (!projectPath) return;
806
- const configPath = path5.join(projectPath, "project.config.json");
1049
+ const configPath = path7.join(projectPath, "project.config.json");
807
1050
  let config = {};
808
1051
  try {
809
1052
  config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
@@ -819,12 +1062,19 @@ function updateProjectSettings(projectPath, patch) {
819
1062
  );
820
1063
  }
821
1064
 
1065
+ // src/main/services/projects/types.ts
1066
+ var DEFAULT_COMPILE_CONFIG = {
1067
+ startPage: "",
1068
+ scene: DEFAULT_SCENE,
1069
+ queryParams: []
1070
+ };
1071
+
822
1072
  // src/main/services/workspace/workspace-service.ts
823
1073
  function createWorkspaceService(ctx) {
824
1074
  let currentSession = null;
825
1075
  let currentProjectPath = "";
826
- function sendStatus(status, message) {
827
- ctx.notify.projectStatus({ status, message });
1076
+ function sendStatus(status2, message, hotReload) {
1077
+ ctx.notify.projectStatus(hotReload ? { status: status2, message, hotReload: true } : { status: status2, message });
828
1078
  }
829
1079
  function bestEffort(label, fn) {
830
1080
  try {
@@ -857,27 +1107,36 @@ function createWorkspaceService(ctx) {
857
1107
  );
858
1108
  }
859
1109
  }
1110
+ const provider = ctx.projectsProvider;
860
1111
  return {
861
- listProjects: () => listProjects(),
862
- addProject: (dirPath) => addProject(dirPath),
863
- removeProject: (dirPath) => removeProject(dirPath),
864
- validateProjectDir: (dirPath) => validateProjectDir(dirPath),
1112
+ listProjects: async () => provider.listProjects(),
1113
+ addProject: async (dirPath) => provider.addProject(dirPath),
1114
+ removeProject: async (dirPath) => {
1115
+ await provider.removeProject(dirPath);
1116
+ },
1117
+ hasProject: async (dirPath) => {
1118
+ const projects = await provider.listProjects();
1119
+ return projects.some((p) => p.path === dirPath);
1120
+ },
1121
+ validateProjectDir: async (dirPath) => provider.validateProjectDir ? provider.validateProjectDir(dirPath) : null,
865
1122
  async openProject(projectPath) {
866
1123
  clearSimulatorServicewechatReferer();
867
1124
  await disposeSession();
868
1125
  currentProjectPath = "";
869
- const dirError = validateProjectDir(projectPath);
1126
+ const dirError = provider.validateProjectDir ? await provider.validateProjectDir(projectPath) : null;
870
1127
  if (dirError) {
871
1128
  sendStatus("error", dirError);
872
1129
  return { success: false, error: dirError };
873
1130
  }
874
1131
  sendStatus("compiling", "\u6B63\u5728\u7F16\u8BD1...");
1132
+ const { compile } = loadWorkbenchSettings();
875
1133
  let session2;
876
1134
  try {
877
1135
  session2 = await ctx.adapter.openProject({
878
1136
  projectPath,
879
1137
  sourcemap: true,
880
- onRebuild: () => sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210\uFF0C\u5DF2\u70ED\u66F4\u65B0"),
1138
+ watch: compile.watch,
1139
+ onRebuild: () => sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210\uFF0C\u5DF2\u70ED\u66F4\u65B0", true),
881
1140
  onBuildError: (err) => sendStatus("error", String(err))
882
1141
  });
883
1142
  } catch (err) {
@@ -887,7 +1146,9 @@ function createWorkspaceService(ctx) {
887
1146
  }
888
1147
  currentSession = session2;
889
1148
  currentProjectPath = projectPath;
890
- bestEffort("updateLastOpened", () => updateLastOpened(projectPath));
1149
+ bestEffort("updateLastOpened", () => {
1150
+ if (provider.updateLastOpened) provider.updateLastOpened(projectPath);
1151
+ });
891
1152
  bestEffort("sendStatus", () => sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210"));
892
1153
  bestEffort("applyReferer", () => applyRefererFromSession(session2));
893
1154
  return {
@@ -905,14 +1166,132 @@ function createWorkspaceService(ctx) {
905
1166
  getSession: () => currentSession,
906
1167
  getProjectPath: () => currentProjectPath,
907
1168
  hasActiveSession: () => currentSession !== null,
1169
+ async captureThumbnail(projectPath) {
1170
+ const wc = ctx.views.getSimulatorWebContents();
1171
+ if (!wc) return null;
1172
+ try {
1173
+ const image = await wc.capturePage();
1174
+ const dataUrl = `data:image/png;base64,${image.toPNG().toString("base64")}`;
1175
+ if (provider.saveThumbnail) {
1176
+ await provider.saveThumbnail(projectPath, dataUrl);
1177
+ }
1178
+ return dataUrl;
1179
+ } catch {
1180
+ return null;
1181
+ }
1182
+ },
1183
+ async getThumbnail(projectPath) {
1184
+ if (provider.getThumbnail) {
1185
+ return await provider.getThumbnail(projectPath) ?? null;
1186
+ }
1187
+ return null;
1188
+ },
1189
+ // `getProjectPages` reads the project's own `app.json` from disk and is
1190
+ // independent of the project registry — it stays on the repo helper.
908
1191
  getProjectPages: (projectPath) => getProjectPages(projectPath),
909
- getCompileConfig: (projectPath) => getCompileConfig(projectPath),
910
- saveCompileConfig: (projectPath, config) => saveCompileConfig(projectPath, config),
1192
+ getCompileConfig: async (projectPath) => provider.getCompileConfig ? await provider.getCompileConfig(projectPath) : DEFAULT_COMPILE_CONFIG,
1193
+ saveCompileConfig: async (projectPath, config) => {
1194
+ if (provider.saveCompileConfig) {
1195
+ await provider.saveCompileConfig(projectPath, config);
1196
+ }
1197
+ },
1198
+ // Per-project settings (uploadWithSourceMap etc.) live in the project's
1199
+ // own `project.config.json`, not the registry — keep direct repo calls.
911
1200
  getProjectSettings: (projectPath) => getProjectSettings(projectPath),
912
1201
  updateProjectSettings: (projectPath, patch) => updateProjectSettings(projectPath, patch)
913
1202
  };
914
1203
  }
915
1204
 
1205
+ // src/main/services/projects/thumbnail.ts
1206
+ import { createHash } from "crypto";
1207
+ import fs4 from "fs";
1208
+ import path8 from "path";
1209
+ import { app as app7 } from "electron";
1210
+ function getThumbnailDir() {
1211
+ return path8.join(app7.getPath("userData"), "thumbnails");
1212
+ }
1213
+ function hashProjectPath(projectPath) {
1214
+ return createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
1215
+ }
1216
+ function getThumbnailPath(projectPath) {
1217
+ return path8.join(getThumbnailDir(), `${hashProjectPath(projectPath)}.png`);
1218
+ }
1219
+ var DATA_URL_PNG_PREFIX = "data:image/png;base64,";
1220
+ function saveThumbnailFromDataUrl(projectPath, imageDataUrl) {
1221
+ if (!imageDataUrl.startsWith(DATA_URL_PNG_PREFIX)) return;
1222
+ const png = Buffer.from(imageDataUrl.slice(DATA_URL_PNG_PREFIX.length), "base64");
1223
+ fs4.mkdirSync(getThumbnailDir(), { recursive: true });
1224
+ fs4.writeFileSync(getThumbnailPath(projectPath), png);
1225
+ }
1226
+ function loadThumbnail(projectPath) {
1227
+ const filePath = getThumbnailPath(projectPath);
1228
+ try {
1229
+ const buf = fs4.readFileSync(filePath);
1230
+ return `${DATA_URL_PNG_PREFIX}${buf.toString("base64")}`;
1231
+ } catch {
1232
+ return null;
1233
+ }
1234
+ }
1235
+
1236
+ // src/main/services/projects/local-provider.ts
1237
+ function createLocalProjectsProvider() {
1238
+ return {
1239
+ listProjects: () => listProjects(),
1240
+ addProject: (dirPath) => addProject(dirPath),
1241
+ removeProject: (dirPath) => removeProject(dirPath),
1242
+ validateProjectDir: (dirPath) => validateProjectDir(dirPath),
1243
+ updateLastOpened: (dirPath) => updateLastOpened(dirPath),
1244
+ getCompileConfig: (dirPath) => getCompileConfig(dirPath),
1245
+ saveCompileConfig: (dirPath, cfg) => saveCompileConfig(dirPath, cfg),
1246
+ saveThumbnail: (dirPath, dataUrl) => saveThumbnailFromDataUrl(dirPath, dataUrl),
1247
+ getThumbnail: (dirPath) => loadThumbnail(dirPath)
1248
+ };
1249
+ }
1250
+
1251
+ // src/main/services/projects/templates.ts
1252
+ function resolveTemplates(builtin, injected, mode) {
1253
+ let kept;
1254
+ if (mode === "none") {
1255
+ kept = [];
1256
+ } else if (mode === "all") {
1257
+ kept = [...builtin];
1258
+ } else {
1259
+ const byId = new Map(builtin.map((t) => [t.id, t]));
1260
+ kept = [];
1261
+ for (const id of mode) {
1262
+ const t = byId.get(id);
1263
+ if (t) kept.push(t);
1264
+ }
1265
+ }
1266
+ const injectedIds = new Set(injected.map((t) => t.id));
1267
+ const remaining = kept.filter((t) => !injectedIds.has(t.id));
1268
+ return [...injected, ...remaining];
1269
+ }
1270
+ function sanitizeTemplates(templates) {
1271
+ return templates.map((t) => {
1272
+ const { generate: _generate, ...rest } = t;
1273
+ return rest;
1274
+ });
1275
+ }
1276
+
1277
+ // src/main/services/projects/builtin-templates.ts
1278
+ import path9 from "node:path";
1279
+ var TEMPLATES_DIR = path9.join(devtoolsPackageRoot, "templates");
1280
+ var BUILTIN_TEMPLATES = [
1281
+ {
1282
+ id: "blank",
1283
+ name: "Blank",
1284
+ description: "\u6700\u5C0F\u9AA8\u67B6\uFF1Aapp.* + \u4E00\u4E2A index \u9875\u9762",
1285
+ source: { type: "directory", path: path9.join(TEMPLATES_DIR, "blank") }
1286
+ },
1287
+ {
1288
+ id: "taro-todo",
1289
+ name: "Taro Todo",
1290
+ description: "Taro \u7F16\u8BD1\u4EA7\u7269\u7684 Todo \u793A\u4F8B",
1291
+ source: { type: "directory", path: path9.join(TEMPLATES_DIR, "taro-todo") }
1292
+ }
1293
+ ];
1294
+
916
1295
  // src/main/services/workbench-context.ts
917
1296
  function hasBuiltinPanel(ctx, panelId) {
918
1297
  return ctx.panels.includes(panelId);
@@ -924,7 +1303,6 @@ function getDefaultTab(ctx) {
924
1303
  }
925
1304
  function createWorkbenchContext(opts) {
926
1305
  const ctx = {
927
- mainWindow: opts.mainWindow,
928
1306
  adapter: opts.adapter ?? defaultAdapter,
929
1307
  preloadPath: opts.preloadPath,
930
1308
  rendererDir: opts.rendererDir,
@@ -932,12 +1310,21 @@ function createWorkbenchContext(opts) {
932
1310
  apiNamespaces: opts.apiNamespaces ?? [],
933
1311
  appName: opts.appName ?? "Dimina DevTools",
934
1312
  toolbarActions: opts.toolbarActions,
935
- brandingProvider: opts.brandingProvider,
936
- workbenchSettingsWindow: null
1313
+ brandingProvider: opts.brandingProvider
937
1314
  };
1315
+ ctx.registry = new DisposableRegistry();
1316
+ ctx.windows = createWindowService(opts.mainWindow);
938
1317
  ctx.views = createViewManager(ctx);
939
1318
  ctx.notify = createRendererNotifier(ctx);
1319
+ ctx.projectsProvider = opts.projectsProvider ?? createLocalProjectsProvider();
1320
+ ctx.projectTemplates = resolveTemplates(
1321
+ BUILTIN_TEMPLATES,
1322
+ opts.projectTemplates ?? [],
1323
+ opts.builtinTemplates ?? "all"
1324
+ );
1325
+ ctx.customCreateProjectDialog = opts.customCreateProjectDialog;
940
1326
  ctx.workspace = createWorkspaceService(ctx);
1327
+ ctx.senderPolicy = createWorkbenchSenderPolicy(ctx);
941
1328
  return ctx;
942
1329
  }
943
1330
 
@@ -967,6 +1354,17 @@ function installAppMenu(ctx) {
967
1354
  { role: "quit" }
968
1355
  ]
969
1356
  },
1357
+ {
1358
+ label: "\u9879\u76EE",
1359
+ submenu: [
1360
+ {
1361
+ label: "\u6253\u5F00\u9879\u76EE",
1362
+ click: () => {
1363
+ ctx.notify.windowNavigateBack();
1364
+ }
1365
+ }
1366
+ ]
1367
+ },
970
1368
  {
971
1369
  label: "\u7F16\u8F91",
972
1370
  submenu: [
@@ -1008,72 +1406,256 @@ function installAppMenu(ctx) {
1008
1406
  }
1009
1407
 
1010
1408
  // src/main/ipc/app.ts
1409
+ import fs5 from "fs";
1410
+
1411
+ // src/main/utils/ipc-registry.ts
1011
1412
  import { ipcMain } from "electron";
1012
- import fs4 from "fs";
1413
+
1414
+ // src/main/utils/ipc-schema.ts
1415
+ var IpcValidationError = class extends Error {
1416
+ channel;
1417
+ paths;
1418
+ constructor(channel, issues) {
1419
+ const paths = issues.map((i) => i.path.map((p) => String(p)).join(".") || "<root>");
1420
+ super(`Invalid IPC arguments for ${channel}`);
1421
+ this.name = "IpcValidationError";
1422
+ this.channel = channel;
1423
+ this.paths = paths;
1424
+ }
1425
+ };
1426
+ function validate(channel, schema, args) {
1427
+ const r = schema.safeParse(args);
1428
+ if (!r.success) {
1429
+ console.warn("[ipc] schema reject", channel, r.error.issues);
1430
+ throw new IpcValidationError(channel, r.error.issues);
1431
+ }
1432
+ return r.data;
1433
+ }
1434
+
1435
+ // src/main/utils/ipc-registry.ts
1436
+ var log2 = createLogger("ipc");
1437
+ function reportListenerError(channel, err) {
1438
+ if (err instanceof IpcValidationError) {
1439
+ log2.warn(`schema reject on '${channel}' at [${err.paths.join(", ")}]`);
1440
+ return;
1441
+ }
1442
+ if (err instanceof Error) {
1443
+ log2.error(`listener threw on '${channel}': ${err.message}`, err.stack);
1444
+ return;
1445
+ }
1446
+ log2.error(`listener threw on '${channel}'`, err);
1447
+ }
1448
+ function isThenable(v) {
1449
+ return v != null && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
1450
+ }
1451
+ function summarizeSender(sender) {
1452
+ if (sender.isDestroyed()) return "<destroyed>";
1453
+ const url = sender.getURL();
1454
+ return `id=${sender.id} ${url.slice(0, 120)}`;
1455
+ }
1456
+ var IpcRegistry = class {
1457
+ constructor(policy) {
1458
+ this.policy = policy;
1459
+ }
1460
+ policy;
1461
+ registry = new DisposableRegistry();
1462
+ handle(channel, fn) {
1463
+ const policy = this.policy;
1464
+ const guarded = policy ? (event, ...args) => {
1465
+ const sender = event.sender;
1466
+ if (!policy(sender)) {
1467
+ console.warn(
1468
+ `[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
1469
+ );
1470
+ throw new Error(`IPC sender rejected for channel ${channel}`);
1471
+ }
1472
+ return fn(event, ...args);
1473
+ } : fn;
1474
+ ipcMain.handle(channel, guarded);
1475
+ this.registry.add(() => ipcMain.removeHandler(channel));
1476
+ return this;
1477
+ }
1478
+ on(channel, fn) {
1479
+ const policy = this.policy;
1480
+ const raw = fn;
1481
+ const safeInvoke = (event, args) => {
1482
+ try {
1483
+ const ret = raw(event, ...args);
1484
+ if (isThenable(ret)) {
1485
+ Promise.resolve(ret).catch((err) => reportListenerError(channel, err));
1486
+ }
1487
+ } catch (err) {
1488
+ reportListenerError(channel, err);
1489
+ }
1490
+ };
1491
+ const guarded = policy ? (event, ...args) => {
1492
+ const sender = event.sender;
1493
+ if (!policy(sender)) {
1494
+ console.warn(
1495
+ `[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
1496
+ );
1497
+ return;
1498
+ }
1499
+ safeInvoke(event, args);
1500
+ } : (event, ...args) => safeInvoke(event, args);
1501
+ ipcMain.on(channel, guarded);
1502
+ this.registry.add(() => {
1503
+ ipcMain.removeListener(channel, guarded);
1504
+ });
1505
+ return this;
1506
+ }
1507
+ dispose() {
1508
+ return this.registry.dispose();
1509
+ }
1510
+ };
1511
+
1512
+ // src/main/ipc/app.ts
1013
1513
  function registerAppIpc(ctx) {
1014
- ipcMain.handle(AppChannel.GetPreloadPath, () => {
1015
- return `file://${fs4.realpathSync(ctx.preloadPath)}`;
1016
- });
1017
- ipcMain.handle(AppChannel.GetBranding, async () => {
1514
+ return new IpcRegistry(ctx.senderPolicy).handle(AppChannel.GetPreloadPath, () => {
1515
+ return `file://${fs5.realpathSync(ctx.preloadPath)}`;
1516
+ }).handle(AppChannel.GetBranding, async () => {
1018
1517
  if (ctx.brandingProvider) return ctx.brandingProvider();
1019
1518
  return { appName: ctx.appName };
1020
1519
  });
1021
1520
  }
1022
1521
 
1522
+ // src/shared/ipc-schemas.ts
1523
+ import { z } from "zod";
1524
+ var AbsolutePath = z.string().min(1).refine(
1525
+ (p) => p.startsWith("/") || /^[a-zA-Z]:\\/.test(p) || /^[a-zA-Z]:\//.test(p),
1526
+ "must be an absolute path"
1527
+ );
1528
+ var PanelEvalSchema = z.tuple([z.string().min(1).max(5e4)]);
1529
+ var ProjectsAddSchema = z.tuple([AbsolutePath]);
1530
+ var ProjectOpenSchema = z.tuple([AbsolutePath]);
1531
+ var CompileConfigShape = z.looseObject({
1532
+ startPage: z.string().optional(),
1533
+ scene: z.number().optional(),
1534
+ queryParams: z.array(z.looseObject({ key: z.string(), value: z.string() })).optional()
1535
+ });
1536
+ var ProjectSaveCompileConfigSchema = z.tuple([
1537
+ AbsolutePath,
1538
+ CompileConfigShape
1539
+ ]);
1540
+ var PopoverShowSchema = z.tuple([z.looseObject({})]);
1541
+ var SimWidth = z.number().int().min(100).max(2e3);
1542
+ var SimulatorAttachSchema = z.tuple([
1543
+ z.number().int().positive(),
1544
+ SimWidth
1545
+ ]);
1546
+ var SimulatorResizeSchema = z.tuple([SimWidth]);
1547
+ var SimulatorSetVisibleSchema = z.tuple([z.boolean(), SimWidth]);
1548
+ var SimulatorCustomApiInvokeSchema = z.tuple([
1549
+ z.string().min(1).max(256),
1550
+ z.unknown()
1551
+ ]);
1552
+ var ProjectGetPagesSchema = z.tuple([AbsolutePath]);
1553
+ var ProjectGetCompileConfigSchema = z.tuple([AbsolutePath]);
1554
+ var ProjectsRemoveSchema = z.tuple([AbsolutePath]);
1555
+ var WorkbenchSettingsSaveSchema = z.tuple([
1556
+ z.looseObject({
1557
+ cdp: z.looseObject({
1558
+ enabled: z.boolean(),
1559
+ port: z.number().int().min(0).max(65535)
1560
+ }),
1561
+ mcp: z.looseObject({
1562
+ enabled: z.boolean(),
1563
+ port: z.number().int().min(0).max(65535)
1564
+ }),
1565
+ compile: z.looseObject({
1566
+ watch: z.boolean()
1567
+ }),
1568
+ theme: z.enum(["system", "light", "dark"]),
1569
+ lastCreateBaseDir: z.union([z.string(), z.null()])
1570
+ })
1571
+ ]);
1572
+ var WorkbenchSettingsSetThemeSchema = z.tuple([
1573
+ z.enum(["system", "light", "dark"])
1574
+ ]);
1575
+ var WorkbenchSettingsSetVisibleSchema = z.tuple([z.boolean()]);
1576
+ var SettingsSetVisibleSchema = z.tuple([z.boolean()]);
1577
+ var SettingsConfigChangedSchema = z.tuple([CompileConfigShape]);
1578
+ var PopoverRelaunchSchema = z.tuple([CompileConfigShape]);
1579
+ var SettingsProjectSettingsChangedSchema = z.tuple([
1580
+ z.looseObject({
1581
+ uploadWithSourceMap: z.boolean().optional()
1582
+ })
1583
+ ]);
1584
+ var PanelSelectSchema = z.tuple([z.string().min(1).max(200)]);
1585
+ var ProjectCaptureThumbnailSchema = z.tuple([AbsolutePath]);
1586
+ var ProjectGetThumbnailSchema = z.tuple([AbsolutePath]);
1587
+
1588
+ // src/main/services/simulator/custom-apis.ts
1589
+ function createSimulatorApiRegistry() {
1590
+ const handlers2 = /* @__PURE__ */ new Map();
1591
+ return {
1592
+ register(name, handler) {
1593
+ handlers2.set(name, handler);
1594
+ return () => {
1595
+ if (handlers2.get(name) === handler) handlers2.delete(name);
1596
+ };
1597
+ },
1598
+ list() {
1599
+ return Array.from(handlers2.keys());
1600
+ },
1601
+ async invoke(name, params) {
1602
+ const handler = handlers2.get(name);
1603
+ if (!handler) throw new Error(`Simulator API "${name}" is not registered`);
1604
+ return await handler(params);
1605
+ },
1606
+ clear() {
1607
+ handlers2.clear();
1608
+ }
1609
+ };
1610
+ }
1611
+ var simulatorApiRegistry = createSimulatorApiRegistry();
1612
+
1023
1613
  // src/main/ipc/simulator.ts
1024
- import { ipcMain as ipcMain2 } from "electron";
1025
1614
  function registerSimulatorIpc(ctx) {
1026
- ipcMain2.handle(SimulatorChannel.Attach, (_, simWcId, simWidth) => {
1615
+ return new IpcRegistry(ctx.senderPolicy).handle(SimulatorChannel.Attach, (_, ...args) => {
1616
+ const [simWcId, simWidth] = validate(SimulatorChannel.Attach, SimulatorAttachSchema, args);
1027
1617
  ctx.views.attachSimulator(simWcId, simWidth);
1028
- });
1029
- ipcMain2.handle(SimulatorChannel.Detach, () => {
1618
+ }).handle(SimulatorChannel.Detach, () => {
1030
1619
  ctx.views.detachSimulator();
1031
- });
1032
- ipcMain2.handle(SimulatorChannel.Resize, (_, simWidth) => {
1620
+ }).handle(SimulatorChannel.Resize, (_, ...args) => {
1621
+ const [simWidth] = validate(SimulatorChannel.Resize, SimulatorResizeSchema, args);
1033
1622
  ctx.views.resize(simWidth);
1034
- });
1035
- ipcMain2.handle(SimulatorChannel.SetVisible, (_, visible, simWidth) => {
1623
+ }).handle(SimulatorChannel.SetVisible, (_, ...args) => {
1624
+ const [visible, simWidth] = validate(SimulatorChannel.SetVisible, SimulatorSetVisibleSchema, args);
1036
1625
  ctx.views.setVisible(visible, simWidth);
1037
- });
1038
- ipcMain2.handle(WorkbenchChannel.GetPanelConfig, () => {
1039
- return ctx.panels;
1040
- });
1041
- ipcMain2.handle(WorkbenchChannel.GetApiNamespaces, () => {
1042
- return ctx.apiNamespaces;
1043
- });
1044
- ipcMain2.on(WorkbenchChannel.Reset, () => {
1045
- ctx.notify.workbenchReset();
1626
+ }).handle(SimulatorCustomApiChannel.List, () => {
1627
+ return simulatorApiRegistry.list();
1628
+ }).handle(SimulatorCustomApiChannel.Invoke, (_, ...args) => {
1629
+ const [name, params] = validate(SimulatorCustomApiChannel.Invoke, SimulatorCustomApiInvokeSchema, args);
1630
+ return simulatorApiRegistry.invoke(name, params);
1046
1631
  });
1047
1632
  }
1048
1633
 
1049
1634
  // src/main/ipc/panels.ts
1050
- import { ipcMain as ipcMain3, webContents as webContents2 } from "electron";
1051
1635
  var BUILTIN_PANELS = [
1052
1636
  { id: "wxml", label: "WXML" },
1053
1637
  { id: "appdata", label: "AppData" },
1054
1638
  { id: "storage", label: "Storage" }
1055
1639
  ];
1056
1640
  function registerPanelsIpc(ctx) {
1057
- ipcMain3.handle(PanelChannel.List, () => {
1641
+ return new IpcRegistry(ctx.senderPolicy).handle(PanelChannel.List, () => {
1058
1642
  return BUILTIN_PANELS.filter((p) => ctx.panels.includes(p.id));
1059
- });
1060
- ipcMain3.handle(PanelChannel.Eval, async (_, expression) => {
1061
- const simWcId = ctx.views.getSimulatorWebContentsId();
1062
- if (!ctx.workspace.hasActiveSession() || simWcId == null) return void 0;
1063
- const sim = webContents2.fromId(simWcId);
1064
- if (!sim || sim.isDestroyed()) return void 0;
1643
+ }).handle(PanelChannel.Eval, async (_event, ...args) => {
1644
+ const [expression] = validate(PanelChannel.Eval, PanelEvalSchema, args);
1645
+ if (!ctx.workspace.hasActiveSession()) return void 0;
1646
+ const sim = ctx.views.getSimulatorWebContents();
1647
+ if (!sim) return void 0;
1065
1648
  try {
1066
1649
  return await sim.executeJavaScript(expression);
1067
1650
  } catch {
1068
1651
  return void 0;
1069
1652
  }
1070
- });
1071
- ipcMain3.handle(PanelChannel.Select, (_event, _panelId) => {
1653
+ }).handle(PanelChannel.Select, (_event, ...args) => {
1654
+ validate(PanelChannel.Select, PanelSelectSchema, args);
1072
1655
  if (ctx.views.hasSimulatorView() && ctx.views.isSimulatorAdded()) {
1073
1656
  ctx.views.hideSimulator();
1074
1657
  }
1075
- });
1076
- ipcMain3.handle(PanelChannel.SelectSimulator, () => {
1658
+ }).handle(PanelChannel.SelectSimulator, () => {
1077
1659
  if (ctx.views.hasSimulatorView() && !ctx.views.isSimulatorAdded()) {
1078
1660
  ctx.views.showSimulator(ctx.views.getLastSimWidth());
1079
1661
  }
@@ -1081,79 +1663,272 @@ function registerPanelsIpc(ctx) {
1081
1663
  }
1082
1664
 
1083
1665
  // src/main/ipc/popover.ts
1084
- import { ipcMain as ipcMain4 } from "electron";
1085
1666
  function registerPopoverIpc(ctx) {
1086
- ipcMain4.handle(PopoverChannel.Show, (_event, data) => {
1667
+ return new IpcRegistry(ctx.senderPolicy).handle(PopoverChannel.Show, (_event, ...args) => {
1668
+ const [data] = validate(PopoverChannel.Show, PopoverShowSchema, args);
1087
1669
  ctx.views.showPopover(data);
1088
- });
1089
- ipcMain4.handle(PopoverChannel.Hide, () => ctx.views.hidePopover());
1090
- ipcMain4.on(PopoverChannel.Relaunch, (_, newConfig) => {
1670
+ }).handle(PopoverChannel.Hide, () => {
1671
+ ctx.views.hidePopover();
1672
+ }).on(PopoverChannel.Relaunch, (_event, ...args) => {
1673
+ const [newConfig] = validate(PopoverChannel.Relaunch, PopoverRelaunchSchema, args);
1091
1674
  ctx.views.hidePopover();
1092
1675
  ctx.notify.popoverRelaunch(newConfig);
1093
1676
  });
1094
1677
  }
1678
+ var popoverModule = {
1679
+ setup: (ctx) => registerPopoverIpc(ctx)
1680
+ };
1681
+
1682
+ // src/main/ipc/projects.ts
1683
+ import { app as app8, dialog } from "electron";
1684
+ import path11 from "path";
1685
+
1686
+ // src/main/services/projects/create-project-service.ts
1687
+ import fs6 from "node:fs";
1688
+ import path10 from "node:path";
1689
+ var DEFAULT_TEMPLATE_ID = "blank";
1690
+ async function createProject(input, ctx) {
1691
+ const name = (input.name ?? "").trim();
1692
+ if (name.length === 0) {
1693
+ throw new Error("Project name cannot be empty");
1694
+ }
1695
+ const target = input.path;
1696
+ if (!target || typeof target !== "string") {
1697
+ throw new Error("Project path is required");
1698
+ }
1699
+ if (fs6.existsSync(target)) {
1700
+ const stat = fs6.statSync(target);
1701
+ if (!stat.isDirectory()) {
1702
+ throw new Error(`Project path exists and is not a directory: ${target}`);
1703
+ }
1704
+ const entries = fs6.readdirSync(target);
1705
+ if (entries.length > 0) {
1706
+ throw new Error(
1707
+ `Project path is not empty (refusing to overwrite): ${target}`
1708
+ );
1709
+ }
1710
+ }
1711
+ const templateId = input.templateId ?? DEFAULT_TEMPLATE_ID;
1712
+ const template = ctx.templates.find((t) => t.id === templateId);
1713
+ if (!template) {
1714
+ throw new Error(`Template not found: ${templateId}`);
1715
+ }
1716
+ if (!template.source && !template.generate) {
1717
+ throw new Error(
1718
+ `Template '${templateId}' has neither a source directory nor a generate function`
1719
+ );
1720
+ }
1721
+ fs6.mkdirSync(target, { recursive: true });
1722
+ if (template.generate) {
1723
+ await template.generate(target, { name });
1724
+ } else if (template.source) {
1725
+ if (!fs6.existsSync(template.source.path)) {
1726
+ throw new Error(
1727
+ `Template source missing on disk: ${template.source.path}`
1728
+ );
1729
+ }
1730
+ fs6.cpSync(template.source.path, target, {
1731
+ recursive: true,
1732
+ force: true
1733
+ });
1734
+ }
1735
+ const cfgPath = path10.join(target, "project.config.json");
1736
+ let cfg = {};
1737
+ if (fs6.existsSync(cfgPath)) {
1738
+ try {
1739
+ cfg = JSON.parse(fs6.readFileSync(cfgPath, "utf-8"));
1740
+ } catch {
1741
+ cfg = {};
1742
+ }
1743
+ }
1744
+ cfg.projectname = name;
1745
+ fs6.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
1746
+ const created = await ctx.projectsProvider.addProject(target);
1747
+ return created;
1748
+ }
1095
1749
 
1096
1750
  // src/main/ipc/projects.ts
1097
- import { ipcMain as ipcMain5, dialog } from "electron";
1098
1751
  function registerProjectsIpc(ctx) {
1099
- ipcMain5.handle(ProjectsChannel.List, () => ctx.workspace.listProjects());
1100
- ipcMain5.handle(DialogChannel.OpenDirectory, async () => {
1101
- const result = await dialog.showOpenDialog(ctx.mainWindow, {
1752
+ return new IpcRegistry(ctx.senderPolicy).handle(ProjectsChannel.List, () => {
1753
+ return ctx.workspace.listProjects();
1754
+ }).handle(DialogChannel.OpenDirectory, async () => {
1755
+ const result = await dialog.showOpenDialog(ctx.windows.mainWindow, {
1102
1756
  properties: ["openDirectory"],
1103
1757
  title: "\u9009\u62E9\u5C0F\u7A0B\u5E8F\u9879\u76EE\u76EE\u5F55"
1104
1758
  });
1105
1759
  return result.canceled ? null : result.filePaths[0];
1760
+ }).handle(ProjectsChannel.Add, async (_event, ...args) => {
1761
+ const [dirPath] = validate(ProjectsChannel.Add, ProjectsAddSchema, args);
1762
+ const dirError = await ctx.workspace.validateProjectDir(dirPath);
1763
+ if (dirError) {
1764
+ await dialog.showMessageBox(ctx.windows.mainWindow, {
1765
+ type: "error",
1766
+ title: "\u65E0\u6CD5\u5BFC\u5165\u9879\u76EE",
1767
+ message: "\u8BE5\u76EE\u5F55\u4E0D\u662F\u6709\u6548\u7684\u5C0F\u7A0B\u5E8F\u9879\u76EE",
1768
+ detail: dirError,
1769
+ buttons: ["\u786E\u5B9A"]
1770
+ });
1771
+ throw new Error(dirError);
1772
+ }
1773
+ const duplicate = await ctx.workspace.hasProject(dirPath);
1774
+ const project = await ctx.workspace.addProject(dirPath);
1775
+ if (duplicate) {
1776
+ await dialog.showMessageBox(ctx.windows.mainWindow, {
1777
+ type: "info",
1778
+ title: "\u9879\u76EE\u5DF2\u5B58\u5728",
1779
+ message: "\u8BE5\u9879\u76EE\u5DF2\u5728\u5217\u8868\u4E2D",
1780
+ detail: dirPath,
1781
+ buttons: ["\u786E\u5B9A"]
1782
+ });
1783
+ }
1784
+ return project;
1785
+ }).handle(ProjectsChannel.Remove, (_event, ...args) => {
1786
+ const [dirPath] = validate(ProjectsChannel.Remove, ProjectsRemoveSchema, args);
1787
+ return ctx.workspace.removeProject(dirPath);
1788
+ }).handle(ProjectsChannel.ListTemplates, () => {
1789
+ return sanitizeTemplates(ctx.projectTemplates ?? []);
1790
+ }).handle(ProjectsChannel.OpenCreateDialog, async () => {
1791
+ if (!ctx.customCreateProjectDialog) return null;
1792
+ const sanitized = sanitizeTemplates(ctx.projectTemplates ?? []);
1793
+ return await ctx.customCreateProjectDialog({
1794
+ parentWindow: ctx.windows.mainWindow,
1795
+ templates: sanitized
1796
+ });
1797
+ }).handle(ProjectsChannel.Create, async (_event, ...args) => {
1798
+ const [raw] = args;
1799
+ if (!raw || typeof raw !== "object") {
1800
+ throw new Error("projects:create expects a CreateProjectInput object");
1801
+ }
1802
+ const input = raw;
1803
+ let project;
1804
+ try {
1805
+ project = await createProject(input, {
1806
+ templates: ctx.projectTemplates ?? [],
1807
+ projectsProvider: ctx.projectsProvider
1808
+ });
1809
+ } catch (err) {
1810
+ await dialog.showMessageBox(ctx.windows.mainWindow, {
1811
+ type: "error",
1812
+ title: "\u521B\u5EFA\u9879\u76EE\u5931\u8D25",
1813
+ message: "\u65E0\u6CD5\u521B\u5EFA\u9879\u76EE",
1814
+ detail: err instanceof Error ? err.message : String(err),
1815
+ buttons: ["\u786E\u5B9A"]
1816
+ });
1817
+ throw err;
1818
+ }
1819
+ try {
1820
+ const settings = loadWorkbenchSettings();
1821
+ const newBase = path11.dirname(input.path);
1822
+ if (newBase && settings.lastCreateBaseDir !== newBase) {
1823
+ saveWorkbenchSettings({ ...settings, lastCreateBaseDir: newBase });
1824
+ }
1825
+ } catch (err) {
1826
+ console.warn("[projects:create] failed to persist lastCreateBaseDir", err);
1827
+ }
1828
+ return project;
1829
+ }).handle(ProjectsChannel.GetCreateDefaults, () => {
1830
+ const settings = loadWorkbenchSettings();
1831
+ const baseDir = settings.lastCreateBaseDir ?? safeAppPath("documents") ?? safeAppPath("home") ?? "";
1832
+ return { baseDir };
1106
1833
  });
1107
- ipcMain5.handle(
1108
- ProjectsChannel.Add,
1109
- (_, dirPath) => ctx.workspace.addProject(dirPath)
1110
- );
1111
- ipcMain5.handle(
1112
- ProjectsChannel.Remove,
1113
- (_, dirPath) => ctx.workspace.removeProject(dirPath)
1114
- );
1115
1834
  }
1835
+ function safeAppPath(name) {
1836
+ try {
1837
+ return app8.getPath(name);
1838
+ } catch {
1839
+ return null;
1840
+ }
1841
+ }
1842
+ var projectsModule = {
1843
+ setup: (ctx) => registerProjectsIpc(ctx)
1844
+ };
1116
1845
 
1117
1846
  // src/main/ipc/session.ts
1118
- import { ipcMain as ipcMain6 } from "electron";
1119
1847
  function registerSessionIpc(ctx) {
1120
- ipcMain6.handle(
1121
- ProjectChannel.Open,
1122
- (_, projectPath) => ctx.workspace.openProject(projectPath)
1123
- );
1124
- ipcMain6.handle(
1125
- ProjectChannel.GetPages,
1126
- (_, projectPath) => ctx.workspace.getProjectPages(projectPath)
1127
- );
1128
- ipcMain6.handle(
1129
- ProjectChannel.GetCompileConfig,
1130
- (_, projectPath) => ctx.workspace.getCompileConfig(projectPath)
1131
- );
1132
- ipcMain6.handle(
1133
- ProjectChannel.SaveCompileConfig,
1134
- (_, projectPath, config) => ctx.workspace.saveCompileConfig(projectPath, config)
1135
- );
1136
- ipcMain6.handle(ProjectChannel.Close, () => ctx.workspace.closeProject());
1848
+ return new IpcRegistry(ctx.senderPolicy).handle(ProjectChannel.Open, (_, ...args) => {
1849
+ const [projectPath] = validate(ProjectChannel.Open, ProjectOpenSchema, args);
1850
+ return ctx.workspace.openProject(projectPath);
1851
+ }).handle(ProjectChannel.GetPages, (_, ...args) => {
1852
+ const [projectPath] = validate(ProjectChannel.GetPages, ProjectGetPagesSchema, args);
1853
+ return ctx.workspace.getProjectPages(projectPath);
1854
+ }).handle(ProjectChannel.GetCompileConfig, (_, ...args) => {
1855
+ const [projectPath] = validate(
1856
+ ProjectChannel.GetCompileConfig,
1857
+ ProjectGetCompileConfigSchema,
1858
+ args
1859
+ );
1860
+ return ctx.workspace.getCompileConfig(projectPath);
1861
+ }).handle(ProjectChannel.SaveCompileConfig, (_, ...args) => {
1862
+ const [projectPath, config] = validate(
1863
+ ProjectChannel.SaveCompileConfig,
1864
+ ProjectSaveCompileConfigSchema,
1865
+ args
1866
+ );
1867
+ return ctx.workspace.saveCompileConfig(projectPath, config);
1868
+ }).handle(ProjectChannel.Close, () => {
1869
+ return ctx.workspace.closeProject();
1870
+ }).handle(ProjectChannel.CaptureThumbnail, (_, ...args) => {
1871
+ const [projectPath] = validate(
1872
+ ProjectChannel.CaptureThumbnail,
1873
+ ProjectCaptureThumbnailSchema,
1874
+ args
1875
+ );
1876
+ return ctx.workspace.captureThumbnail(projectPath);
1877
+ }).handle(ProjectChannel.GetThumbnail, (_, ...args) => {
1878
+ const [projectPath] = validate(
1879
+ ProjectChannel.GetThumbnail,
1880
+ ProjectGetThumbnailSchema,
1881
+ args
1882
+ );
1883
+ return ctx.workspace.getThumbnail(projectPath);
1884
+ });
1885
+ }
1886
+ var sessionModule = {
1887
+ setup: (ctx) => registerSessionIpc(ctx)
1888
+ };
1889
+
1890
+ // src/main/ipc/settings.ts
1891
+ import { app as app9 } from "electron";
1892
+
1893
+ // src/main/services/mcp/status.ts
1894
+ var status = { running: false, port: null, error: null };
1895
+ function getMcpStatus() {
1896
+ return { ...status };
1897
+ }
1898
+ function recordMcpStarted(port) {
1899
+ status = { running: true, port, error: null };
1900
+ }
1901
+ function recordMcpFailed(error) {
1902
+ status = { running: false, port: null, error };
1903
+ }
1904
+ function recordMcpStopped() {
1905
+ status = { running: false, port: null, error: null };
1137
1906
  }
1138
1907
 
1139
1908
  // src/main/ipc/settings.ts
1140
- import { app as app7, ipcMain as ipcMain7 } from "electron";
1141
1909
  function registerSettingsIpc(ctx) {
1142
- ipcMain7.handle(WorkbenchSettingsChannel.Get, () => {
1910
+ return new IpcRegistry(ctx.senderPolicy).handle(WorkbenchSettingsChannel.Get, () => {
1143
1911
  return loadWorkbenchSettings();
1144
- });
1145
- ipcMain7.handle(WorkbenchSettingsChannel.Save, (_, settings) => {
1912
+ }).handle(WorkbenchSettingsChannel.Save, (_, ...args) => {
1913
+ const [settings] = validate(
1914
+ WorkbenchSettingsChannel.Save,
1915
+ WorkbenchSettingsSaveSchema,
1916
+ args
1917
+ );
1146
1918
  saveWorkbenchSettings(settings);
1147
1919
  applyTheme(settings.theme ?? "system");
1148
1920
  return { success: true };
1149
- });
1150
- ipcMain7.handle(WorkbenchSettingsChannel.SetTheme, (_, theme) => {
1921
+ }).handle(WorkbenchSettingsChannel.SetTheme, (_, ...args) => {
1922
+ const [theme] = validate(
1923
+ WorkbenchSettingsChannel.SetTheme,
1924
+ WorkbenchSettingsSetThemeSchema,
1925
+ args
1926
+ );
1151
1927
  applyTheme(theme);
1152
- });
1153
- ipcMain7.handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
1928
+ }).handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
1154
1929
  const settings = loadWorkbenchSettings();
1155
- const switchValue = app7.commandLine.getSwitchValue("remote-debugging-port");
1156
- const implicitDevDefault = !app7.isPackaged && !settings.cdp.enabled && switchValue === "9222";
1930
+ const switchValue = app9.commandLine.getSwitchValue("remote-debugging-port");
1931
+ const implicitDevDefault = !app9.isPackaged && !settings.cdp.enabled && switchValue === String(DEFAULT_CDP_PORT);
1157
1932
  return {
1158
1933
  configured: settings.cdp.enabled,
1159
1934
  port: settings.cdp.port,
@@ -1161,62 +1936,94 @@ function registerSettingsIpc(ctx) {
1161
1936
  activePort: switchValue ? parseInt(switchValue, 10) : null,
1162
1937
  implicitDevDefault
1163
1938
  };
1164
- });
1165
- ipcMain7.handle(WorkbenchSettingsChannel.SetVisible, async (_, visible) => {
1939
+ }).handle(WorkbenchSettingsChannel.GetMcpStatus, () => {
1940
+ const settings = loadWorkbenchSettings();
1941
+ const runtime = getMcpStatus();
1942
+ return {
1943
+ configured: settings.mcp.enabled,
1944
+ configuredPort: settings.mcp.port,
1945
+ running: runtime.running,
1946
+ activePort: runtime.port,
1947
+ error: runtime.error
1948
+ };
1949
+ }).handle(WorkbenchSettingsChannel.SetVisible, async (_, ...args) => {
1950
+ const [visible] = validate(
1951
+ WorkbenchSettingsChannel.SetVisible,
1952
+ WorkbenchSettingsSetVisibleSchema,
1953
+ args
1954
+ );
1166
1955
  if (visible) {
1167
1956
  await openSettingsWindow(ctx);
1168
1957
  } else {
1169
- ctx.workbenchSettingsWindow?.close();
1958
+ ctx.windows.closeSettingsWindow();
1170
1959
  }
1171
- });
1172
- ipcMain7.handle(SettingsChannel.SetVisible, async (_, visible) => {
1960
+ }).handle(SettingsChannel.SetVisible, async (_, ...args) => {
1961
+ const [visible] = validate(SettingsChannel.SetVisible, SettingsSetVisibleSchema, args);
1173
1962
  if (visible) {
1174
1963
  await ctx.views.showSettings();
1964
+ const projectPath = ctx.workspace.getProjectPath();
1175
1965
  ctx.notify.settingsInit({
1176
- projectPath: ctx.workspace.getProjectPath(),
1177
- config: ctx.workspace.getCompileConfig(ctx.workspace.getProjectPath()),
1178
- projectSettings: ctx.workspace.getProjectSettings(ctx.workspace.getProjectPath())
1966
+ projectPath,
1967
+ config: await ctx.workspace.getCompileConfig(projectPath),
1968
+ projectSettings: ctx.workspace.getProjectSettings(projectPath)
1179
1969
  });
1180
1970
  } else {
1181
1971
  ctx.views.hideSettings();
1182
1972
  ctx.notify.settingsClosed();
1183
1973
  }
1184
- });
1185
- ipcMain7.on(SettingsChannel.ConfigChanged, (_, config) => {
1186
- if (ctx.workspace.getProjectPath()) {
1187
- ctx.workspace.saveCompileConfig(ctx.workspace.getProjectPath(), config);
1974
+ }).on(SettingsChannel.ConfigChanged, async (_, ...args) => {
1975
+ const [config] = validate(
1976
+ SettingsChannel.ConfigChanged,
1977
+ SettingsConfigChangedSchema,
1978
+ args
1979
+ );
1980
+ const projectPath = ctx.workspace.getProjectPath();
1981
+ if (projectPath) {
1982
+ await ctx.workspace.saveCompileConfig(projectPath, config);
1188
1983
  }
1189
1984
  ctx.notify.settingsChanged(config);
1985
+ }).on(SettingsChannel.ProjectSettingsChanged, (_, ...args) => {
1986
+ const [patch] = validate(
1987
+ SettingsChannel.ProjectSettingsChanged,
1988
+ SettingsProjectSettingsChangedSchema,
1989
+ args
1990
+ );
1991
+ ctx.workspace.updateProjectSettings(
1992
+ ctx.workspace.getProjectPath(),
1993
+ patch
1994
+ );
1190
1995
  });
1191
- ipcMain7.on(
1192
- SettingsChannel.ProjectSettingsChanged,
1193
- (_, patch) => {
1194
- ctx.workspace.updateProjectSettings(ctx.workspace.getProjectPath(), patch);
1195
- }
1196
- );
1197
1996
  }
1997
+ var settingsModule = {
1998
+ setup: (ctx) => registerSettingsIpc(ctx)
1999
+ };
1198
2000
 
1199
2001
  // src/main/ipc/toolbar.ts
1200
- import { ipcMain as ipcMain8 } from "electron";
1201
2002
  function registerToolbarIpc(ctx) {
1202
- ipcMain8.handle(ToolbarChannel.GetActions, async () => {
2003
+ return new IpcRegistry(ctx.senderPolicy).handle(ToolbarChannel.GetActions, async () => {
1203
2004
  if (ctx.toolbarActions) return ctx.toolbarActions();
1204
2005
  return [];
1205
2006
  });
1206
2007
  }
1207
2008
 
2009
+ // src/main/ipc/simulator-module.ts
2010
+ var simulatorModule = {
2011
+ setup: (ctx) => {
2012
+ const reg = new DisposableRegistry();
2013
+ reg.add(registerSimulatorIpc(ctx));
2014
+ reg.add(registerPanelsIpc(ctx));
2015
+ reg.add(registerToolbarIpc(ctx));
2016
+ return reg;
2017
+ }
2018
+ };
2019
+
1208
2020
  // src/main/services/automation/index.ts
1209
- import { ipcMain as ipcMain9 } from "electron";
1210
2021
  import { WebSocketServer } from "ws";
1211
2022
 
1212
2023
  // src/main/services/automation/exec.ts
1213
- import { webContents as webContents3 } from "electron";
1214
2024
  function getSimulator(ctx) {
1215
- const simWcId = ctx.views.getSimulatorWebContentsId();
1216
- if (!ctx.workspace.hasActiveSession() || simWcId == null) return null;
1217
- const sim = webContents3.fromId(simWcId);
1218
- if (!sim || sim.isDestroyed()) return null;
1219
- return sim;
2025
+ if (!ctx.workspace.hasActiveSession()) return null;
2026
+ return ctx.views.getSimulatorWebContents();
1220
2027
  }
1221
2028
  async function evalInSim(ctx, expression) {
1222
2029
  const sim = getSimulator(ctx);
@@ -1224,8 +2031,11 @@ async function evalInSim(ctx, expression) {
1224
2031
  return sim.executeJavaScript(expression);
1225
2032
  }
1226
2033
  function buildElAccess(ref, varName = "el") {
1227
- const escaped = ref.selector.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1228
- return `const ${varName} = _doc.querySelectorAll('${escaped}')[${ref.index}]`;
2034
+ return `const ${varName} = _doc.querySelectorAll(${JSON.stringify(ref.selector)})[${ref.index}]`;
2035
+ }
2036
+ function evalInElement(ctx, ref, body, varName = "el") {
2037
+ return evalInSim(ctx, inIframe(`${buildElAccess(ref, varName)}
2038
+ ${body}`));
1229
2039
  }
1230
2040
  function inIframe(code) {
1231
2041
  return `(() => {
@@ -1245,47 +2055,57 @@ toolHandlers["Tool.close"] = async (ctx) => {
1245
2055
  return {};
1246
2056
  };
1247
2057
 
2058
+ // src/shared/simulator-route.ts
2059
+ function decodePageSpec(value) {
2060
+ const qIdx = value.indexOf("?");
2061
+ if (qIdx < 0) return { pagePath: value, query: {} };
2062
+ const pagePath = value.slice(0, qIdx);
2063
+ const query = {};
2064
+ for (const pair of value.slice(qIdx + 1).split("&")) {
2065
+ if (!pair) continue;
2066
+ const eqIdx = pair.indexOf("=");
2067
+ const k = eqIdx >= 0 ? pair.slice(0, eqIdx) : pair;
2068
+ const v = eqIdx >= 0 ? pair.slice(eqIdx + 1) : "";
2069
+ if (k) query[decodeURIComponent(k)] = decodeURIComponent(v);
2070
+ }
2071
+ return { pagePath, query };
2072
+ }
2073
+ function parseLocationRoute(search) {
2074
+ const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
2075
+ const appId = params.get("appId");
2076
+ const entryRaw = params.get("entry");
2077
+ if (!appId || !entryRaw) return null;
2078
+ const entry = decodePageSpec(entryRaw);
2079
+ const pageRaw = params.get("page");
2080
+ const current = pageRaw && pageRaw !== entryRaw ? decodePageSpec(pageRaw) : entry;
2081
+ return { appId, entry, current };
2082
+ }
2083
+
1248
2084
  // src/main/services/automation/handlers/app.ts
1249
2085
  var appHandlers = {};
2086
+ async function readRoute(ctx) {
2087
+ const { search } = await evalInSim(
2088
+ ctx,
2089
+ `({ search: location.search })`
2090
+ );
2091
+ return parseLocationRoute(search);
2092
+ }
1250
2093
  appHandlers["App.getCurrentPage"] = async (ctx) => {
1251
- const hash = await evalInSim(ctx, "location.hash");
1252
- const clean = hash.replace(/^#/, "");
1253
- const qIdx = clean.indexOf("?");
1254
- const pathPart = qIdx >= 0 ? clean.substring(0, qIdx) : clean;
1255
- const queryPart = qIdx >= 0 ? clean.substring(qIdx + 1) : "";
1256
- let pagePath;
1257
- if (pathPart.includes("|")) {
1258
- const parts = pathPart.split("|");
1259
- pagePath = parts[parts.length - 1] ?? "";
1260
- } else {
1261
- const slashIdx = pathPart.indexOf("/");
1262
- pagePath = slashIdx >= 0 ? pathPart.substring(slashIdx + 1) : pathPart;
1263
- }
1264
- const query = {};
1265
- if (queryPart) {
1266
- for (const pair of queryPart.split("&")) {
1267
- const [k, v] = pair.split("=");
1268
- if (k) query[k] = decodeURIComponent(v || "");
1269
- }
1270
- }
2094
+ const route = await readRoute(ctx);
1271
2095
  const iframeCount = await evalInSim(ctx, `document.querySelectorAll('iframe').length`);
1272
- return { pageId: iframeCount, path: pagePath, query };
2096
+ return {
2097
+ pageId: iframeCount,
2098
+ path: route?.current.pagePath ?? "",
2099
+ query: route?.current.query ?? {}
2100
+ };
1273
2101
  };
1274
2102
  appHandlers["App.getPageStack"] = async (ctx) => {
1275
- const hash = await evalInSim(ctx, "location.hash");
1276
- const clean = hash.replace(/^#/, "").replace(/\?.*$/, "");
2103
+ const route = await readRoute(ctx);
1277
2104
  const pageStack = [];
1278
- if (clean.includes("|")) {
1279
- const parts = clean.split("|");
1280
- for (let i = 1; i < parts.length; i++) {
1281
- pageStack.push({ pageId: i, path: parts[i] ?? "", query: {} });
1282
- }
1283
- } else {
1284
- const slashIdx = clean.indexOf("/");
1285
- const currentPath = slashIdx >= 0 ? clean.substring(slashIdx + 1) : clean;
1286
- if (currentPath) {
1287
- pageStack.push({ pageId: 1, path: currentPath, query: {} });
1288
- }
2105
+ if (!route) return { pageStack };
2106
+ pageStack.push({ pageId: 1, path: route.entry.pagePath, query: route.entry.query });
2107
+ if (route.current.pagePath !== route.entry.pagePath) {
2108
+ pageStack.push({ pageId: 2, path: route.current.pagePath, query: route.current.query });
1289
2109
  }
1290
2110
  return { pageStack };
1291
2111
  };
@@ -1298,18 +2118,29 @@ appHandlers["App.callWxMethod"] = async (ctx, params) => {
1298
2118
  if (url) {
1299
2119
  const cleanUrl = url.startsWith("/") ? url : `/${url}`;
1300
2120
  const clicked = await evalInSim(ctx, inIframe(`
1301
- const el = _doc.querySelector('[data-path="${cleanUrl}"]')
2121
+ const path = ${JSON.stringify(cleanUrl)}
2122
+ const el = Array.from(_doc.querySelectorAll('[data-path]'))
2123
+ .find(e => e.getAttribute('data-path') === path)
1302
2124
  if (el) { el.click(); return true }
1303
2125
  return false
1304
2126
  `)).catch(() => false);
1305
2127
  if (!clicked) {
1306
- const hash = await evalInSim(ctx, "location.hash");
1307
- const appId = hash.replace(/^#/, "").split("/")[0];
1308
- if (method === "reLaunch" || method === "redirectTo") {
1309
- await evalInSim(ctx, `location.href = location.pathname + '#${appId}${cleanUrl}'`);
1310
- } else {
1311
- await evalInSim(ctx, `location.hash = '#${appId}${cleanUrl}'`);
1312
- }
2128
+ const apiName = JSON.stringify(method);
2129
+ const urlJson = JSON.stringify(cleanUrl);
2130
+ await evalInSim(
2131
+ ctx,
2132
+ `(() => {
2133
+ const iframes = Array.from(document.querySelectorAll('iframe'))
2134
+ for (const f of iframes) {
2135
+ try {
2136
+ if (f.contentWindow && f.contentWindow.wx && typeof f.contentWindow.wx[${apiName}] === 'function') {
2137
+ f.contentWindow.wx[${apiName}]({ url: ${urlJson} })
2138
+ return
2139
+ }
2140
+ } catch (_) {}
2141
+ }
2142
+ })()`
2143
+ );
1313
2144
  }
1314
2145
  await new Promise((r) => setTimeout(r, 2e3));
1315
2146
  }
@@ -1444,109 +2275,97 @@ pageHandlers["Page.getWindowProperties"] = async (ctx, params) => {
1444
2275
  var elementHandlers = {};
1445
2276
  elementHandlers["Element.tap"] = async (ctx, params) => {
1446
2277
  const ref = getElementRef(params);
1447
- await evalInSim(ctx, inIframe(`
1448
- ${buildElAccess(ref)}
2278
+ await evalInElement(ctx, ref, `
1449
2279
  if (!el) throw new Error('Element not found')
1450
2280
  el.click()
1451
- `));
2281
+ `);
1452
2282
  return {};
1453
2283
  };
1454
2284
  elementHandlers["Element.triggerEvent"] = async (ctx, params) => {
1455
2285
  const ref = getElementRef(params);
1456
2286
  const type = params.type;
1457
2287
  const detail = params.detail || {};
1458
- await evalInSim(ctx, inIframe(`
1459
- ${buildElAccess(ref)}
2288
+ await evalInElement(ctx, ref, `
1460
2289
  if (!el) throw new Error('Element not found')
1461
2290
  el.dispatchEvent(new CustomEvent('${type}', { detail: ${JSON.stringify(detail)}, bubbles: true }))
1462
- `));
2291
+ `);
1463
2292
  return {};
1464
2293
  };
1465
2294
  elementHandlers["Element.touchstart"] = async (ctx, params) => {
1466
2295
  const ref = getElementRef(params);
1467
- await evalInSim(ctx, inIframe(`
1468
- ${buildElAccess(ref)}
2296
+ await evalInElement(ctx, ref, `
1469
2297
  if (el) el.dispatchEvent(new TouchEvent('touchstart', { bubbles: true }))
1470
- `));
2298
+ `);
1471
2299
  return {};
1472
2300
  };
1473
2301
  elementHandlers["Element.touchmove"] = async (ctx, params) => {
1474
2302
  const ref = getElementRef(params);
1475
- await evalInSim(ctx, inIframe(`
1476
- ${buildElAccess(ref)}
2303
+ await evalInElement(ctx, ref, `
1477
2304
  if (el) el.dispatchEvent(new TouchEvent('touchmove', { bubbles: true }))
1478
- `));
2305
+ `);
1479
2306
  return {};
1480
2307
  };
1481
2308
  elementHandlers["Element.touchend"] = async (ctx, params) => {
1482
2309
  const ref = getElementRef(params);
1483
- await evalInSim(ctx, inIframe(`
1484
- ${buildElAccess(ref)}
2310
+ await evalInElement(ctx, ref, `
1485
2311
  if (el) el.dispatchEvent(new TouchEvent('touchend', { bubbles: true }))
1486
- `));
2312
+ `);
1487
2313
  return {};
1488
2314
  };
1489
2315
  elementHandlers["Element.getDOMProperties"] = async (ctx, params) => {
1490
2316
  const ref = getElementRef(params);
1491
2317
  const names = params.names;
1492
- const properties = await evalInSim(ctx, inIframe(`
1493
- ${buildElAccess(ref)}
2318
+ const properties = await evalInElement(ctx, ref, `
1494
2319
  if (!el) return ${JSON.stringify(names.map(() => null))}
1495
2320
  return ${JSON.stringify(names)}.map(n => el[n])
1496
- `));
2321
+ `);
1497
2322
  return { properties };
1498
2323
  };
1499
2324
  elementHandlers["Element.getAttributes"] = async (ctx, params) => {
1500
2325
  const ref = getElementRef(params);
1501
2326
  const names = params.names;
1502
- const attributes = await evalInSim(ctx, inIframe(`
1503
- ${buildElAccess(ref)}
2327
+ const attributes = await evalInElement(ctx, ref, `
1504
2328
  if (!el) return ${JSON.stringify(names.map(() => null))}
1505
2329
  return ${JSON.stringify(names)}.map(n => el.getAttribute(n))
1506
- `));
2330
+ `);
1507
2331
  return { attributes };
1508
2332
  };
1509
2333
  elementHandlers["Element.getStyles"] = async (ctx, params) => {
1510
2334
  const ref = getElementRef(params);
1511
2335
  const names = params.names;
1512
- const styles = await evalInSim(ctx, inIframe(`
1513
- ${buildElAccess(ref)}
2336
+ const styles = await evalInElement(ctx, ref, `
1514
2337
  if (!el) return ${JSON.stringify(names.map(() => ""))}
1515
2338
  const cs = getComputedStyle(el)
1516
2339
  return ${JSON.stringify(names)}.map(n => cs.getPropertyValue(n))
1517
- `));
2340
+ `);
1518
2341
  return { styles };
1519
2342
  };
1520
2343
  elementHandlers["Element.getWXML"] = async (ctx, params) => {
1521
2344
  const ref = getElementRef(params);
1522
2345
  const type = params.type || "inner";
1523
2346
  const prop = type === "outer" ? "outerHTML" : "innerHTML";
1524
- const wxml = await evalInSim(ctx, inIframe(`
1525
- ${buildElAccess(ref)}
2347
+ const wxml = await evalInElement(ctx, ref, `
1526
2348
  return el ? el.${prop} : ''
1527
- `));
2349
+ `);
1528
2350
  return { wxml };
1529
2351
  };
1530
2352
  elementHandlers["Element.getOffset"] = async (ctx, params) => {
1531
2353
  const ref = getElementRef(params);
1532
- return evalInSim(ctx, inIframe(`
1533
- ${buildElAccess(ref)}
2354
+ return evalInElement(ctx, ref, `
1534
2355
  if (!el) return { left: 0, top: 0 }
1535
2356
  const r = el.getBoundingClientRect()
1536
2357
  return { left: r.left, top: r.top }
1537
- `));
2358
+ `);
1538
2359
  };
1539
2360
  elementHandlers["Element.getElement"] = async (ctx, params) => {
1540
2361
  const parentRef = getElementRef(params);
1541
2362
  const selector = params.selector;
1542
- const escaped = selector.replace(/'/g, "\\'");
1543
- const info = await evalInSim(ctx, inIframe(`
1544
- ${buildElAccess(parentRef, "parent")}
2363
+ const info = await evalInElement(ctx, parentRef, `
1545
2364
  if (!parent) return null
1546
- const child = parent.querySelector('${escaped}')
2365
+ const child = parent.querySelector(${JSON.stringify(selector)})
1547
2366
  if (!child) return null
1548
2367
  return { tagName: child.tagName.toLowerCase() }
1549
- `));
2368
+ `, "parent");
1550
2369
  if (!info) throw new Error(`Child element not found: ${selector}`);
1551
2370
  const combinedSelector = `${parentRef.selector} ${selector}`;
1552
2371
  const elementId = registerElement(combinedSelector, 0, parentRef.pageId);
@@ -1555,14 +2374,12 @@ elementHandlers["Element.getElement"] = async (ctx, params) => {
1555
2374
  elementHandlers["Element.getElements"] = async (ctx, params) => {
1556
2375
  const parentRef = getElementRef(params);
1557
2376
  const selector = params.selector;
1558
- const escaped = selector.replace(/'/g, "\\'");
1559
- const items = await evalInSim(ctx, inIframe(`
1560
- ${buildElAccess(parentRef, "parent")}
2377
+ const items = await evalInElement(ctx, parentRef, `
1561
2378
  if (!parent) return []
1562
- return Array.from(parent.querySelectorAll('${escaped}')).map(el => ({
2379
+ return Array.from(parent.querySelectorAll(${JSON.stringify(selector)})).map(el => ({
1563
2380
  tagName: el.tagName.toLowerCase(),
1564
2381
  }))
1565
- `));
2382
+ `, "parent");
1566
2383
  const combinedSelector = `${parentRef.selector} ${selector}`;
1567
2384
  return {
1568
2385
  elements: items.map((item, i) => ({
@@ -1595,7 +2412,6 @@ var handlers = {
1595
2412
  ...elementHandlers
1596
2413
  };
1597
2414
  var currentPort = null;
1598
- var portIpcRegistered = false;
1599
2415
  async function startAutomationServer(ctx, port = 0) {
1600
2416
  const wss = new WebSocketServer({ port });
1601
2417
  const clients = /* @__PURE__ */ new Set();
@@ -1606,10 +2422,10 @@ async function startAutomationServer(ctx, port = 0) {
1606
2422
  const addr = wss.address();
1607
2423
  const resolvedPort = typeof addr === "object" && addr ? addr.port : port;
1608
2424
  currentPort = resolvedPort;
1609
- if (!portIpcRegistered) {
1610
- portIpcRegistered = true;
1611
- ipcMain9.handle(AutomationChannel.GetPort, () => currentPort);
1612
- }
2425
+ const portIpc = new IpcRegistry(ctx.senderPolicy);
2426
+ portIpc.handle(AutomationChannel.GetPort, () => {
2427
+ return currentPort;
2428
+ });
1613
2429
  function broadcast(event) {
1614
2430
  const msg = JSON.stringify(event);
1615
2431
  for (const ws of clients) {
@@ -1617,14 +2433,46 @@ async function startAutomationServer(ctx, port = 0) {
1617
2433
  }
1618
2434
  }
1619
2435
  let consoleForwardingSetup = false;
2436
+ let pollInterval = null;
2437
+ let pollStopTimer = null;
2438
+ let attachedSim = null;
2439
+ let ipcMessageHostHandler = null;
2440
+ let simDestroyedHandler = null;
2441
+ function detachConsoleForwarding() {
2442
+ if (attachedSim) {
2443
+ if (ipcMessageHostHandler) {
2444
+ try {
2445
+ if (!attachedSim.isDestroyed()) {
2446
+ ;
2447
+ attachedSim.removeListener("ipc-message-host", ipcMessageHostHandler);
2448
+ }
2449
+ } catch {
2450
+ }
2451
+ }
2452
+ if (simDestroyedHandler) {
2453
+ try {
2454
+ if (!attachedSim.isDestroyed()) {
2455
+ attachedSim.removeListener("destroyed", simDestroyedHandler);
2456
+ }
2457
+ } catch {
2458
+ }
2459
+ }
2460
+ }
2461
+ attachedSim = null;
2462
+ ipcMessageHostHandler = null;
2463
+ simDestroyedHandler = null;
2464
+ }
1620
2465
  function setupConsoleForwarding() {
1621
2466
  if (consoleForwardingSetup) return;
1622
2467
  consoleForwardingSetup = true;
1623
- const check = setInterval(() => {
2468
+ pollInterval = setInterval(() => {
1624
2469
  const sim = getSimulator(ctx);
1625
2470
  if (!sim) return;
1626
- clearInterval(check);
1627
- sim.on("ipc-message-host", (_event, channel, data) => {
2471
+ if (pollInterval) {
2472
+ clearInterval(pollInterval);
2473
+ pollInterval = null;
2474
+ }
2475
+ const onIpcMessageHost = (_event, channel, data) => {
1628
2476
  if (channel === SimulatorChannel.Console) {
1629
2477
  const logData = data;
1630
2478
  broadcast({
@@ -1632,9 +2480,26 @@ async function startAutomationServer(ctx, port = 0) {
1632
2480
  params: { type: logData.level || "log", args: logData.args || [] }
1633
2481
  });
1634
2482
  }
1635
- });
2483
+ };
2484
+ const onSimDestroyed = () => {
2485
+ attachedSim = null;
2486
+ ipcMessageHostHandler = null;
2487
+ simDestroyedHandler = null;
2488
+ consoleForwardingSetup = false;
2489
+ };
2490
+ attachedSim = sim;
2491
+ ipcMessageHostHandler = onIpcMessageHost;
2492
+ simDestroyedHandler = onSimDestroyed;
2493
+ sim.on("ipc-message-host", onIpcMessageHost);
2494
+ sim.once("destroyed", onSimDestroyed);
1636
2495
  }, 1e3);
1637
- setTimeout(() => clearInterval(check), 3e4);
2496
+ pollStopTimer = setTimeout(() => {
2497
+ if (pollInterval) {
2498
+ clearInterval(pollInterval);
2499
+ pollInterval = null;
2500
+ }
2501
+ pollStopTimer = null;
2502
+ }, 3e4);
1638
2503
  }
1639
2504
  wss.on("connection", (ws) => {
1640
2505
  clients.add(ws);
@@ -1665,7 +2530,24 @@ async function startAutomationServer(ctx, port = 0) {
1665
2530
  });
1666
2531
  return {
1667
2532
  close: () => {
2533
+ if (pollInterval) {
2534
+ clearInterval(pollInterval);
2535
+ pollInterval = null;
2536
+ }
2537
+ if (pollStopTimer) {
2538
+ clearTimeout(pollStopTimer);
2539
+ pollStopTimer = null;
2540
+ }
2541
+ detachConsoleForwarding();
2542
+ for (const ws of clients) {
2543
+ try {
2544
+ ws.close();
2545
+ } catch {
2546
+ }
2547
+ }
2548
+ clients.clear();
1668
2549
  if (currentPort === resolvedPort) currentPort = null;
2550
+ void portIpc.dispose();
1669
2551
  wss.close();
1670
2552
  },
1671
2553
  port: resolvedPort
@@ -1683,7 +2565,7 @@ import CDP from "chrome-remote-interface";
1683
2565
  var SIMULATOR_URL_PATTERN = "localhost:7788";
1684
2566
  var MAX_BUFFER = 500;
1685
2567
  var RECONNECT_INTERVAL_MS = 3e3;
1686
- var cdpPort = 9222;
2568
+ var cdpPort = DEFAULT_CDP_PORT;
1687
2569
  var targets = {
1688
2570
  simulator: { client: null, connected: false, timer: null, consoleLogs: [], networkRequests: [] },
1689
2571
  workbench: { client: null, connected: false, timer: null, consoleLogs: [], networkRequests: [] }
@@ -1824,7 +2706,7 @@ function getClient(kind) {
1824
2706
  }
1825
2707
 
1826
2708
  // src/main/services/mcp/tool-registry.ts
1827
- import { z } from "zod";
2709
+ import { z as z2 } from "zod";
1828
2710
  function registerCommonTargetTools(server, kind) {
1829
2711
  const descriptions = kind === "simulator" ? {
1830
2712
  screenshot: "Take a screenshot of the simulator webview",
@@ -1845,9 +2727,9 @@ function registerCommonTargetTools(server, kind) {
1845
2727
  return { content: [{ type: "image", data, mimeType: "image/png" }] };
1846
2728
  });
1847
2729
  server.tool(`${kind}_console_logs`, descriptions.consoleLogs, {
1848
- limit: z.number().optional().default(50).describe("Maximum number of log entries"),
1849
- level: z.enum(["error", "warning", "warn", "info", "log", "debug"]).optional().describe("Only return entries with this level"),
1850
- sinceTimestamp: z.string().optional().describe("ISO timestamp; only return entries at or after this time")
2730
+ limit: z2.number().optional().default(50).describe("Maximum number of log entries"),
2731
+ level: z2.enum(["error", "warning", "warn", "info", "log", "debug"]).optional().describe("Only return entries with this level"),
2732
+ sinceTimestamp: z2.string().optional().describe("ISO timestamp; only return entries at or after this time")
1851
2733
  }, async ({ limit, level, sinceTimestamp }) => {
1852
2734
  getClient(kind);
1853
2735
  let entries = getTargetState(kind).consoleLogs;
@@ -1862,7 +2744,7 @@ function registerCommonTargetTools(server, kind) {
1862
2744
  });
1863
2745
  if (kind !== "workbench") {
1864
2746
  server.tool(`${kind}_evaluate`, descriptions.evaluate, {
1865
- expression: z.string().describe("JavaScript expression to evaluate")
2747
+ expression: z2.string().describe("JavaScript expression to evaluate")
1866
2748
  }, async ({ expression }) => {
1867
2749
  const c = getClient(kind);
1868
2750
  const result = await c.Runtime.evaluate({ expression, returnByValue: true, awaitPromise: true });
@@ -1875,15 +2757,15 @@ ${JSON.stringify(result.exceptionDetails, null, 2)}` }], isError: true };
1875
2757
  });
1876
2758
  }
1877
2759
  server.tool(`${kind}_get_dom`, descriptions.getDom, {
1878
- depth: z.number().optional().default(3).describe("Maximum depth")
2760
+ depth: z2.number().optional().default(3).describe("Maximum depth")
1879
2761
  }, async ({ depth }) => {
1880
2762
  const c = getClient(kind);
1881
2763
  const { root } = await c.DOM.getDocument({ depth });
1882
2764
  return { content: [{ type: "text", text: JSON.stringify(root, null, 2) }] };
1883
2765
  });
1884
2766
  server.tool(`${kind}_network_log`, descriptions.networkLog, {
1885
- limit: z.number().optional().default(20).describe("Maximum number of entries"),
1886
- minStatus: z.number().optional().describe("Only return entries with status >= minStatus (status 0 = failed before response is included when minStatus >= 400)")
2767
+ limit: z2.number().optional().default(20).describe("Maximum number of entries"),
2768
+ minStatus: z2.number().optional().describe("Only return entries with status >= minStatus (status 0 = failed before response is included when minStatus >= 400)")
1887
2769
  }, async ({ limit, minStatus }) => {
1888
2770
  getClient(kind);
1889
2771
  let entries = getTargetState(kind).networkRequests;
@@ -2126,11 +3008,11 @@ function registerContextTools(server) {
2126
3008
  }
2127
3009
 
2128
3010
  // src/main/services/mcp/tools/simulator-tools.ts
2129
- import { z as z2 } from "zod";
3011
+ import { z as z3 } from "zod";
2130
3012
  function registerSimulatorTools(server) {
2131
3013
  server.tool("simulator_navigate", "Navigate the simulator to a URL, or reload the current page", {
2132
- url: z2.string().optional().describe("URL to navigate to"),
2133
- reload: z2.boolean().optional().describe("If true, reload current page instead of navigating")
3014
+ url: z3.string().optional().describe("URL to navigate to"),
3015
+ reload: z3.boolean().optional().describe("If true, reload current page instead of navigating")
2134
3016
  }, async ({ url, reload }) => {
2135
3017
  const c = getClient("simulator");
2136
3018
  if (reload) {
@@ -2145,15 +3027,15 @@ function registerSimulatorTools(server) {
2145
3027
  return { content: [{ type: "text", text: "Error: either url or reload is required" }], isError: true };
2146
3028
  });
2147
3029
  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.", {
2148
- action: z2.enum(["tap_coord", "tap_selector", "type", "scroll", "key"]).describe("Input action to dispatch"),
2149
- x: z2.number().optional().describe("Viewport x coordinate for `tap_coord` or `scroll`"),
2150
- y: z2.number().optional().describe("Viewport y coordinate for `tap_coord` or `scroll`"),
2151
- selector: z2.string().optional().describe("CSS selector for `tap_selector` or `type`"),
2152
- nth: z2.number().int().optional().describe("Zero-based index when selector matches multiple elements; defaults to 0"),
2153
- text: z2.string().optional().describe("Text to insert for `type`"),
2154
- key: z2.string().optional().describe("Best-effort keyboard key name for `key`; common keys such as Enter, Escape, Tab, Arrow* and Space work best"),
2155
- deltaX: z2.number().optional().describe("Horizontal wheel delta for `scroll`"),
2156
- deltaY: z2.number().optional().describe("Vertical wheel delta for `scroll`")
3030
+ action: z3.enum(["tap_coord", "tap_selector", "type", "scroll", "key"]).describe("Input action to dispatch"),
3031
+ x: z3.number().optional().describe("Viewport x coordinate for `tap_coord` or `scroll`"),
3032
+ y: z3.number().optional().describe("Viewport y coordinate for `tap_coord` or `scroll`"),
3033
+ selector: z3.string().optional().describe("CSS selector for `tap_selector` or `type`"),
3034
+ nth: z3.number().int().optional().describe("Zero-based index when selector matches multiple elements; defaults to 0"),
3035
+ text: z3.string().optional().describe("Text to insert for `type`"),
3036
+ key: z3.string().optional().describe("Best-effort keyboard key name for `key`; common keys such as Enter, Escape, Tab, Arrow* and Space work best"),
3037
+ deltaX: z3.number().optional().describe("Horizontal wheel delta for `scroll`"),
3038
+ deltaY: z3.number().optional().describe("Vertical wheel delta for `scroll`")
2157
3039
  }, async ({ action, x, y, selector, nth, text, key, deltaX, deltaY }) => {
2158
3040
  const c = getClient("simulator");
2159
3041
  const err = (msg) => ({ content: [{ type: "text", text: `Error: ${msg}` }], isError: true });
@@ -2301,94 +3183,280 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
2301
3183
  httpServer.on("error", (err) => {
2302
3184
  if (err.code === "EADDRINUSE") {
2303
3185
  console.warn(`[MCP] Port ${mcpPort} already in use \u2014 MCP server not started`);
3186
+ recordMcpFailed("port-in-use");
2304
3187
  } else {
2305
3188
  console.error("[MCP] Server error:", err);
3189
+ recordMcpFailed(err.message);
2306
3190
  }
2307
3191
  });
2308
3192
  httpServer.listen(mcpPort, "127.0.0.1", () => {
2309
3193
  console.log(`[MCP] SSE server listening on http://127.0.0.1:${mcpPort}/sse`);
3194
+ recordMcpStarted(mcpPort);
3195
+ });
3196
+ return toDisposable(() => {
3197
+ transports.clear();
3198
+ httpServer.close();
3199
+ recordMcpStopped();
2310
3200
  });
2311
3201
  }
2312
3202
 
2313
3203
  // src/main/services/simulator-storage/index.ts
2314
- import { app as app8, ipcMain as ipcMain10, webContents as wcStatic } from "electron";
2315
- var attachedWc = null;
2316
- var hostWc = null;
3204
+ import { app as app10, webContents as wcStatic } from "electron";
2317
3205
  function isSimulatorWebview(wc) {
2318
3206
  if (wc.isDestroyed()) return false;
2319
3207
  if (wc.getType() !== "webview") return false;
2320
3208
  return wc.getURL().includes("simulator.html");
2321
3209
  }
2322
- function detach() {
2323
- if (!attachedWc) return;
2324
- const wc = attachedWc;
2325
- attachedWc = null;
3210
+ function safeOff(target, event, fn) {
2326
3211
  try {
2327
- if (!wc.isDestroyed() && wc.debugger.isAttached()) {
2328
- wc.debugger.detach();
2329
- }
3212
+ if (target.isDestroyed?.()) return;
3213
+ target.removeListener(event, fn);
2330
3214
  } catch {
2331
3215
  }
2332
3216
  }
2333
- async function attachToSim(wc) {
2334
- if (attachedWc === wc) return;
2335
- if (wc.isDestroyed()) return;
2336
- detach();
2337
- try {
2338
- if (!wc.debugger.isAttached()) {
2339
- wc.debugger.attach("1.3");
3217
+ function setupSimulatorStorage(host, options) {
3218
+ const registry = new DisposableRegistry();
3219
+ const getActiveAppId = options.getActiveAppId;
3220
+ let attachedWc = null;
3221
+ let attachDisposables = null;
3222
+ let cachedOrigin = null;
3223
+ function activePrefix() {
3224
+ const appId = getActiveAppId();
3225
+ return appId ? `${appId}_` : null;
3226
+ }
3227
+ function detachFromSim() {
3228
+ if (!attachedWc) return;
3229
+ const wc = attachedWc;
3230
+ attachedWc = null;
3231
+ cachedOrigin = null;
3232
+ const ad = attachDisposables;
3233
+ attachDisposables = null;
3234
+ if (ad) void ad.disposeAll().catch(() => {
3235
+ });
3236
+ try {
3237
+ if (!wc.isDestroyed() && wc.debugger.isAttached()) {
3238
+ wc.debugger.detach();
3239
+ }
3240
+ } catch {
3241
+ }
3242
+ }
3243
+ async function attachToSim(wc) {
3244
+ if (attachedWc === wc) return;
3245
+ if (wc.isDestroyed()) return;
3246
+ detachFromSim();
3247
+ try {
3248
+ if (!wc.debugger.isAttached()) {
3249
+ wc.debugger.attach("1.3");
3250
+ }
3251
+ await wc.debugger.sendCommand("DOMStorage.enable");
3252
+ const attach = new DisposableRegistry();
3253
+ attachDisposables = attach;
3254
+ const onMessage = (_event, method, params) => forwardCdpMessage(method, params);
3255
+ wc.debugger.on("message", onMessage);
3256
+ attach.add(() => safeOff(wc.debugger, "message", onMessage));
3257
+ const onDetach = () => {
3258
+ if (attachedWc === wc) attachedWc = null;
3259
+ };
3260
+ wc.debugger.on("detach", onDetach);
3261
+ attach.add(() => safeOff(wc.debugger, "detach", onDetach));
3262
+ const onDestroyed = () => {
3263
+ if (attachedWc === wc) {
3264
+ attachedWc = null;
3265
+ const ad = attachDisposables;
3266
+ attachDisposables = null;
3267
+ if (ad) void ad.disposeAll().catch(() => {
3268
+ });
3269
+ }
3270
+ };
3271
+ wc.once("destroyed", onDestroyed);
3272
+ attach.add(() => safeOff(wc, "destroyed", onDestroyed));
3273
+ attachedWc = wc;
3274
+ } catch (e) {
3275
+ console.warn("[storage-watcher] attach failed:", e.message);
3276
+ }
3277
+ }
3278
+ function forwardCdpMessage(method, params) {
3279
+ if (host.isDestroyed()) return;
3280
+ const p = params;
3281
+ const prefix = activePrefix();
3282
+ let evt;
3283
+ switch (method) {
3284
+ case "DOMStorage.domStorageItemAdded": {
3285
+ const key = String(p.key);
3286
+ if (prefix && !key.startsWith(prefix)) return;
3287
+ evt = { type: "added", key, newValue: String(p.newValue ?? "") };
3288
+ break;
3289
+ }
3290
+ case "DOMStorage.domStorageItemUpdated": {
3291
+ const key = String(p.key);
3292
+ if (prefix && !key.startsWith(prefix)) return;
3293
+ evt = {
3294
+ type: "updated",
3295
+ key,
3296
+ oldValue: String(p.oldValue ?? ""),
3297
+ newValue: String(p.newValue ?? "")
3298
+ };
3299
+ break;
3300
+ }
3301
+ case "DOMStorage.domStorageItemRemoved": {
3302
+ const key = String(p.key);
3303
+ if (prefix && !key.startsWith(prefix)) return;
3304
+ evt = { type: "removed", key };
3305
+ break;
3306
+ }
3307
+ case "DOMStorage.domStorageItemsCleared":
3308
+ evt = { type: "cleared" };
3309
+ break;
3310
+ default:
3311
+ return;
2340
3312
  }
2341
- await wc.debugger.sendCommand("DOMStorage.enable");
2342
- wc.debugger.on("message", onCdpMessage);
2343
- wc.debugger.on("detach", () => {
2344
- if (attachedWc === wc) attachedWc = null;
3313
+ host.send(SimulatorStorageChannel.Event, evt);
3314
+ }
3315
+ async function ensureAttached() {
3316
+ if (attachedWc && !attachedWc.isDestroyed()) return;
3317
+ const sim = findSimulatorWebContents();
3318
+ if (!sim) return;
3319
+ await attachToSim(sim);
3320
+ }
3321
+ async function getStorageId() {
3322
+ if (!attachedWc || attachedWc.isDestroyed()) return null;
3323
+ if (!cachedOrigin) {
3324
+ try {
3325
+ cachedOrigin = String(await attachedWc.executeJavaScript("location.origin"));
3326
+ } catch {
3327
+ return null;
3328
+ }
3329
+ }
3330
+ return { securityOrigin: cachedOrigin, isLocalStorage: true };
3331
+ }
3332
+ async function withStorageWrite(fn) {
3333
+ await ensureAttached();
3334
+ if (!attachedWc || attachedWc.isDestroyed()) {
3335
+ return { ok: false, error: "simulator not attached" };
3336
+ }
3337
+ const storageId = await getStorageId();
3338
+ if (!storageId) return { ok: false, error: "failed to resolve simulator origin" };
3339
+ try {
3340
+ return await fn({ wc: attachedWc, storageId });
3341
+ } catch (e) {
3342
+ return { ok: false, error: e.message };
3343
+ }
3344
+ }
3345
+ async function withStorageRead(fallback, fn) {
3346
+ await ensureAttached();
3347
+ if (!attachedWc || attachedWc.isDestroyed()) return fallback;
3348
+ try {
3349
+ const storageId = await getStorageId();
3350
+ if (!storageId) return fallback;
3351
+ return await fn({ wc: attachedWc, storageId });
3352
+ } catch {
3353
+ return fallback;
3354
+ }
3355
+ }
3356
+ function pushSyntheticEvent(evt) {
3357
+ if (host.isDestroyed()) return;
3358
+ const prefix = activePrefix();
3359
+ if (prefix && !evt.key.startsWith(prefix)) return;
3360
+ host.send(SimulatorStorageChannel.Event, evt);
3361
+ }
3362
+ async function setItem(key, value) {
3363
+ return withStorageWrite(async ({ wc, storageId }) => {
3364
+ await wc.debugger.sendCommand("DOMStorage.setDOMStorageItem", { storageId, key, value });
3365
+ pushSyntheticEvent({ type: "added", key, newValue: value });
3366
+ return { ok: true };
2345
3367
  });
2346
- wc.once("destroyed", () => {
2347
- if (attachedWc === wc) attachedWc = null;
3368
+ }
3369
+ async function removeItem(key) {
3370
+ return withStorageWrite(async ({ wc, storageId }) => {
3371
+ await wc.debugger.sendCommand("DOMStorage.removeDOMStorageItem", { storageId, key });
3372
+ pushSyntheticEvent({ type: "removed", key });
3373
+ return { ok: true };
2348
3374
  });
2349
- attachedWc = wc;
2350
- } catch (e) {
2351
- console.warn("[storage-watcher] attach failed:", e.message);
2352
3375
  }
2353
- }
2354
- function onCdpMessage(_event, method, params) {
2355
- if (!hostWc || hostWc.isDestroyed()) return;
2356
- const p = params;
2357
- let evt;
2358
- switch (method) {
2359
- case "DOMStorage.domStorageItemAdded":
2360
- evt = { type: "added", key: String(p.key), newValue: String(p.newValue ?? "") };
2361
- break;
2362
- case "DOMStorage.domStorageItemUpdated":
2363
- evt = {
2364
- type: "updated",
2365
- key: String(p.key),
2366
- oldValue: String(p.oldValue ?? ""),
2367
- newValue: String(p.newValue ?? "")
2368
- };
2369
- break;
2370
- case "DOMStorage.domStorageItemRemoved":
2371
- evt = { type: "removed", key: String(p.key) };
2372
- break;
2373
- case "DOMStorage.domStorageItemsCleared":
2374
- evt = { type: "cleared" };
2375
- break;
2376
- default:
2377
- return;
3376
+ async function clearScoped() {
3377
+ return withStorageWrite(async ({ wc, storageId }) => {
3378
+ const prefix = activePrefix();
3379
+ if (!prefix) {
3380
+ await wc.debugger.sendCommand("DOMStorage.clear", { storageId });
3381
+ return { ok: true };
3382
+ }
3383
+ const result = await wc.debugger.sendCommand("DOMStorage.getDOMStorageItems", {
3384
+ storageId
3385
+ });
3386
+ for (const [key] of result.entries) {
3387
+ if (!key.startsWith(prefix)) continue;
3388
+ await wc.debugger.sendCommand("DOMStorage.removeDOMStorageItem", { storageId, key });
3389
+ }
3390
+ return { ok: true };
3391
+ });
2378
3392
  }
2379
- hostWc.send(SimulatorStorageChannel.Event, evt);
2380
- }
2381
- async function getSnapshot() {
2382
- if (!attachedWc || attachedWc.isDestroyed()) return [];
2383
- try {
2384
- const origin = await attachedWc.executeJavaScript("location.origin");
2385
- const result = await attachedWc.debugger.sendCommand("DOMStorage.getDOMStorageItems", {
2386
- storageId: { securityOrigin: origin, isLocalStorage: true }
3393
+ async function clearAll() {
3394
+ return withStorageWrite(async ({ wc, storageId }) => {
3395
+ await wc.debugger.sendCommand("DOMStorage.clear", { storageId });
3396
+ return { ok: true };
2387
3397
  });
2388
- return result.entries.map(([key, value]) => ({ key, value }));
2389
- } catch {
2390
- return [];
2391
3398
  }
3399
+ async function getSnapshot() {
3400
+ return withStorageRead([], async ({ wc, storageId }) => {
3401
+ const result = await wc.debugger.sendCommand("DOMStorage.getDOMStorageItems", {
3402
+ storageId
3403
+ });
3404
+ const prefix = activePrefix();
3405
+ const filtered = prefix ? result.entries.filter(([key]) => key.startsWith(prefix)) : result.entries;
3406
+ return filtered.map(([key, value]) => ({ key, value }));
3407
+ });
3408
+ }
3409
+ for (const wc of wcStatic.getAllWebContents()) {
3410
+ if (isSimulatorWebview(wc)) {
3411
+ void attachToSim(wc);
3412
+ break;
3413
+ }
3414
+ }
3415
+ const wcSubs = /* @__PURE__ */ new Map();
3416
+ const onWcCreated = (_event, wc) => {
3417
+ const sub = new DisposableRegistry();
3418
+ wcSubs.set(wc, sub);
3419
+ const onFinishLoad = () => {
3420
+ if (isSimulatorWebview(wc)) void attachToSim(wc);
3421
+ };
3422
+ wc.on("did-finish-load", onFinishLoad);
3423
+ sub.add(() => safeOff(wc, "did-finish-load", onFinishLoad));
3424
+ wc.once("destroyed", () => {
3425
+ void sub.disposeAll().catch(() => {
3426
+ });
3427
+ wcSubs.delete(wc);
3428
+ });
3429
+ };
3430
+ app10.on("web-contents-created", onWcCreated);
3431
+ registry.add(() => {
3432
+ app10.removeListener("web-contents-created", onWcCreated);
3433
+ });
3434
+ registry.add(async () => {
3435
+ const subs = Array.from(wcSubs.values());
3436
+ wcSubs.clear();
3437
+ for (const sub of subs) {
3438
+ await sub.disposeAll().catch(() => {
3439
+ });
3440
+ }
3441
+ });
3442
+ const ipc = new IpcRegistry(options.senderPolicy);
3443
+ ipc.handle(SimulatorStorageChannel.GetSnapshot, () => getSnapshot());
3444
+ ipc.handle(SimulatorStorageChannel.GetActivePrefix, () => activePrefix() ?? "");
3445
+ ipc.handle(
3446
+ SimulatorStorageChannel.Set,
3447
+ (_event, payload) => setItem(String(payload?.key ?? ""), String(payload?.value ?? ""))
3448
+ );
3449
+ ipc.handle(
3450
+ SimulatorStorageChannel.Remove,
3451
+ (_event, payload) => removeItem(String(payload?.key ?? ""))
3452
+ );
3453
+ ipc.handle(SimulatorStorageChannel.Clear, () => clearScoped());
3454
+ ipc.handle(SimulatorStorageChannel.ClearAll, () => clearAll());
3455
+ ipc.handle(SimulatorElementChannel.Inspect, (_event, sid) => inspectElement(sid));
3456
+ ipc.handle(SimulatorElementChannel.Clear, () => clearElementInspection());
3457
+ registry.add(ipc);
3458
+ registry.add(() => detachFromSim());
3459
+ return registry;
2392
3460
  }
2393
3461
  function findSimulatorWebContents() {
2394
3462
  for (const wc of wcStatic.getAllWebContents()) {
@@ -2420,30 +3488,9 @@ async function clearElementInspection() {
2420
3488
  } catch {
2421
3489
  }
2422
3490
  }
2423
- function setupSimulatorStorage(host) {
2424
- hostWc = host;
2425
- for (const wc of wcStatic.getAllWebContents()) {
2426
- if (isSimulatorWebview(wc)) {
2427
- void attachToSim(wc);
2428
- break;
2429
- }
2430
- }
2431
- app8.on("web-contents-created", (_event, wc) => {
2432
- wc.on("did-finish-load", () => {
2433
- if (isSimulatorWebview(wc)) {
2434
- void attachToSim(wc);
2435
- }
2436
- });
2437
- wc.on("did-navigate-in-page", () => {
2438
- });
2439
- });
2440
- ipcMain10.handle(SimulatorStorageChannel.GetSnapshot, getSnapshot);
2441
- ipcMain10.handle(SimulatorElementChannel.Inspect, (_event, sid) => inspectElement(sid));
2442
- ipcMain10.handle(SimulatorElementChannel.Clear, clearElementInspection);
2443
- }
2444
3491
 
2445
3492
  // src/main/services/update/update-manager.ts
2446
- import { app as app9, ipcMain as ipcMain11, shell } from "electron";
3493
+ import { app as app11, shell as shell3 } from "electron";
2447
3494
  var UpdateManager = class {
2448
3495
  checker;
2449
3496
  mainWindow;
@@ -2452,23 +3499,17 @@ var UpdateManager = class {
2452
3499
  downloadedPath = null;
2453
3500
  timer = null;
2454
3501
  initialTimer = null;
3502
+ ipc;
2455
3503
  constructor(opts) {
2456
3504
  this.checker = opts.checker;
2457
3505
  this.mainWindow = opts.mainWindow;
2458
- this.getCurrentVersion = opts.getCurrentVersion ?? (() => app9.getVersion());
3506
+ this.getCurrentVersion = opts.getCurrentVersion ?? (() => app11.getVersion());
3507
+ this.ipc = new IpcRegistry(opts.senderPolicy);
2459
3508
  this.registerIpc();
2460
3509
  this.startPeriodicCheck(opts.checkInterval ?? 60 * 60 * 1e3, opts.initialDelay ?? 5e3);
2461
3510
  }
2462
3511
  registerIpc() {
2463
- ipcMain11.handle("updates:check", async () => {
2464
- return this.check();
2465
- });
2466
- ipcMain11.handle("updates:download", async () => {
2467
- return this.download();
2468
- });
2469
- ipcMain11.handle("updates:install", async () => {
2470
- this.install();
2471
- });
3512
+ this.ipc.handle(UpdateChannel.Check, async () => this.check()).handle(UpdateChannel.Download, async () => this.download()).handle(UpdateChannel.Install, async () => this.install());
2472
3513
  }
2473
3514
  async check() {
2474
3515
  try {
@@ -2491,7 +3532,7 @@ var UpdateManager = class {
2491
3532
  }
2492
3533
  try {
2493
3534
  const filePath = await this.checker.downloadUpdate(this.latestUpdate, (percent) => {
2494
- this.mainWindow.webContents.send("updates:downloadProgress", { percent });
3535
+ this.mainWindow.webContents.send(UpdateChannel.DownloadProgress, { percent });
2495
3536
  });
2496
3537
  this.downloadedPath = filePath;
2497
3538
  return { success: true, filePath };
@@ -2502,10 +3543,16 @@ var UpdateManager = class {
2502
3543
  }
2503
3544
  install() {
2504
3545
  if (this.downloadedPath) {
2505
- shell.openPath(this.downloadedPath);
2506
- app9.quit();
3546
+ shell3.openPath(this.downloadedPath);
3547
+ app11.quit();
2507
3548
  }
2508
3549
  }
3550
+ /**
3551
+ * Tear down timers + IPC handlers. Returns a Promise so callers that need
3552
+ * a settled cleanup (e.g. workbench registry shutdown, the unit-test
3553
+ * lifecycle suite) can await it; calling without await is also safe since
3554
+ * the timer clears + handler removals are scheduled synchronously.
3555
+ */
2509
3556
  dispose() {
2510
3557
  if (this.timer) {
2511
3558
  clearInterval(this.timer);
@@ -2515,9 +3562,7 @@ var UpdateManager = class {
2515
3562
  clearTimeout(this.initialTimer);
2516
3563
  this.initialTimer = null;
2517
3564
  }
2518
- ipcMain11.removeHandler("updates:check");
2519
- ipcMain11.removeHandler("updates:download");
2520
- ipcMain11.removeHandler("updates:install");
3565
+ return this.ipc.dispose();
2521
3566
  }
2522
3567
  startPeriodicCheck(interval, initialDelay) {
2523
3568
  this.initialTimer = setTimeout(() => void this.checkAndNotify(), initialDelay);
@@ -2526,13 +3571,13 @@ var UpdateManager = class {
2526
3571
  async checkAndNotify() {
2527
3572
  const result = await this.check();
2528
3573
  if (result.hasUpdate && result.info) {
2529
- this.mainWindow.webContents.send("updates:available", result.info);
3574
+ this.mainWindow.webContents.send(UpdateChannel.Available, result.info);
2530
3575
  }
2531
3576
  }
2532
3577
  };
2533
3578
 
2534
3579
  // src/main/services/update/github-release-checker.ts
2535
- import { app as app10 } from "electron";
3580
+ import { app as app12 } from "electron";
2536
3581
 
2537
3582
  // src/main/app/app.ts
2538
3583
  var DEFAULT_MODULES = {
@@ -2542,16 +3587,12 @@ var DEFAULT_MODULES = {
2542
3587
  popover: true,
2543
3588
  settings: true
2544
3589
  };
2545
- var MODULE_REGISTRARS = {
2546
- projects: registerProjectsIpc,
2547
- session: registerSessionIpc,
2548
- simulator: (ctx) => {
2549
- registerSimulatorIpc(ctx);
2550
- registerPanelsIpc(ctx);
2551
- registerToolbarIpc(ctx);
2552
- },
2553
- popover: registerPopoverIpc,
2554
- settings: registerSettingsIpc
3590
+ var BUILTIN_MODULES = {
3591
+ projects: projectsModule,
3592
+ session: sessionModule,
3593
+ simulator: simulatorModule,
3594
+ popover: popoverModule,
3595
+ settings: settingsModule
2555
3596
  };
2556
3597
  function parseAutoArgs() {
2557
3598
  const argv = process.argv;
@@ -2576,18 +3617,25 @@ function resolveModules(config) {
2576
3617
  ...config.modules
2577
3618
  };
2578
3619
  }
2579
- async function disposeContext(ctx, updateManager) {
2580
- updateManager?.dispose();
3620
+ async function disposeContext(ctx) {
2581
3621
  await ctx.workspace.closeProject();
3622
+ await ctx.registry.dispose().catch((err) => {
3623
+ console.warn("[workbench] dispose registry encountered errors:", err);
3624
+ });
2582
3625
  }
2583
3626
  function createConfiguredMainWindow(config, rendererDir2) {
2584
3627
  const mainWindow = createMainWindow({
2585
3628
  title: config.appName ?? "Dimina DevTools",
2586
- indexHtml: path6.join(rendererDir2, "entries/main/index.html"),
3629
+ indexHtml: path12.join(rendererDir2, "entries/main/index.html"),
2587
3630
  width: config.window?.width,
2588
3631
  height: config.window?.height,
2589
3632
  minWidth: config.window?.minWidth,
2590
- minHeight: config.window?.minHeight
3633
+ minHeight: config.window?.minHeight,
3634
+ // P1: pass the same preload path the workbench context stores on
3635
+ // ctx.preloadPath so hosts that ship a custom simulator preload are the
3636
+ // ones registered on the persist:simulator session — otherwise the
3637
+ // backstop wouldn't be the host's script.
3638
+ simulatorPreloadPath: config.preloadPath ?? defaultPreloadPath
2591
3639
  });
2592
3640
  if (config.icon) {
2593
3641
  const icon = nativeImage.createFromPath(config.icon);
@@ -2605,13 +3653,21 @@ function createContext(config, mainWindow, rendererDir2) {
2605
3653
  appName: config.appName,
2606
3654
  apiNamespaces: config.apiNamespaces,
2607
3655
  brandingProvider: config.brandingProvider,
2608
- toolbarActions: config.toolbarActions
3656
+ toolbarActions: config.toolbarActions,
3657
+ // The host-supplied ProjectsProvider / template types in `shared/types`
3658
+ // are structurally compatible with the main-process equivalents —
3659
+ // these casts are safe; we re-narrow at the workspace-service /
3660
+ // create-project-service boundary.
3661
+ projectsProvider: config.projectsProvider,
3662
+ projectTemplates: config.projectTemplates,
3663
+ builtinTemplates: config.builtinTemplates,
3664
+ customCreateProjectDialog: config.customCreateProjectDialog
2609
3665
  });
2610
3666
  }
2611
3667
  function registerBuiltinModules(config, context) {
2612
3668
  const modules = resolveModules(config);
2613
3669
  Object.keys(modules).forEach((moduleId) => {
2614
- if (modules[moduleId]) MODULE_REGISTRARS[moduleId](context);
3670
+ if (modules[moduleId]) context.registry.add(BUILTIN_MODULES[moduleId].setup(context));
2615
3671
  });
2616
3672
  }
2617
3673
  function installMenu(config, mainWindow, context) {
@@ -2626,19 +3682,20 @@ async function setupAutomation(instance) {
2626
3682
  if (autoArgs.auto) {
2627
3683
  const server = await startAutomationServer(instance.context, autoArgs.autoPort);
2628
3684
  instance.automationServer = server;
3685
+ instance.context.registry.add(() => server.close());
2629
3686
  console.log(`[automation] listening on ws://127.0.0.1:${server.port}`);
2630
3687
  }
2631
3688
  }
2632
3689
  function setupMcp() {
2633
3690
  const settings = loadWorkbenchSettings();
2634
- if (!settings.mcp.enabled) return;
2635
- const cdpPortSwitch = app11.commandLine.getSwitchValue("remote-debugging-port");
3691
+ if (!settings.mcp.enabled) return null;
3692
+ const cdpPortSwitch = app13.commandLine.getSwitchValue("remote-debugging-port");
2636
3693
  const cdpPort2 = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
2637
- startMcpServer(cdpPort2, settings.mcp.port);
3694
+ return startMcpServer(cdpPort2, settings.mcp.port);
2638
3695
  }
2639
3696
  function wireAppWindowEvents(config, instance) {
2640
3697
  const { mainWindow, context } = instance;
2641
- wireMainWindowEvents(mainWindow, {
3698
+ return wireMainWindowEvents(mainWindow, {
2642
3699
  context,
2643
3700
  onResize: () => context.views.repositionAll(),
2644
3701
  onClose: async (e) => {
@@ -2647,23 +3704,32 @@ function wireAppWindowEvents(config, instance) {
2647
3704
  if (config.onBeforeClose) {
2648
3705
  await config.onBeforeClose(instance);
2649
3706
  }
2650
- await disposeContext(context);
3707
+ await context.workspace.closeProject();
2651
3708
  context.notify.windowNavigateBack();
2652
3709
  }
2653
3710
  });
2654
3711
  }
2655
3712
  function enableDevRendererAutoReload(rendererDir2) {
2656
- if (!app11.isPackaged) {
2657
- let reloadTimer = null;
2658
- fs5.watch(rendererDir2, { recursive: true }, () => {
2659
- if (reloadTimer) clearTimeout(reloadTimer);
2660
- reloadTimer = setTimeout(() => {
2661
- for (const win of BrowserWindow3.getAllWindows()) {
2662
- win.webContents.reload();
2663
- }
2664
- }, 300);
3713
+ if (app13.isPackaged) {
3714
+ return toDisposable(() => {
2665
3715
  });
2666
3716
  }
3717
+ let reloadTimer = null;
3718
+ const watcher = fs7.watch(rendererDir2, { recursive: true }, () => {
3719
+ if (reloadTimer) clearTimeout(reloadTimer);
3720
+ reloadTimer = setTimeout(() => {
3721
+ for (const win of BrowserWindow4.getAllWindows()) {
3722
+ win.webContents.reload();
3723
+ }
3724
+ }, 300);
3725
+ });
3726
+ return toDisposable(() => {
3727
+ if (reloadTimer) {
3728
+ clearTimeout(reloadTimer);
3729
+ reloadTimer = null;
3730
+ }
3731
+ watcher.close();
3732
+ });
2667
3733
  }
2668
3734
  function createWorkbenchApp(config = {}) {
2669
3735
  let setupPromise = null;
@@ -2671,18 +3737,19 @@ function createWorkbenchApp(config = {}) {
2671
3737
  setupCdpPort();
2672
3738
  async function setup() {
2673
3739
  if (setupPromise) return setupPromise;
2674
- setupPromise = app11.whenReady().then(async () => {
3740
+ setupPromise = app13.whenReady().then(async () => {
2675
3741
  applyTheme(loadWorkbenchSettings().theme);
2676
3742
  const rendererDir2 = config.rendererDir ?? rendererDir;
2677
3743
  const mainWindow = createConfiguredMainWindow(config, rendererDir2);
2678
3744
  const context = createContext(config, mainWindow, rendererDir2);
2679
- registerAppIpc(context);
3745
+ context.registry.add(registerAppIpc(context));
3746
+ context.registry.add(installThemeBackgroundSync());
2680
3747
  registerBuiltinModules(config, context);
2681
3748
  installMenu(config, mainWindow, context);
2682
3749
  const instance = {
2683
3750
  mainWindow,
2684
3751
  context,
2685
- dispose: () => disposeContext(context, instance.updateManager)
3752
+ dispose: () => disposeContext(context)
2686
3753
  };
2687
3754
  if (config.onSetup) {
2688
3755
  await config.onSetup(instance);
@@ -2695,22 +3762,37 @@ function createWorkbenchApp(config = {}) {
2695
3762
  initialDelay: config.updateOptions?.initialDelay,
2696
3763
  getCurrentVersion: config.updateOptions?.getCurrentVersion
2697
3764
  });
3765
+ context.registry.add(() => instance.updateManager.dispose());
2698
3766
  }
2699
3767
  await setupAutomation(instance);
2700
- setupMcp();
2701
- setupSimulatorStorage(mainWindow.webContents);
2702
- wireAppWindowEvents(config, instance);
2703
- enableDevRendererAutoReload(rendererDir2);
3768
+ const mcp = setupMcp();
3769
+ if (mcp) context.registry.add(mcp);
3770
+ context.registry.add(setupSimulatorStorage(mainWindow.webContents, {
3771
+ senderPolicy: context.senderPolicy,
3772
+ // Per-project filter for the simulator-storage panel: the simulator
3773
+ // uses a fixed `persist:simulator` partition + a fixed simulator.html
3774
+ // origin, so localStorage is shared across every project that has
3775
+ // ever opened. The dimina runtime isolates writes with `${appId}_`
3776
+ // prefixes; this callback lets the storage panel filter the CDP
3777
+ // snapshot/event stream to the active appId.
3778
+ getActiveAppId: () => {
3779
+ const session2 = context.workspace.getSession();
3780
+ const appInfo = session2?.appInfo;
3781
+ return appInfo?.appId ?? null;
3782
+ }
3783
+ }));
3784
+ context.registry.add(wireAppWindowEvents(config, instance));
3785
+ context.registry.add(enableDevRendererAutoReload(rendererDir2));
2704
3786
  return instance;
2705
3787
  });
2706
3788
  return setupPromise;
2707
3789
  }
2708
3790
  function start() {
2709
- void setup();
2710
3791
  if (!appEventsRegistered) {
2711
3792
  appEventsRegistered = true;
2712
3793
  registerAppLifecycle();
2713
3794
  }
3795
+ return setup().then(() => void 0);
2714
3796
  }
2715
3797
  return {
2716
3798
  setup,
@@ -2719,10 +3801,10 @@ function createWorkbenchApp(config = {}) {
2719
3801
  }
2720
3802
 
2721
3803
  // src/main/windows/settings-window/create.ts
2722
- import { BrowserWindow as BrowserWindow4 } from "electron";
2723
- import path7 from "path";
3804
+ import { BrowserWindow as BrowserWindow5 } from "electron";
3805
+ import path13 from "path";
2724
3806
  async function createSettingsWindow(parent, rendererDir2) {
2725
- const win = new BrowserWindow4({
3807
+ const win = new BrowserWindow5({
2726
3808
  width: 420,
2727
3809
  height: 560,
2728
3810
  minWidth: 380,
@@ -2730,9 +3812,15 @@ async function createSettingsWindow(parent, rendererDir2) {
2730
3812
  parent: parent ?? void 0,
2731
3813
  title: "\u5F00\u53D1\u5DE5\u5177\u8BBE\u7F6E",
2732
3814
  backgroundColor: themeBg(),
2733
- webPreferences: { nodeIntegration: true, contextIsolation: false }
3815
+ webPreferences: {
3816
+ nodeIntegration: false,
3817
+ contextIsolation: true,
3818
+ sandbox: false,
3819
+ preload: mainPreloadPath
3820
+ }
2734
3821
  });
2735
- await win.loadFile(path7.join(rendererDir2, "entries/workbench-settings/index.html"));
3822
+ applyNavigationHardening(win.webContents, rendererDir2);
3823
+ await win.loadFile(path13.join(rendererDir2, "entries/workbench-settings/index.html"));
2736
3824
  return win;
2737
3825
  }
2738
3826
 
@@ -2745,18 +3833,20 @@ function wireSettingsWindowEvents(win, onClosed) {
2745
3833
 
2746
3834
  // src/main/app/launch.ts
2747
3835
  function launch(config = {}) {
2748
- createWorkbenchApp(config).start();
3836
+ return createWorkbenchApp(config).start();
2749
3837
  }
2750
3838
  async function openSettingsWindow(ctx) {
2751
- if (!ctx.workbenchSettingsWindow || ctx.workbenchSettingsWindow.isDestroyed()) {
2752
- ctx.workbenchSettingsWindow = await createSettingsWindow(ctx.mainWindow, ctx.rendererDir);
2753
- wireSettingsWindowEvents(ctx.workbenchSettingsWindow, () => {
2754
- ctx.workbenchSettingsWindow = null;
3839
+ let win = ctx.windows.settingsWindow;
3840
+ if (!win || win.isDestroyed()) {
3841
+ win = await createSettingsWindow(ctx.windows.mainWindow, ctx.rendererDir);
3842
+ ctx.windows.setSettingsWindow(win);
3843
+ wireSettingsWindowEvents(win, () => {
3844
+ ctx.windows.setSettingsWindow(null);
2755
3845
  });
2756
3846
  }
2757
- ctx.workbenchSettingsWindow.show();
2758
- ctx.workbenchSettingsWindow.focus();
2759
- ctx.notify.workbenchSettingsInit(ctx.workbenchSettingsWindow, {
3847
+ win.show();
3848
+ win.focus();
3849
+ ctx.notify.workbenchSettingsInit(win, {
2760
3850
  settings: loadWorkbenchSettings()
2761
3851
  });
2762
3852
  }