@wlv-zedd/dsh-chatgpt-web 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +140 -140
  3. package/cordis.patch.yml +4 -4
  4. package/lib/cli.js +13269 -11860
  5. package/lib/plugin.js +3 -50
  6. package/package.json +89 -88
  7. package/src/adapters/base.ts +16 -16
  8. package/src/adapters/chatgpt-web/adapter-error.ts +59 -59
  9. package/src/adapters/chatgpt-web/browser-helper-main.ts +513 -513
  10. package/src/adapters/chatgpt-web/browser-helper-prompt-selection.ts +27 -27
  11. package/src/adapters/chatgpt-web/browser-worker.ts +4944 -4944
  12. package/src/adapters/chatgpt-web/codex-rollout-environment.ts +628 -628
  13. package/src/adapters/chatgpt-web/compaction-handoff.ts +533 -533
  14. package/src/adapters/chatgpt-web/compaction-transaction.ts +142 -142
  15. package/src/adapters/chatgpt-web/concurrency.ts +6 -6
  16. package/src/adapters/chatgpt-web/conversation-key.ts +58 -58
  17. package/src/adapters/chatgpt-web/environment.ts +669 -669
  18. package/src/adapters/chatgpt-web/index.ts +1544 -1544
  19. package/src/adapters/chatgpt-web/input-tokens.ts +74 -74
  20. package/src/adapters/chatgpt-web/launcher-helper-client.ts +695 -695
  21. package/src/adapters/chatgpt-web/markdown.ts +418 -418
  22. package/src/adapters/chatgpt-web/mcp-main.ts +25 -25
  23. package/src/adapters/chatgpt-web/mcp-server.ts +933 -933
  24. package/src/adapters/chatgpt-web/model.ts +70 -70
  25. package/src/adapters/chatgpt-web/native-compaction-control.ts +74 -74
  26. package/src/adapters/chatgpt-web/output-validation.ts +62 -62
  27. package/src/adapters/chatgpt-web/process-line-writer.ts +46 -46
  28. package/src/adapters/chatgpt-web/prompt.ts +702 -702
  29. package/src/adapters/chatgpt-web/retry-policy.ts +73 -73
  30. package/src/adapters/chatgpt-web/rolling-checkpoint.ts +384 -384
  31. package/src/adapters/chatgpt-web/thread-environment.ts +238 -238
  32. package/src/adapters/chatgpt-web/tool-stream-parser.ts +1 -1
  33. package/src/adapters/chatgpt-web/turn-broker.ts +1481 -1481
  34. package/src/adapters/chatgpt-web/turn-execution.ts +816 -816
  35. package/src/adapters/chatgpt-web/turn-progress.ts +292 -292
  36. package/src/adapters/chatgpt-web/usage.ts +121 -121
  37. package/src/adapters/image.ts +9 -9
  38. package/src/bridge.ts +1083 -1083
  39. package/src/browser-login.ts +521 -521
  40. package/src/chatgpt-session.ts +240 -240
  41. package/src/chatgpt-web-models.ts +400 -400
  42. package/src/cli.ts +568 -568
  43. package/src/codex-integration-document.ts +824 -824
  44. package/src/codex-integration-journal.ts +212 -212
  45. package/src/codex-integration-route.ts +515 -515
  46. package/src/codex-integration-shared.ts +332 -332
  47. package/src/codex-integration.ts +529 -529
  48. package/src/codex-interrupt-hook.ts +158 -158
  49. package/src/config.ts +616 -616
  50. package/src/dev-chat/cli.ts +432 -432
  51. package/src/dev-chat/constants.ts +3 -3
  52. package/src/dev-chat/driver.ts +655 -655
  53. package/src/dev-chat/profile.ts +223 -223
  54. package/src/dev-chat/session.ts +287 -287
  55. package/src/dev-chat/transport.ts +54 -54
  56. package/src/doctor.ts +237 -237
  57. package/src/event-queue.ts +45 -45
  58. package/src/http-body.ts +30 -30
  59. package/src/launcher-browser-host.ts +695 -695
  60. package/src/lib/errors.ts +281 -281
  61. package/src/lib/token-estimate.ts +42 -42
  62. package/src/login-helper.cjs +1 -1
  63. package/src/model-catalog.ts +197 -197
  64. package/src/native-passthrough.ts +261 -261
  65. package/src/plugin.ts +191 -191
  66. package/src/process.ts +45 -45
  67. package/src/responses/compaction.ts +199 -199
  68. package/src/responses/parser.ts +633 -633
  69. package/src/responses/reasoning-envelope.ts +49 -49
  70. package/src/responses/schema.ts +172 -172
  71. package/src/responses/state.ts +230 -230
  72. package/src/server.ts +1111 -1111
  73. package/src/service.ts +315 -315
  74. package/src/setup.ts +671 -671
  75. package/src/stall-timeout.ts +23 -23
  76. package/src/tunnel-service.ts +160 -160
  77. package/src/tunnel.ts +417 -417
  78. package/src/turndown-plugin-gfm.d.ts +5 -5
  79. package/src/types.ts +307 -307
  80. package/src/usage/totals.ts +12 -12
  81. package/src/version.ts +1 -1
@@ -1,223 +1,223 @@
1
- import { execFileSync, spawn } from "node:child_process";
2
- import { readFileSync, statSync } from "node:fs";
3
- import { homedir } from "node:os";
4
- import { isAbsolute, join, posix, resolve, win32 } from "node:path";
5
- import { expandUserPath, getConfigPath } from "../config";
6
- import {
7
- readLauncherBrowserHostDescriptor,
8
- type LauncherBrowserHostDescriptor,
9
- } from "../launcher-browser-host";
10
-
11
- import { DEV_LAUNCHER_PROFILE } from "./constants";
12
-
13
- export interface DevProfilePaths {
14
- home: string;
15
- codexHome: string;
16
- launcherUserData: string;
17
- launcherStatePath: string;
18
- descriptorPath: string;
19
- chatsPath: string;
20
- runtimePath: string;
21
- configPath: string;
22
- }
23
-
24
- const WINDOWS_LAUNCHER_GUID = "d1a6026a-6210-588e-9a2b-da3936f94e02";
25
-
26
- function registeredWindowsLauncherInstallLocation(): string | undefined {
27
- try {
28
- const output = execFileSync(
29
- "reg.exe",
30
- ["query", `HKCU\\Software\\${WINDOWS_LAUNCHER_GUID}`, "/v", "InstallLocation"],
31
- { encoding: "utf8", windowsHide: true },
32
- );
33
- const match = output.match(/^\s*InstallLocation\s+REG_SZ\s+(.+?)\s*$/mi);
34
- return match && win32.isAbsolute(match[1]) ? match[1] : undefined;
35
- } catch {
36
- return undefined;
37
- }
38
- }
39
-
40
- export function resolveDevProfilePaths({
41
- environment = process.env,
42
- homeDirectory = homedir(),
43
- }: {
44
- environment?: NodeJS.ProcessEnv;
45
- homeDirectory?: string;
46
- } = {}): DevProfilePaths {
47
- const home = resolve(expandUserPath(
48
- environment.DSH_CHATGPT_FREE_DEV_HOME?.trim()
49
- || join(homeDirectory, ".dsh", "storages", "chatgpt-web-dev"),
50
- ));
51
- const productionHome = resolve(expandUserPath(
52
- environment.DSH_CHATGPT_FREE_HOME?.trim()
53
- || environment.DSH_CHATGPT_WEB_HOME?.trim()
54
- || join(homeDirectory, ".dsh", "storages", "chatgpt-web"),
55
- ));
56
- if (home === productionHome) {
57
- throw new Error("DEV profile home must differ from the production home");
58
- }
59
- const launcherUserData = join(home, "launcher");
60
- return {
61
- home,
62
- codexHome: join(home, "codex-home"),
63
- launcherUserData,
64
- launcherStatePath: join(launcherUserData, "launcher-state.json"),
65
- descriptorPath: join(home, "runtime", "launcher-browser.json"),
66
- chatsPath: join(home, "chats"),
67
- runtimePath: join(home, "runtime", "dev-chat"),
68
- configPath: join(home, "config.json"),
69
- };
70
- }
71
-
72
- export interface DevChatExperimentalFeatures {
73
- biggerContext: boolean;
74
- }
75
-
76
- /** Read the canonical DEV runtime setting consumed by repository chat commands. */
77
- export function readDevChatExperimentalFeatures(
78
- paths = resolveDevProfilePaths(),
79
- ): DevChatExperimentalFeatures {
80
- let value: unknown;
81
- try {
82
- value = JSON.parse(readFileSync(paths.configPath, "utf8"));
83
- } catch (error) {
84
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return { biggerContext: false };
85
- throw new Error(
86
- `Could not read DEV runtime settings from ${paths.configPath}: ${error instanceof Error ? error.message : String(error)}`,
87
- );
88
- }
89
- if (!value || typeof value !== "object" || Array.isArray(value) || (value as { version?: unknown }).version !== 3) {
90
- throw new Error(`Invalid DEV runtime settings in ${paths.configPath}`);
91
- }
92
- const enabled = (value as { experimentalBiggerContext?: unknown }).experimentalBiggerContext;
93
- if (enabled !== undefined && typeof enabled !== "boolean") {
94
- throw new Error(`Invalid Bigger Context preference in ${paths.configPath}`);
95
- }
96
- return { biggerContext: enabled === true };
97
- }
98
-
99
- export function activateDevProfileEnvironment(paths = resolveDevProfilePaths()): DevProfilePaths {
100
- process.env.DSH_CHATGPT_FREE_DEV_HOME = paths.home;
101
- process.env.DSH_CHATGPT_FREE_HOME = paths.home;
102
- if (getConfigPath() !== paths.configPath) {
103
- throw new Error("DEV profile environment did not resolve to its isolated configuration path");
104
- }
105
- return paths;
106
- }
107
-
108
- function executableFile(path: string): boolean {
109
- try {
110
- const stat = statSync(path);
111
- return stat.isFile() && (process.platform === "win32" || (stat.mode & 0o111) !== 0);
112
- } catch {
113
- return false;
114
- }
115
- }
116
-
117
- export function installedLauncherCandidates({
118
- environment = process.env,
119
- homeDirectory = homedir(),
120
- platform = process.platform,
121
- windowsInstallLocation,
122
- }: {
123
- environment?: NodeJS.ProcessEnv;
124
- homeDirectory?: string;
125
- platform?: NodeJS.Platform;
126
- windowsInstallLocation?: string;
127
- } = {}): string[] {
128
- const override = environment.CODEX_WEB_GPT_LAUNCHER_EXECUTABLE?.trim();
129
- const candidates = override ? [expandUserPath(override)] : [];
130
- const targetPath = platform === "win32" ? win32 : posix;
131
- if (platform === "darwin") {
132
- candidates.push(
133
- "/Applications/Codex Web GPT.app/Contents/MacOS/Codex Web GPT",
134
- posix.join(homeDirectory, "Applications", "Codex Web GPT.app", "Contents", "MacOS", "Codex Web GPT"),
135
- );
136
- } else if (platform === "win32") {
137
- const registeredLocation = windowsInstallLocation?.trim()
138
- || (process.platform === "win32" ? registeredWindowsLauncherInstallLocation() : undefined);
139
- if (registeredLocation && win32.isAbsolute(registeredLocation)) {
140
- candidates.push(win32.join(registeredLocation, "Codex Web GPT.exe"));
141
- } else {
142
- const localAppData = environment.LOCALAPPDATA?.trim();
143
- if (localAppData) {
144
- candidates.push(win32.join(localAppData, "Programs", "Codex Web GPT", "Codex Web GPT.exe"));
145
- }
146
- }
147
- } else if (platform === "linux") {
148
- candidates.push(posix.join(homeDirectory, ".local", "bin", "codex-web-gpt"));
149
- for (const entry of (environment.PATH || "").split(":").filter(Boolean)) {
150
- candidates.push(posix.join(entry, "codex-web-gpt"));
151
- }
152
- }
153
- return [...new Set(candidates.map(candidate => targetPath.resolve(candidate)))];
154
- }
155
-
156
- export function findInstalledLauncherExecutable(options: Parameters<typeof installedLauncherCandidates>[0] = {}): string {
157
- const candidates = installedLauncherCandidates(options);
158
- const executable = candidates.find(executableFile);
159
- if (executable) return executable;
160
- throw new Error(
161
- "Installed Codex Web GPT launcher was not found. Install it first or set CODEX_WEB_GPT_LAUNCHER_EXECUTABLE to its absolute executable path."
162
- + ` Checked: ${candidates.join(", ") || "no platform candidates"}`,
163
- );
164
- }
165
-
166
- export function devLauncherEnvironment(
167
- paths: DevProfilePaths,
168
- environment: NodeJS.ProcessEnv = process.env,
169
- ): NodeJS.ProcessEnv {
170
- const childEnvironment = { ...environment };
171
- delete childEnvironment.DSH_CHATGPT_FREE_HOME;
172
- delete childEnvironment.DSH_CHATGPT_WEB_HOME;
173
- childEnvironment.DSH_CHATGPT_FREE_DEV_HOME = paths.home;
174
- return childEnvironment;
175
- }
176
-
177
- function devDescriptor(path: string): LauncherBrowserHostDescriptor {
178
- const descriptor = readLauncherBrowserHostDescriptor(path);
179
- if (descriptor.profile !== DEV_LAUNCHER_PROFILE) {
180
- throw new Error(`Launcher descriptor belongs to ${descriptor.profile}, not the isolated DEV profile`);
181
- }
182
- return descriptor;
183
- }
184
-
185
- export async function waitForDevLauncher(
186
- descriptorPath: string,
187
- timeoutMs = 30_000,
188
- ): Promise<LauncherBrowserHostDescriptor> {
189
- const deadline = Date.now() + timeoutMs;
190
- let lastError = "descriptor is not ready";
191
- while (Date.now() < deadline) {
192
- try {
193
- return devDescriptor(descriptorPath);
194
- } catch (error) {
195
- lastError = error instanceof Error ? error.message : String(error);
196
- }
197
- await new Promise(resolveWait => setTimeout(resolveWait, 100));
198
- }
199
- throw new Error(`DEV launcher did not become ready within ${timeoutMs}ms: ${lastError}`);
200
- }
201
-
202
- export async function launchDevProfile(
203
- paths = resolveDevProfilePaths(),
204
- options: { executable?: string; timeoutMs?: number } = {},
205
- ): Promise<{ descriptor: LauncherBrowserHostDescriptor; executable: string; alreadyRunning: boolean }> {
206
- let existing: LauncherBrowserHostDescriptor | undefined;
207
- try { existing = devDescriptor(paths.descriptorPath); }
208
- catch { /* A stale or absent descriptor is replaced only by its owning launcher. */ }
209
-
210
- const executable = options.executable ? resolve(options.executable) : findInstalledLauncherExecutable();
211
- if (!isAbsolute(executable) || !executableFile(executable)) {
212
- throw new Error(`DEV launcher executable is not an executable regular file: ${executable}`);
213
- }
214
- const child = spawn(executable, ["--dev-profile"], {
215
- detached: true,
216
- env: devLauncherEnvironment(paths),
217
- stdio: "ignore",
218
- windowsHide: false,
219
- });
220
- child.unref();
221
- const descriptor = await waitForDevLauncher(paths.descriptorPath, options.timeoutMs);
222
- return { descriptor, executable, alreadyRunning: existing?.pid === descriptor.pid };
223
- }
1
+ import { execFileSync, spawn } from "node:child_process";
2
+ import { readFileSync, statSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { isAbsolute, join, posix, resolve, win32 } from "node:path";
5
+ import { expandUserPath, getConfigPath } from "../config";
6
+ import {
7
+ readLauncherBrowserHostDescriptor,
8
+ type LauncherBrowserHostDescriptor,
9
+ } from "../launcher-browser-host";
10
+
11
+ import { DEV_LAUNCHER_PROFILE } from "./constants";
12
+
13
+ export interface DevProfilePaths {
14
+ home: string;
15
+ codexHome: string;
16
+ launcherUserData: string;
17
+ launcherStatePath: string;
18
+ descriptorPath: string;
19
+ chatsPath: string;
20
+ runtimePath: string;
21
+ configPath: string;
22
+ }
23
+
24
+ const WINDOWS_LAUNCHER_GUID = "d1a6026a-6210-588e-9a2b-da3936f94e02";
25
+
26
+ function registeredWindowsLauncherInstallLocation(): string | undefined {
27
+ try {
28
+ const output = execFileSync(
29
+ "reg.exe",
30
+ ["query", `HKCU\\Software\\${WINDOWS_LAUNCHER_GUID}`, "/v", "InstallLocation"],
31
+ { encoding: "utf8", windowsHide: true },
32
+ );
33
+ const match = output.match(/^\s*InstallLocation\s+REG_SZ\s+(.+?)\s*$/mi);
34
+ return match && win32.isAbsolute(match[1]) ? match[1] : undefined;
35
+ } catch {
36
+ return undefined;
37
+ }
38
+ }
39
+
40
+ export function resolveDevProfilePaths({
41
+ environment = process.env,
42
+ homeDirectory = homedir(),
43
+ }: {
44
+ environment?: NodeJS.ProcessEnv;
45
+ homeDirectory?: string;
46
+ } = {}): DevProfilePaths {
47
+ const home = resolve(expandUserPath(
48
+ environment.DSH_CHATGPT_FREE_DEV_HOME?.trim()
49
+ || join(homeDirectory, ".dsh", "storages", "chatgpt-web-dev"),
50
+ ));
51
+ const productionHome = resolve(expandUserPath(
52
+ environment.DSH_CHATGPT_FREE_HOME?.trim()
53
+ || environment.DSH_CHATGPT_WEB_HOME?.trim()
54
+ || join(homeDirectory, ".dsh", "storages", "chatgpt-web"),
55
+ ));
56
+ if (home === productionHome) {
57
+ throw new Error("DEV profile home must differ from the production home");
58
+ }
59
+ const launcherUserData = join(home, "launcher");
60
+ return {
61
+ home,
62
+ codexHome: join(home, "codex-home"),
63
+ launcherUserData,
64
+ launcherStatePath: join(launcherUserData, "launcher-state.json"),
65
+ descriptorPath: join(home, "runtime", "launcher-browser.json"),
66
+ chatsPath: join(home, "chats"),
67
+ runtimePath: join(home, "runtime", "dev-chat"),
68
+ configPath: join(home, "config.json"),
69
+ };
70
+ }
71
+
72
+ export interface DevChatExperimentalFeatures {
73
+ biggerContext: boolean;
74
+ }
75
+
76
+ /** Read the canonical DEV runtime setting consumed by repository chat commands. */
77
+ export function readDevChatExperimentalFeatures(
78
+ paths = resolveDevProfilePaths(),
79
+ ): DevChatExperimentalFeatures {
80
+ let value: unknown;
81
+ try {
82
+ value = JSON.parse(readFileSync(paths.configPath, "utf8"));
83
+ } catch (error) {
84
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return { biggerContext: false };
85
+ throw new Error(
86
+ `Could not read DEV runtime settings from ${paths.configPath}: ${error instanceof Error ? error.message : String(error)}`,
87
+ );
88
+ }
89
+ if (!value || typeof value !== "object" || Array.isArray(value) || (value as { version?: unknown }).version !== 3) {
90
+ throw new Error(`Invalid DEV runtime settings in ${paths.configPath}`);
91
+ }
92
+ const enabled = (value as { experimentalBiggerContext?: unknown }).experimentalBiggerContext;
93
+ if (enabled !== undefined && typeof enabled !== "boolean") {
94
+ throw new Error(`Invalid Bigger Context preference in ${paths.configPath}`);
95
+ }
96
+ return { biggerContext: enabled === true };
97
+ }
98
+
99
+ export function activateDevProfileEnvironment(paths = resolveDevProfilePaths()): DevProfilePaths {
100
+ process.env.DSH_CHATGPT_FREE_DEV_HOME = paths.home;
101
+ process.env.DSH_CHATGPT_FREE_HOME = paths.home;
102
+ if (getConfigPath() !== paths.configPath) {
103
+ throw new Error("DEV profile environment did not resolve to its isolated configuration path");
104
+ }
105
+ return paths;
106
+ }
107
+
108
+ function executableFile(path: string): boolean {
109
+ try {
110
+ const stat = statSync(path);
111
+ return stat.isFile() && (process.platform === "win32" || (stat.mode & 0o111) !== 0);
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+
117
+ export function installedLauncherCandidates({
118
+ environment = process.env,
119
+ homeDirectory = homedir(),
120
+ platform = process.platform,
121
+ windowsInstallLocation,
122
+ }: {
123
+ environment?: NodeJS.ProcessEnv;
124
+ homeDirectory?: string;
125
+ platform?: NodeJS.Platform;
126
+ windowsInstallLocation?: string;
127
+ } = {}): string[] {
128
+ const override = environment.CODEX_WEB_GPT_LAUNCHER_EXECUTABLE?.trim();
129
+ const candidates = override ? [expandUserPath(override)] : [];
130
+ const targetPath = platform === "win32" ? win32 : posix;
131
+ if (platform === "darwin") {
132
+ candidates.push(
133
+ "/Applications/Codex Web GPT.app/Contents/MacOS/Codex Web GPT",
134
+ posix.join(homeDirectory, "Applications", "Codex Web GPT.app", "Contents", "MacOS", "Codex Web GPT"),
135
+ );
136
+ } else if (platform === "win32") {
137
+ const registeredLocation = windowsInstallLocation?.trim()
138
+ || (process.platform === "win32" ? registeredWindowsLauncherInstallLocation() : undefined);
139
+ if (registeredLocation && win32.isAbsolute(registeredLocation)) {
140
+ candidates.push(win32.join(registeredLocation, "Codex Web GPT.exe"));
141
+ } else {
142
+ const localAppData = environment.LOCALAPPDATA?.trim();
143
+ if (localAppData) {
144
+ candidates.push(win32.join(localAppData, "Programs", "Codex Web GPT", "Codex Web GPT.exe"));
145
+ }
146
+ }
147
+ } else if (platform === "linux") {
148
+ candidates.push(posix.join(homeDirectory, ".local", "bin", "codex-web-gpt"));
149
+ for (const entry of (environment.PATH || "").split(":").filter(Boolean)) {
150
+ candidates.push(posix.join(entry, "codex-web-gpt"));
151
+ }
152
+ }
153
+ return [...new Set(candidates.map(candidate => targetPath.resolve(candidate)))];
154
+ }
155
+
156
+ export function findInstalledLauncherExecutable(options: Parameters<typeof installedLauncherCandidates>[0] = {}): string {
157
+ const candidates = installedLauncherCandidates(options);
158
+ const executable = candidates.find(executableFile);
159
+ if (executable) return executable;
160
+ throw new Error(
161
+ "Installed Codex Web GPT launcher was not found. Install it first or set CODEX_WEB_GPT_LAUNCHER_EXECUTABLE to its absolute executable path."
162
+ + ` Checked: ${candidates.join(", ") || "no platform candidates"}`,
163
+ );
164
+ }
165
+
166
+ export function devLauncherEnvironment(
167
+ paths: DevProfilePaths,
168
+ environment: NodeJS.ProcessEnv = process.env,
169
+ ): NodeJS.ProcessEnv {
170
+ const childEnvironment = { ...environment };
171
+ delete childEnvironment.DSH_CHATGPT_FREE_HOME;
172
+ delete childEnvironment.DSH_CHATGPT_WEB_HOME;
173
+ childEnvironment.DSH_CHATGPT_FREE_DEV_HOME = paths.home;
174
+ return childEnvironment;
175
+ }
176
+
177
+ function devDescriptor(path: string): LauncherBrowserHostDescriptor {
178
+ const descriptor = readLauncherBrowserHostDescriptor(path);
179
+ if (descriptor.profile !== DEV_LAUNCHER_PROFILE) {
180
+ throw new Error(`Launcher descriptor belongs to ${descriptor.profile}, not the isolated DEV profile`);
181
+ }
182
+ return descriptor;
183
+ }
184
+
185
+ export async function waitForDevLauncher(
186
+ descriptorPath: string,
187
+ timeoutMs = 30_000,
188
+ ): Promise<LauncherBrowserHostDescriptor> {
189
+ const deadline = Date.now() + timeoutMs;
190
+ let lastError = "descriptor is not ready";
191
+ while (Date.now() < deadline) {
192
+ try {
193
+ return devDescriptor(descriptorPath);
194
+ } catch (error) {
195
+ lastError = error instanceof Error ? error.message : String(error);
196
+ }
197
+ await new Promise(resolveWait => setTimeout(resolveWait, 100));
198
+ }
199
+ throw new Error(`DEV launcher did not become ready within ${timeoutMs}ms: ${lastError}`);
200
+ }
201
+
202
+ export async function launchDevProfile(
203
+ paths = resolveDevProfilePaths(),
204
+ options: { executable?: string; timeoutMs?: number } = {},
205
+ ): Promise<{ descriptor: LauncherBrowserHostDescriptor; executable: string; alreadyRunning: boolean }> {
206
+ let existing: LauncherBrowserHostDescriptor | undefined;
207
+ try { existing = devDescriptor(paths.descriptorPath); }
208
+ catch { /* A stale or absent descriptor is replaced only by its owning launcher. */ }
209
+
210
+ const executable = options.executable ? resolve(options.executable) : findInstalledLauncherExecutable();
211
+ if (!isAbsolute(executable) || !executableFile(executable)) {
212
+ throw new Error(`DEV launcher executable is not an executable regular file: ${executable}`);
213
+ }
214
+ const child = spawn(executable, ["--dev-profile"], {
215
+ detached: true,
216
+ env: devLauncherEnvironment(paths),
217
+ stdio: "ignore",
218
+ windowsHide: false,
219
+ });
220
+ child.unref();
221
+ const descriptor = await waitForDevLauncher(paths.descriptorPath, options.timeoutMs);
222
+ return { descriptor, executable, alreadyRunning: existing?.pid === descriptor.pid };
223
+ }