@wlv-zedd/dsh-chatgpt-web 1.0.0

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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +140 -0
  3. package/assets/demo.gif +0 -0
  4. package/assets/hero-demo.png +0 -0
  5. package/assets/promo-dshmarket-official.png +0 -0
  6. package/cordis.patch.yml +4 -0
  7. package/lib/cli.js +239642 -0
  8. package/lib/plugin.js +195 -0
  9. package/package.json +88 -0
  10. package/screenshots.json +5 -0
  11. package/src/adapters/base.ts +16 -0
  12. package/src/adapters/chatgpt-web/adapter-error.ts +59 -0
  13. package/src/adapters/chatgpt-web/browser-helper-main.ts +513 -0
  14. package/src/adapters/chatgpt-web/browser-helper-prompt-selection.ts +27 -0
  15. package/src/adapters/chatgpt-web/browser-worker.ts +4944 -0
  16. package/src/adapters/chatgpt-web/codex-rollout-environment.ts +628 -0
  17. package/src/adapters/chatgpt-web/compaction-handoff.ts +533 -0
  18. package/src/adapters/chatgpt-web/compaction-transaction.ts +142 -0
  19. package/src/adapters/chatgpt-web/concurrency.ts +6 -0
  20. package/src/adapters/chatgpt-web/conversation-key.ts +58 -0
  21. package/src/adapters/chatgpt-web/environment.ts +669 -0
  22. package/src/adapters/chatgpt-web/index.ts +1544 -0
  23. package/src/adapters/chatgpt-web/input-tokens.ts +74 -0
  24. package/src/adapters/chatgpt-web/launcher-helper-client.ts +695 -0
  25. package/src/adapters/chatgpt-web/markdown.ts +418 -0
  26. package/src/adapters/chatgpt-web/mcp-main.ts +25 -0
  27. package/src/adapters/chatgpt-web/mcp-server.ts +933 -0
  28. package/src/adapters/chatgpt-web/model.ts +70 -0
  29. package/src/adapters/chatgpt-web/native-compaction-control.ts +74 -0
  30. package/src/adapters/chatgpt-web/output-validation.ts +62 -0
  31. package/src/adapters/chatgpt-web/process-line-writer.ts +46 -0
  32. package/src/adapters/chatgpt-web/prompt.ts +702 -0
  33. package/src/adapters/chatgpt-web/retry-policy.ts +73 -0
  34. package/src/adapters/chatgpt-web/rolling-checkpoint.ts +384 -0
  35. package/src/adapters/chatgpt-web/thread-environment.ts +238 -0
  36. package/src/adapters/chatgpt-web/tool-stream-parser.ts +601 -0
  37. package/src/adapters/chatgpt-web/turn-broker.ts +1481 -0
  38. package/src/adapters/chatgpt-web/turn-execution.ts +816 -0
  39. package/src/adapters/chatgpt-web/turn-progress.ts +292 -0
  40. package/src/adapters/chatgpt-web/usage.ts +121 -0
  41. package/src/adapters/image.ts +9 -0
  42. package/src/bridge.ts +1083 -0
  43. package/src/browser-login.ts +521 -0
  44. package/src/chatgpt-session.ts +240 -0
  45. package/src/chatgpt-web-models.ts +400 -0
  46. package/src/cli.ts +568 -0
  47. package/src/codex-integration-document.ts +824 -0
  48. package/src/codex-integration-journal.ts +212 -0
  49. package/src/codex-integration-route.ts +515 -0
  50. package/src/codex-integration-shared.ts +332 -0
  51. package/src/codex-integration.ts +529 -0
  52. package/src/codex-interrupt-hook.ts +158 -0
  53. package/src/config.ts +616 -0
  54. package/src/dev-chat/cli.ts +432 -0
  55. package/src/dev-chat/constants.ts +3 -0
  56. package/src/dev-chat/driver.ts +655 -0
  57. package/src/dev-chat/profile.ts +223 -0
  58. package/src/dev-chat/session.ts +287 -0
  59. package/src/dev-chat/transport.ts +54 -0
  60. package/src/doctor.ts +237 -0
  61. package/src/event-queue.ts +45 -0
  62. package/src/http-body.ts +30 -0
  63. package/src/launcher-browser-host.ts +695 -0
  64. package/src/lib/errors.ts +281 -0
  65. package/src/lib/token-estimate.ts +42 -0
  66. package/src/login-helper.cjs +140 -0
  67. package/src/model-catalog.ts +197 -0
  68. package/src/native-passthrough.ts +261 -0
  69. package/src/plugin.ts +191 -0
  70. package/src/process.ts +45 -0
  71. package/src/responses/compaction.ts +199 -0
  72. package/src/responses/parser.ts +633 -0
  73. package/src/responses/reasoning-envelope.ts +49 -0
  74. package/src/responses/schema.ts +172 -0
  75. package/src/responses/state.ts +230 -0
  76. package/src/server.ts +1111 -0
  77. package/src/service.ts +315 -0
  78. package/src/setup.ts +671 -0
  79. package/src/stall-timeout.ts +23 -0
  80. package/src/tunnel-service.ts +160 -0
  81. package/src/tunnel.ts +417 -0
  82. package/src/turndown-plugin-gfm.d.ts +5 -0
  83. package/src/types.ts +307 -0
  84. package/src/usage/totals.ts +12 -0
  85. package/src/version.ts +1 -0
@@ -0,0 +1,432 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { stdin, stdout } from "node:process";
4
+ import { loadConfig, resolveDevSetupConnectorName } from "../config";
5
+ import {
6
+ inspectLauncherBrowserHost,
7
+ inspectLauncherBrowserHostLiveness,
8
+ readLauncherBrowserHostDescriptor,
9
+ } from "../launcher-browser-host";
10
+ import { setupDevProfile } from "../setup";
11
+ import { tunnelStatus } from "../tunnel";
12
+ import {
13
+ createLauncherDevAdapter,
14
+ DevChatDriver,
15
+ type DevChatEvent,
16
+ type DevContextStatus,
17
+ } from "./driver";
18
+ import {
19
+ createDevContextFiller,
20
+ DEV_CHAT_MODELS,
21
+ DevChatStore,
22
+ type DevChatModel,
23
+ type DevChatState,
24
+ } from "./session";
25
+ import { startDevChatTransport } from "./transport";
26
+ import {
27
+ activateDevProfileEnvironment,
28
+ launchDevProfile,
29
+ readDevChatExperimentalFeatures,
30
+ resolveDevProfilePaths,
31
+ } from "./profile";
32
+ import { DEV_CONFIG_PURPOSE, DEV_LAUNCHER_PROFILE } from "./constants";
33
+
34
+ const DEV_HELP = `Codex Web GPT DEV chat
35
+
36
+ Usage:
37
+ codex-chatgpt-web dev launcher
38
+ codex-chatgpt-web dev status [--json]
39
+ codex-chatgpt-web dev setup --browser-only [--automatic-browser-interaction]
40
+ codex-chatgpt-web dev setup --full --tunnel-id ID --runtime-key-file PATH [--automatic-browser-interaction|--zero-risk-browser-interaction]
41
+ codex-chatgpt-web dev chat NAME [--model MODEL] [MESSAGE]
42
+ codex-chatgpt-web dev list
43
+
44
+ Repository shortcut:
45
+ bun run dev:launcher
46
+ bun run dev:chat NAME "message"
47
+ bun run dev:chat NAME
48
+
49
+ Interactive commands:
50
+ /status Show estimated next-turn context occupancy
51
+ /fill TOKENS Append deterministic inert context without opening ChatGPT
52
+ /send-fill TOKENS Send deterministic inert text through the live browser now
53
+ /compact Run the real browser compaction path now
54
+ /model MODEL Select zero-risk, luna, think, light, medium, high, extra-high, or pro
55
+ /reset yes Clear this named DEV chat and create a new thread identity
56
+ /help Show this command list
57
+ /exit Exit
58
+
59
+ Experimental settings:
60
+ Bigger Context Enable in Settings; adapts context across 1, 2, or 3 messages
61
+ `;
62
+
63
+ function takeFlag(args: string[], name: string): boolean {
64
+ const index = args.indexOf(name);
65
+ if (index < 0) return false;
66
+ args.splice(index, 1);
67
+ return true;
68
+ }
69
+
70
+ function takeOption(args: string[], name: string): string | undefined {
71
+ const index = args.indexOf(name);
72
+ if (index < 0) return undefined;
73
+ const value = args[index + 1];
74
+ if (!value || value.startsWith("--")) throw new Error(`${name} requires a value`);
75
+ args.splice(index, 2);
76
+ return value;
77
+ }
78
+
79
+ function color(code: number, text: string): string {
80
+ return stdout.isTTY ? `\u001b[${code}m${text}\u001b[0m` : text;
81
+ }
82
+
83
+ const bold = (text: string) => color(1, text);
84
+ const dim = (text: string) => color(2, text);
85
+ const cyan = (text: string) => color(36, text);
86
+ const yellow = (text: string) => color(33, text);
87
+
88
+ function compactJson(value: unknown, limit = 2_000): string {
89
+ const encoded = JSON.stringify(value);
90
+ if (encoded.length <= limit) return encoded;
91
+ return `${encoded.slice(0, limit)}… (${encoded.length.toLocaleString("en-US")} chars)`;
92
+ }
93
+
94
+ function modelFromCli(value: string | undefined): DevChatModel | undefined {
95
+ if (!value) return undefined;
96
+ const normalized = value.trim().toLowerCase();
97
+ const slug = normalized.startsWith("chatgpt-web/") ? normalized : `chatgpt-web/${normalized}`;
98
+ if (!(DEV_CHAT_MODELS as readonly string[]).includes(slug)) {
99
+ throw new Error(`Unknown DEV model ${JSON.stringify(value)}; choose zero-risk, luna, think, light, medium, high, extra-high, or pro`);
100
+ }
101
+ return slug as DevChatModel;
102
+ }
103
+
104
+ function statusLine(status: DevContextStatus): string {
105
+ const main = `${status.inputTokens.toLocaleString("en-US")} / ${status.autoCompactTokenLimit.toLocaleString("en-US")} tokens (${status.percent}%)`;
106
+ const transport = status.browserInputTokenLimit
107
+ ? ` · Luna browser request budget ${status.browserInputTokenLimit.toLocaleString("en-US")}`
108
+ : "";
109
+ return `${main} · ${status.inputItems} history items${transport}`;
110
+ }
111
+
112
+ class EventRenderer {
113
+ private channel: "reasoning" | "commentary" | "text" | undefined;
114
+
115
+ write(event: DevChatEvent): void {
116
+ if (event.type === "reasoning" || event.type === "commentary" || event.type === "text") {
117
+ if (this.channel !== event.type) {
118
+ if (this.channel) stdout.write("\n");
119
+ const label = event.type === "text" ? "assistant" : event.type;
120
+ stdout.write(`${event.type === "text" ? cyan(label) : dim(label)}> `);
121
+ this.channel = event.type;
122
+ }
123
+ stdout.write(event.text);
124
+ return;
125
+ }
126
+ this.endChannel();
127
+ if (event.type === "tool_call") {
128
+ stdout.write(`${yellow("tool")}> ${event.name} ${compactJson(event.input)}\n`);
129
+ } else if (event.type === "tool_result") {
130
+ stdout.write(`${dim("simulated")}> ${event.name} ${compactJson(event.receipt)}\n`);
131
+ } else if (event.type === "compaction_start") {
132
+ stdout.write(`${yellow("compact")}> ${event.reason} browser compaction started (${event.inputItems} input items)\n`);
133
+ } else {
134
+ stdout.write(`${yellow("compact")}> ${event.reason} browser compaction completed (${event.inputItems} replacement items)\n`);
135
+ }
136
+ }
137
+
138
+ finish(): void {
139
+ this.endChannel();
140
+ }
141
+
142
+ private endChannel(): void {
143
+ if (!this.channel) return;
144
+ stdout.write("\n");
145
+ this.channel = undefined;
146
+ }
147
+ }
148
+
149
+ function printHeader(
150
+ state: DevChatState,
151
+ created: boolean,
152
+ status: DevContextStatus,
153
+ mode: "browser-only" | "full",
154
+ biggerContext: boolean,
155
+ ): void {
156
+ stdout.write(`${bold("Codex Web GPT DEV")} · ${created ? "created" : "continued"} chat ${cyan(state.name)}\n`);
157
+ stdout.write(`model ${state.model} · ${mode === "full" ? "tools explicitly simulated" : "browser-only, no outer tools"} · live launcher browser\n`);
158
+ stdout.write(`context ${statusLine(status)}\n`);
159
+ if (biggerContext) {
160
+ stdout.write(`${yellow("Bigger Context experimental")} · adaptive 1/2/3-message context · same-agent compaction handoff · elevated rate-limit/cooldown risk\n`);
161
+ }
162
+ stdout.write(`${dim("Codex route is untouched. No Responses port is bound, replaced, stopped, or restarted.")}\n`);
163
+ }
164
+
165
+ async function assertLauncherReady(config: ReturnType<typeof loadConfig>): Promise<void> {
166
+ if (config.purpose !== DEV_CONFIG_PURPOSE) {
167
+ throw new Error("DEV chat requires a configuration created inside the isolated DEV profile");
168
+ }
169
+ if (config.browserHost !== "launcher" || !config.browserHostDescriptorPath) {
170
+ throw new Error("DEV chat requires the isolated desktop launcher; run bun run dev:launcher first");
171
+ }
172
+ if (config.browserInteractionMode === "manual") {
173
+ await inspectLauncherBrowserHostLiveness(config.browserHostDescriptorPath, {
174
+ expectedProfile: DEV_LAUNCHER_PROFILE,
175
+ });
176
+ } else {
177
+ await inspectLauncherBrowserHost(config.browserHostDescriptorPath, {
178
+ expectedProfile: DEV_LAUNCHER_PROFILE,
179
+ });
180
+ }
181
+ }
182
+
183
+ async function executeMessage(driver: DevChatDriver, state: DevChatState, message: string): Promise<void> {
184
+ const renderer = new EventRenderer();
185
+ try {
186
+ const result = await driver.send(state, message, event => renderer.write(event));
187
+ renderer.finish();
188
+ stdout.write(`${dim(`usage ${result.usage.inputTokens.toLocaleString("en-US")} input + ${result.usage.outputTokens.toLocaleString("en-US")} output · context ${statusLine(result.status)}`)}\n`);
189
+ } catch (error) {
190
+ renderer.finish();
191
+ throw error;
192
+ }
193
+ }
194
+
195
+ async function interactive(driver: DevChatDriver, state: DevChatState): Promise<void> {
196
+ stdout.write(`${dim("Type a message or /help. Ctrl-C or Ctrl-D exits.")}\n`);
197
+ const reader = createInterface({ input: stdin, output: stdout });
198
+ reader.on("SIGINT", () => reader.close());
199
+ try {
200
+ for (;;) {
201
+ let line: string;
202
+ try { line = await reader.question(`${cyan(state.name)}> `); }
203
+ catch { break; }
204
+ const value = line.trim();
205
+ if (!value) continue;
206
+ try {
207
+ if (!value.startsWith("/")) {
208
+ await executeMessage(driver, state, value);
209
+ continue;
210
+ }
211
+ const [command, argument, ...rest] = value.slice(1).split(/\s+/);
212
+ if (command === "exit" || command === "quit") break;
213
+ if (command === "help") {
214
+ if (argument) throw new Error("Usage: /help");
215
+ stdout.write(DEV_HELP);
216
+ } else if (command === "status") {
217
+ if (argument) throw new Error("Usage: /status");
218
+ stdout.write(`context ${statusLine(driver.status(state))}\n`);
219
+ } else if (command === "fill") {
220
+ if (rest.length > 0) throw new Error("Usage: /fill TOKENS");
221
+ const tokens = Number(argument);
222
+ const result = driver.fill(state, tokens);
223
+ stdout.write(`added ${result.addedTokens.toLocaleString("en-US")} measured synthetic tokens · context ${statusLine(result.status)}\n`);
224
+ } else if (command === "send-fill") {
225
+ if (rest.length > 0) throw new Error("Usage: /send-fill TOKENS");
226
+ const filler = createDevContextFiller(Number(argument));
227
+ stdout.write(`sending ${filler.tokens.toLocaleString("en-US")} measured synthetic tokens through the live browser\n`);
228
+ await executeMessage(driver, state, filler.text);
229
+ } else if (command === "compact") {
230
+ if (argument) throw new Error("Usage: /compact");
231
+ const renderer = new EventRenderer();
232
+ try {
233
+ const status = await driver.compact(state, event => renderer.write(event));
234
+ renderer.finish();
235
+ stdout.write(`context ${statusLine(status)}\n`);
236
+ } catch (error) {
237
+ renderer.finish();
238
+ throw error;
239
+ }
240
+ } else if (command === "model") {
241
+ const model = modelFromCli(argument);
242
+ if (!model || rest.length > 0) throw new Error("Usage: /model MODEL");
243
+ driver.setModel(state, model);
244
+ stdout.write(`model ${state.model} · context ${statusLine(driver.status(state))}\n`);
245
+ } else if (command === "reset") {
246
+ if (argument !== "yes" || rest.length > 0) {
247
+ stdout.write("Use /reset yes to clear this DEV chat.\n");
248
+ continue;
249
+ }
250
+ driver.reset(state);
251
+ stdout.write(`reset ${state.name}; new empty DEV thread created\n`);
252
+ } else {
253
+ stdout.write(`Unknown DEV command /${command}. Use /help.\n`);
254
+ }
255
+ } catch (error) {
256
+ stdout.write(`${color(31, "error")}> ${error instanceof Error ? error.message : String(error)}\n`);
257
+ }
258
+ }
259
+ } finally {
260
+ reader.close();
261
+ }
262
+ }
263
+
264
+ export async function runDevCommand(args: string[]): Promise<void> {
265
+ const action = args.shift() ?? "help";
266
+ const paths = resolveDevProfilePaths();
267
+ if (action === "help") {
268
+ if (args.length > 0) throw new Error(`Unknown DEV arguments: ${args.join(" ")}`);
269
+ stdout.write(DEV_HELP);
270
+ return;
271
+ }
272
+ if (action === "list") {
273
+ activateDevProfileEnvironment(paths);
274
+ const store = new DevChatStore(paths.chatsPath);
275
+ if (args.length > 0) throw new Error(`Unknown DEV arguments: ${args.join(" ")}`);
276
+ const chats = store.list();
277
+ if (chats.length === 0) {
278
+ stdout.write("No named DEV chats yet.\n");
279
+ return;
280
+ }
281
+ for (const chat of chats) {
282
+ stdout.write(`${chat.name}\t${chat.model}\tturns=${chat.turns}\tcompactions=${chat.compactions}\titems=${chat.inputItems}\t${chat.updatedAt}\n`);
283
+ }
284
+ return;
285
+ }
286
+ if (action === "launcher") {
287
+ if (args.length > 0) throw new Error(`Unknown DEV launcher arguments: ${args.join(" ")}`);
288
+ const launched = await launchDevProfile(paths);
289
+ stdout.write(
290
+ `${launched.alreadyRunning ? "Opened" : "Started"} isolated DEV launcher`
291
+ + ` (pid ${launched.descriptor.pid})\n`
292
+ + `DEV home: ${paths.home}\n`
293
+ + `ChatGPT session: ${paths.launcherUserData}\n`,
294
+ );
295
+ return;
296
+ }
297
+ if (action === "status") {
298
+ activateDevProfileEnvironment(paths);
299
+ const json = takeFlag(args, "--json");
300
+ if (args.length > 0) throw new Error(`Unknown DEV status arguments: ${args.join(" ")}`);
301
+ let launcher: { running: boolean; pid?: number; profile?: string; error?: string } = { running: false };
302
+ try {
303
+ const descriptor = readLauncherBrowserHostDescriptor(paths.descriptorPath);
304
+ launcher = { running: true, pid: descriptor.pid, profile: descriptor.profile };
305
+ if (descriptor.profile !== DEV_LAUNCHER_PROFILE) {
306
+ launcher = { running: false, error: `descriptor belongs to ${descriptor.profile}` };
307
+ }
308
+ } catch (error) {
309
+ launcher = { running: false, error: error instanceof Error ? error.message : String(error) };
310
+ }
311
+ let config: { configured: boolean; mode?: string; purpose?: string; error?: string } = { configured: false };
312
+ let mcpRuntime: { required: boolean; ready: boolean; detail?: string } = { required: false, ready: false };
313
+ if (existsSync(paths.configPath)) {
314
+ try {
315
+ const loaded = loadConfig();
316
+ config = { configured: true, mode: loaded.mode, purpose: loaded.purpose };
317
+ if (loaded.mode === "full") {
318
+ const inspected = tunnelStatus(loaded);
319
+ mcpRuntime = { required: true, ready: inspected.ok && inspected.ready, detail: inspected.detail };
320
+ }
321
+ } catch (error) {
322
+ config = { configured: false, error: error instanceof Error ? error.message : String(error) };
323
+ }
324
+ }
325
+ const features = readDevChatExperimentalFeatures(paths);
326
+ const status = { paths, launcher, config, mcpRuntime, features };
327
+ if (json) stdout.write(`${JSON.stringify(status, null, 2)}\n`);
328
+ else {
329
+ stdout.write(`DEV home: ${paths.home}\n`);
330
+ stdout.write(`launcher: ${launcher.running ? `running (pid ${launcher.pid})` : `not ready${launcher.error ? ` · ${launcher.error}` : ""}`}\n`);
331
+ stdout.write(`config: ${config.configured ? `${config.mode} (${config.purpose})` : `not ready${config.error ? ` · ${config.error}` : ""}`}\n`);
332
+ stdout.write(`MCP runtime: ${mcpRuntime.required ? (mcpRuntime.ready ? "ready" : `not ready${mcpRuntime.detail ? ` · ${mcpRuntime.detail}` : ""}`) : "not required"}\n`);
333
+ stdout.write(`Bigger Context: ${features.biggerContext ? "enabled (experimental, adaptive 1/2/3 messages; same-agent compaction handoff)" : "disabled"}\n`);
334
+ stdout.write("Codex route: isolated and unused\nResponses listener: not started\n");
335
+ }
336
+ return;
337
+ }
338
+ if (action === "setup") {
339
+ activateDevProfileEnvironment(paths);
340
+ const browserOnly = takeFlag(args, "--browser-only");
341
+ const full = takeFlag(args, "--full");
342
+ if (browserOnly === full) throw new Error("Choose exactly one DEV setup mode: --browser-only or --full");
343
+ const tunnelId = takeOption(args, "--tunnel-id");
344
+ const runtimeKeyFile = takeOption(args, "--runtime-key-file");
345
+ const appName = takeOption(args, "--app-name");
346
+ const descriptorPath = takeOption(args, "--browser-host-descriptor") ?? paths.descriptorPath;
347
+ const acknowledgedUnofficial = takeFlag(args, "--acknowledge-unofficial");
348
+ const refreshAccountCapabilities = takeFlag(args, "--refresh-account-capabilities");
349
+ const automaticBrowserInteraction = takeFlag(args, "--automatic-browser-interaction");
350
+ const manualBrowserInteraction = takeFlag(args, "--zero-risk-browser-interaction");
351
+ if (automaticBrowserInteraction && manualBrowserInteraction) {
352
+ throw new Error("Choose at most one browser interaction mode");
353
+ }
354
+ const biggerContext = takeFlag(args, "--bigger-context");
355
+ const standardContext = takeFlag(args, "--standard-context");
356
+ if (biggerContext && standardContext) {
357
+ throw new Error("Choose at most one context mode: --bigger-context or --standard-context");
358
+ }
359
+ if (args.length > 0) throw new Error(`Unknown DEV setup arguments: ${args.join(" ")}`);
360
+ const result = await setupDevProfile({
361
+ mode: full ? "full" : "browser-only",
362
+ browserHostDescriptorPath: descriptorPath,
363
+ refreshAccountCapabilities,
364
+ acknowledgedUnofficial,
365
+ ...(automaticBrowserInteraction || manualBrowserInteraction
366
+ ? { browserInteractionMode: manualBrowserInteraction ? "manual" : "automatic" }
367
+ : {}),
368
+ ...(biggerContext || standardContext ? { experimentalBiggerContext: biggerContext } : {}),
369
+ ...(tunnelId ? { tunnelId } : {}),
370
+ ...(runtimeKeyFile ? { runtimeKeyFile } : {}),
371
+ ...(appName ? { appName } : {}),
372
+ });
373
+ stdout.write(
374
+ `Isolated DEV profile configured (${result.mode}) at ${result.configPath}.\n`
375
+ + "No Codex route, Responses listener, or system service was installed."
376
+ + " In Full mode, the DEV launcher owns the isolated MCP tunnel.\n",
377
+ );
378
+ return;
379
+ }
380
+ if (action !== "chat") throw new Error(`Unknown DEV action: ${action}\n\n${DEV_HELP}`);
381
+
382
+ activateDevProfileEnvironment(paths);
383
+ const store = new DevChatStore(paths.chatsPath);
384
+ const requestedModel = modelFromCli(takeOption(args, "--model"));
385
+ const name = args.shift();
386
+ if (!name) throw new Error(`DEV chat name is required\n\n${DEV_HELP}`);
387
+ const message = args.join(" ").trim();
388
+ if (!existsSync(paths.configPath)) {
389
+ throw new Error(
390
+ "DEV profile is not configured. In the window labelled DEV: sign in, run the browser smoke test,"
391
+ + " and initialize the DEV profile. Complete optional MCP setup only for simulated tool rounds.",
392
+ );
393
+ }
394
+ const config = loadConfig();
395
+ if (config.mode === "full" && config.appName !== resolveDevSetupConnectorName(config.appName)) {
396
+ throw new Error("DEV connector identity is outdated. Refresh the DEV profile in the launcher before starting a named chat");
397
+ }
398
+ const runtimeStateRoot = paths.runtimePath;
399
+ const features = readDevChatExperimentalFeatures(paths);
400
+ await assertLauncherReady(config);
401
+ const transport = config.mode === "full"
402
+ ? await startDevChatTransport(config, paths.runtimePath)
403
+ : undefined;
404
+ let driver: DevChatDriver | undefined;
405
+ try {
406
+ const runtimeConfig = transport?.config ?? config;
407
+ const runtime = createLauncherDevAdapter(
408
+ runtimeConfig,
409
+ runtimeStateRoot,
410
+ {
411
+ ...(transport ? { broker: transport.broker } : {}),
412
+ },
413
+ );
414
+ driver = new DevChatDriver(runtimeConfig, store, runtime.adapterFactory, process.cwd(), features);
415
+ const opened = driver.open(name, requestedModel);
416
+ if (requestedModel && opened.state.model !== requestedModel) {
417
+ driver.setModel(opened.state, requestedModel);
418
+ }
419
+ printHeader(opened.state, opened.created, driver.status(opened.state), runtimeConfig.mode, features.biggerContext);
420
+ if (message) await executeMessage(driver, opened.state, message);
421
+ else await interactive(driver, opened.state);
422
+ } finally {
423
+ const results = await Promise.allSettled([
424
+ driver?.close() ?? Promise.resolve(),
425
+ transport?.close() ?? Promise.resolve(),
426
+ ]);
427
+ const failures = results.filter((result): result is PromiseRejectedResult => result.status === "rejected");
428
+ if (failures.length > 0) {
429
+ throw new AggregateError(failures.map(result => result.reason), "DEV chat cleanup failed");
430
+ }
431
+ }
432
+ }
@@ -0,0 +1,3 @@
1
+ export const DEV_CONFIG_PURPOSE = "dev-harness" as const;
2
+ export const DEV_LAUNCHER_PROFILE = "development" as const;
3
+ export const DEV_TUNNEL_BASE_NAME = "codex-chatgpt-web-dev" as const;