@youdie006/prodex 0.34.0 → 0.35.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.
- package/dist/chatgpt-browser.js +137 -0
- package/dist/cli-help.js +4 -0
- package/dist/cli-pro.js +116 -3
- package/package.json +1 -1
package/dist/chatgpt-browser.js
CHANGED
|
@@ -2545,6 +2545,143 @@ export function resolveConversationToDelete(conversations, request) {
|
|
|
2545
2545
|
* Remove one conversation. ChatGPT deletes a chat by hiding it, which is the
|
|
2546
2546
|
* same call its own UI makes; the caller is responsible for confirming intent.
|
|
2547
2547
|
*/
|
|
2548
|
+
/**
|
|
2549
|
+
* Processes that ARE the browser prodex launched, from `ps` output.
|
|
2550
|
+
*
|
|
2551
|
+
* prodex only ever asked whether the debug port answered, so a Chrome that
|
|
2552
|
+
* stopped answering read as "not running". Measured on a real machine: such an
|
|
2553
|
+
* instance sat for four days with two renderers pinned near 100% and the
|
|
2554
|
+
* window server burning 75% CPU on its zombie window, and nothing reported it.
|
|
2555
|
+
* The port cannot tell a dead browser from an absent one; the process list can.
|
|
2556
|
+
*/
|
|
2557
|
+
export function findLaunchedBrowserProcesses(psOutput, input) {
|
|
2558
|
+
// `ps -Ao user,pid,command` leads with a user NAME, not a uid.
|
|
2559
|
+
const pidOf = (line) => {
|
|
2560
|
+
const match = /^\s*\S+\s+(\d+)\s/.exec(line);
|
|
2561
|
+
return match ? Number(match[1]) : undefined;
|
|
2562
|
+
};
|
|
2563
|
+
// Mentioning the flag is not being the browser: a shell, an editor, or the
|
|
2564
|
+
// very tool running this scan can carry it on its command line, and this list
|
|
2565
|
+
// is what gets SIGTERM. Caught live - the probe matched its own node process.
|
|
2566
|
+
const isBrowserCommand = (line) => {
|
|
2567
|
+
const command = line.replace(/^\s*\S+\s+\d+\s+/, "");
|
|
2568
|
+
// Only the executable counts, never the arguments: a process that merely
|
|
2569
|
+
// quotes a Chrome path is not Chrome. Everything up to the first flag is
|
|
2570
|
+
// the program, which keeps the spaces macOS puts in "Google Chrome".
|
|
2571
|
+
const executable = command.split(/\s-{1,2}\w/)[0];
|
|
2572
|
+
return /(^|[/\\])(google[ -]?chrome|chromium|chrome)( helper)?( \([^)]*\))?$/i.test(executable.trim());
|
|
2573
|
+
};
|
|
2574
|
+
const lines = psOutput.split(/\r?\n/).filter((line) => !/\bgrep\b/.test(line) && isBrowserCommand(line));
|
|
2575
|
+
// Exactly this port: a plain substring test let port 9 match 9333.
|
|
2576
|
+
const portFlag = new RegExp(`--remote-debugging-port=${input.port}(?!\\d)`);
|
|
2577
|
+
const mains = lines.filter((line) => portFlag.test(line));
|
|
2578
|
+
// The port is the instance's identity. A browser sharing the profile while
|
|
2579
|
+
// listening on another port belongs to someone else, and treating it as ours
|
|
2580
|
+
// made a check against an unused port report a healthy Chrome as wedged.
|
|
2581
|
+
if (mains.length === 0)
|
|
2582
|
+
return [];
|
|
2583
|
+
const helpers = input.profileDir.length > 0 ? lines.filter((line) => line.includes(input.profileDir) && !mains.includes(line)) : [];
|
|
2584
|
+
return [...mains, ...helpers].map(pidOf).filter((pid) => pid !== undefined);
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* A browser that is running but deaf is a different problem from one that is
|
|
2588
|
+
* gone, and it needs a different instruction: relaunching on top of it stacks a
|
|
2589
|
+
* second Chrome on the same profile rather than fixing anything.
|
|
2590
|
+
*/
|
|
2591
|
+
/**
|
|
2592
|
+
* The wedged instance, if there is one: a browser prodex launched that is still
|
|
2593
|
+
* running while its control port answers nothing.
|
|
2594
|
+
*/
|
|
2595
|
+
export function findWedgedBrowser(input = {}) {
|
|
2596
|
+
const port = resolveCdpPort(input.port);
|
|
2597
|
+
const profileDir = input.profileDir ?? defaultChatGptProfileDir();
|
|
2598
|
+
// -A over every user's processes is deliberate: the browser may have been
|
|
2599
|
+
// launched by another shell session than the one asking.
|
|
2600
|
+
const listed = spawnSync("ps", ["-Ao", "user,pid,command"], { encoding: "utf8", timeout: 10_000 });
|
|
2601
|
+
if (listed.status !== 0 || typeof listed.stdout !== "string")
|
|
2602
|
+
return [];
|
|
2603
|
+
return findLaunchedBrowserProcesses(listed.stdout, { port, profileDir });
|
|
2604
|
+
}
|
|
2605
|
+
const realSignals = {
|
|
2606
|
+
kill: (pid, signal) => process.kill(pid, signal),
|
|
2607
|
+
isAlive: (pid) => {
|
|
2608
|
+
try {
|
|
2609
|
+
process.kill(pid, 0);
|
|
2610
|
+
return true;
|
|
2611
|
+
}
|
|
2612
|
+
catch {
|
|
2613
|
+
return false;
|
|
2614
|
+
}
|
|
2615
|
+
},
|
|
2616
|
+
sleep: (ms) => sleep(ms)
|
|
2617
|
+
};
|
|
2618
|
+
/**
|
|
2619
|
+
* End a wedged instance. Callers must have confirmed intent before calling.
|
|
2620
|
+
*
|
|
2621
|
+
* Continue it first: a browser held in a stopped state cannot act on SIGTERM,
|
|
2622
|
+
* so it keeps the profile lock and the replacement launch fails with "no
|
|
2623
|
+
* reachable DevTools endpoint" - measured, on exactly that. Then ask politely,
|
|
2624
|
+
* and only insist on what will not go.
|
|
2625
|
+
*/
|
|
2626
|
+
export async function endWedgedBrowser(pids, signals = realSignals) {
|
|
2627
|
+
const send = (pid, signal) => {
|
|
2628
|
+
try {
|
|
2629
|
+
signals.kill(pid, signal);
|
|
2630
|
+
return true;
|
|
2631
|
+
}
|
|
2632
|
+
catch {
|
|
2633
|
+
return false;
|
|
2634
|
+
}
|
|
2635
|
+
};
|
|
2636
|
+
for (const pid of pids) {
|
|
2637
|
+
send(pid, "SIGCONT");
|
|
2638
|
+
send(pid, "SIGTERM");
|
|
2639
|
+
}
|
|
2640
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
2641
|
+
if (!pids.some((pid) => signals.isAlive(pid)))
|
|
2642
|
+
break;
|
|
2643
|
+
await signals.sleep(500);
|
|
2644
|
+
}
|
|
2645
|
+
for (const pid of pids.filter((pid) => signals.isAlive(pid)))
|
|
2646
|
+
send(pid, "SIGKILL");
|
|
2647
|
+
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
2648
|
+
if (!pids.some((pid) => signals.isAlive(pid)))
|
|
2649
|
+
break;
|
|
2650
|
+
await signals.sleep(500);
|
|
2651
|
+
}
|
|
2652
|
+
const failed = pids.filter((pid) => signals.isAlive(pid));
|
|
2653
|
+
return { ended: pids.filter((pid) => !failed.includes(pid)), failed };
|
|
2654
|
+
}
|
|
2655
|
+
/**
|
|
2656
|
+
* What recovery should do when a send finds the browser unreachable.
|
|
2657
|
+
*
|
|
2658
|
+
* Launching is right when the browser is gone and wrong when it is merely deaf:
|
|
2659
|
+
* a second Chrome on the same profile does not replace the wedged one, it joins
|
|
2660
|
+
* it, and the wedged one keeps burning CPU while nobody is looking. That is the
|
|
2661
|
+
* shape of the four-day incident this came from.
|
|
2662
|
+
*/
|
|
2663
|
+
export function browserRecoveryPlan(input) {
|
|
2664
|
+
if (input.reachable)
|
|
2665
|
+
return "reuse";
|
|
2666
|
+
if (input.wedgedPids.length === 0)
|
|
2667
|
+
return "launch";
|
|
2668
|
+
// Reporting a wedged browser is not enough - nobody ran a check for four
|
|
2669
|
+
// days. prodex started this browser and it has stopped answering its own
|
|
2670
|
+
// control port, so clearing it is what a person would do anyway. It waits for
|
|
2671
|
+
// several silent probes first: a browser mid-answer can miss a single poll,
|
|
2672
|
+
// and ending that one would throw away a consult in flight.
|
|
2673
|
+
if (input.confirmedSilent === true && input.autoClearDisabled !== true)
|
|
2674
|
+
return "clear-and-launch";
|
|
2675
|
+
return "reset-first";
|
|
2676
|
+
}
|
|
2677
|
+
export function wedgedBrowserBlocker(pids, port) {
|
|
2678
|
+
return {
|
|
2679
|
+
code: "browser_wedged",
|
|
2680
|
+
message: `The dedicated ChatGPT browser is still running (pid ${pids.join(", ")}) but stopped answering on 127.0.0.1:${port}. A wedged Chrome keeps burning CPU and holding its window, so it will not recover on its own.`,
|
|
2681
|
+
retryable: true,
|
|
2682
|
+
next_step: "Clear it with `prodex pro browser reset --confirm` (it previews first), then run `prodex pro browser login`."
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2548
2685
|
export function deleteConversationExpression(conversationId) {
|
|
2549
2686
|
return `(async () => {
|
|
2550
2687
|
let token = "";
|
package/dist/cli-help.js
CHANGED
|
@@ -274,6 +274,9 @@ export function printProBrowserHelp(stdout, sourceCli) {
|
|
|
274
274
|
const chatDeleteUsage = sourceCli
|
|
275
275
|
? `${cli} pro browser chat-delete${sourceCliOption} [--title "exact title" | --id <id>] [--confirm-delete] # previews unless --confirm-delete`
|
|
276
276
|
: `prodex pro browser chat-delete [--source-cli /absolute/path/to/dist/cli.js] [--title "exact title" | --id <id>] [--confirm-delete] # previews unless --confirm-delete`;
|
|
277
|
+
const resetUsage = sourceCli
|
|
278
|
+
? `${cli} pro browser reset${sourceCliOption} [--port 9333] [--confirm] # end a browser that runs but stopped answering; previews unless --confirm`
|
|
279
|
+
: "prodex pro browser reset [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--confirm] # end a browser that runs but stopped answering; previews unless --confirm";
|
|
277
280
|
const recoverUsage = sourceCli
|
|
278
281
|
? `${cli} pro browser recover${sourceCliOption} [--cwd /absolute/path/to/repo] [--port 9333] --target-url <thread-url> [--timeout-ms 60000] # fetch a finished answer (deep research reports too) from a thread whose send timed out`
|
|
279
282
|
: "prodex pro browser recover [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] --target-url <thread-url> [--timeout-ms 60000] # fetch a finished answer (deep research reports too) from a thread whose send timed out";
|
|
@@ -288,6 +291,7 @@ Commands:
|
|
|
288
291
|
${projectDeleteUsage}
|
|
289
292
|
${chatsUsage}
|
|
290
293
|
${chatDeleteUsage}
|
|
294
|
+
${resetUsage}
|
|
291
295
|
${askUsage}
|
|
292
296
|
${recoverUsage}
|
|
293
297
|
|
package/dist/cli-pro.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, statSync } from "node:fs";
|
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { buildDryRunBundle } from "./bundle.js";
|
|
5
|
-
import { DEFAULT_CDP_PORT, resolveCdpPort, resolveConversationToDelete, resolveProjectToDelete, chatGptVisibilityBlocker, defaultChatGptProfileDir, formatDurationMs, getChatGptBrowserStatus, listChatGptModelOptions, deleteChatGptConversation, deleteChatGptProject, listChatGptProjectsWithIds, listRecentChatGptConversations, listChatGptSidebarProjects, normalizeChatGptTargetUrl, openChatGptBrowser, openChatGptTab, parseProMode, parseReasoningEffort, defaultTimeoutForTools, ensureVirtualDisplay, minimizeChatGptWindow, readLastBrowserLoginLaunch, resolveVirtualDisplayPreference, resolveBrowserWindowMode, resolveHeadlessPreference, recordBrowserLoginLaunch, recoverChatGptAnswerFromThread, sendChatGptPrompt } from "./chatgpt-browser.js";
|
|
5
|
+
import { DEFAULT_CDP_PORT, resolveCdpPort, resolveConversationToDelete, resolveProjectToDelete, chatGptVisibilityBlocker, defaultChatGptProfileDir, formatDurationMs, getChatGptBrowserStatus, listChatGptModelOptions, deleteChatGptConversation, endWedgedBrowser, browserRecoveryPlan, findWedgedBrowser, wedgedBrowserBlocker, deleteChatGptProject, listChatGptProjectsWithIds, listRecentChatGptConversations, listChatGptSidebarProjects, normalizeChatGptTargetUrl, openChatGptBrowser, openChatGptTab, parseProMode, parseReasoningEffort, defaultTimeoutForTools, ensureVirtualDisplay, minimizeChatGptWindow, readLastBrowserLoginLaunch, resolveVirtualDisplayPreference, resolveBrowserWindowMode, resolveHeadlessPreference, recordBrowserLoginLaunch, recoverChatGptAnswerFromThread, sendChatGptPrompt } from "./chatgpt-browser.js";
|
|
6
6
|
import { ASK_PRO_BOOLEAN_FLAGS, ASK_PRO_PREVIEW_VALUE_FLAGS, ASK_PRO_VALUE_FLAGS, assertHelpRequestArgs, assertNoExtraArgs, assertOnlyOptions, findHelpFlagIndexBeforePromptDelimiter, formatCliCommand, hasAskProDryRunMode, hasAskProMode, hasAskProSendMode, isHelpSubcommand, parseAskProArgs, printHelpIfRequested, readFlag, readPortFlag, readPositionalsWithOptions, readNonNegativeIntegerFlag, readPositiveIntegerFlag, readRepeatedFlag, resolveCwdFlag, resolveOptionalFileFlag, unknownSubcommandError } from "./cli-args.js";
|
|
7
7
|
import { printProBrowserHelp, printProHelp } from "./cli-help.js";
|
|
8
8
|
import { listRawResultsForInspection, listTasksForInspection } from "./cli-ledger.js";
|
|
@@ -265,6 +265,18 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
265
265
|
throw new Error(`A ChatGPT browser is already running on port ${port} on ${runningVirtual ? "a virtual display" : "your desktop"}, but ${wantsVirtualDisplay ? "a virtual display" : "your desktop"} was requested. Close it first (\`pkill -f "remote-debugging-port=${port}"\`), then rerun.`);
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
|
+
// A wedged browser is unreachable, so without this login would launch a
|
|
269
|
+
// second Chrome onto the same profile and leave the first one burning
|
|
270
|
+
// CPU - the same mistake the unattended recovery path made. Checked
|
|
271
|
+
// here, after the arguments are validated, so a bad flag still reports
|
|
272
|
+
// as a bad flag.
|
|
273
|
+
if (!alreadyRunning) {
|
|
274
|
+
const wedgedPids = findWedgedBrowser({ port, ...(profileDir ? { profileDir } : {}) });
|
|
275
|
+
if (wedgedPids.length > 0) {
|
|
276
|
+
const wedgedBlocker = wedgedBrowserBlocker(wedgedPids, port);
|
|
277
|
+
throw new Error(`${wedgedBlocker.message} ${wedgedBlocker.next_step}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
268
280
|
const opened = alreadyRunning
|
|
269
281
|
? { profileDir: profileDir ?? defaultChatGptProfileDir(), port }
|
|
270
282
|
: openChatGptBrowser({
|
|
@@ -537,6 +549,47 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
537
549
|
io.stderr(`recovered: answer saved to .bridge; re-print with \`prodex pro latest --cwd ${recoverCwd}\``);
|
|
538
550
|
return 0;
|
|
539
551
|
}
|
|
552
|
+
if (browserSubcommand === "reset") {
|
|
553
|
+
if (printProBrowserHelpIfRequested(browserArgs, "pro browser reset", io, {
|
|
554
|
+
valueFlags: ["--port", "--profile-dir", "--source-cli"],
|
|
555
|
+
booleanFlags: ["--confirm"]
|
|
556
|
+
})) {
|
|
557
|
+
return 0;
|
|
558
|
+
}
|
|
559
|
+
assertOnlyOptions(browserArgs, "pro browser reset", ["--port", "--profile-dir", "--source-cli"], ["--confirm"]);
|
|
560
|
+
const resetPort = readPortFlag(browserArgs, "--port");
|
|
561
|
+
const resetProfileDir = readFlag(browserArgs, "--profile-dir");
|
|
562
|
+
const reachable = (await getChatGptBrowserStatus({ ...(resetPort !== undefined ? { port: resetPort } : {}), timeoutMs: 2_000 }))
|
|
563
|
+
.reachable;
|
|
564
|
+
const pids = findWedgedBrowser({
|
|
565
|
+
...(resetPort !== undefined ? { port: resetPort } : {}),
|
|
566
|
+
...(resetProfileDir ? { profileDir: resetProfileDir } : {})
|
|
567
|
+
});
|
|
568
|
+
if (pids.length === 0) {
|
|
569
|
+
io.stdout("No browser launched by prodex is running; nothing to reset.");
|
|
570
|
+
return 0;
|
|
571
|
+
}
|
|
572
|
+
// A browser that still answers is doing its job - ending it would take
|
|
573
|
+
// an in-flight consult with it.
|
|
574
|
+
if (reachable) {
|
|
575
|
+
io.stdout(`The dedicated browser is running and answering (pid ${pids.join(", ")}). Nothing was ended.`);
|
|
576
|
+
io.stdout("Close it yourself if you meant to, or run this again once it stops responding.");
|
|
577
|
+
return 0;
|
|
578
|
+
}
|
|
579
|
+
if (!browserArgs.includes("--confirm")) {
|
|
580
|
+
io.stdout(`Would end the wedged browser: pid ${pids.join(", ")}.`);
|
|
581
|
+
io.stdout("It is not answering its control port, so nothing in flight is lost; the profile and login stay on disk.");
|
|
582
|
+
io.stdout("Nothing was ended. Re-run with --confirm to go ahead.");
|
|
583
|
+
return 0;
|
|
584
|
+
}
|
|
585
|
+
const outcome = await endWedgedBrowser(pids);
|
|
586
|
+
if (outcome.ended.length > 0)
|
|
587
|
+
io.stdout(`ended wedged browser: pid ${outcome.ended.join(", ")}`);
|
|
588
|
+
if (outcome.failed.length > 0)
|
|
589
|
+
io.stdout(`could not end: pid ${outcome.failed.join(", ")} (try again from the account that started it)`);
|
|
590
|
+
io.stdout("Run `prodex pro browser login` to start a fresh one; it reuses the same profile.");
|
|
591
|
+
return 0;
|
|
592
|
+
}
|
|
540
593
|
if (browserSubcommand === "chats") {
|
|
541
594
|
if (printProBrowserHelpIfRequested(browserArgs, "pro browser chats", io, { valueFlags: ["--port", "--timeout-ms", "--limit", "--source-cli"] }))
|
|
542
595
|
return 0;
|
|
@@ -639,6 +692,7 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
639
692
|
"project-delete",
|
|
640
693
|
"chats",
|
|
641
694
|
"chat-delete",
|
|
695
|
+
"reset",
|
|
642
696
|
"recover"
|
|
643
697
|
]);
|
|
644
698
|
}
|
|
@@ -1393,7 +1447,60 @@ export async function performBrowserConsultForMcp(cwd, input, onProgress) {
|
|
|
1393
1447
|
* throws) when recovery does not reach readiness, so the original blocker
|
|
1394
1448
|
* flow stays intact.
|
|
1395
1449
|
*/
|
|
1450
|
+
/**
|
|
1451
|
+
* Is the control port really dead, or was that one unlucky poll?
|
|
1452
|
+
*
|
|
1453
|
+
* A browser writing a long answer can miss a probe. Ending that one would throw
|
|
1454
|
+
* away a consult in flight, so silence has to hold across several tries before
|
|
1455
|
+
* anything gets killed.
|
|
1456
|
+
*/
|
|
1457
|
+
async function confirmBrowserSilence(port, probes = 3, gapMs = 2_000) {
|
|
1458
|
+
for (let attempt = 0; attempt < probes; attempt += 1) {
|
|
1459
|
+
if (attempt > 0)
|
|
1460
|
+
await sleep(gapMs);
|
|
1461
|
+
const status = await getChatGptBrowserStatus({ ...(port !== undefined ? { port } : {}), timeoutMs: 2_000 });
|
|
1462
|
+
if (status.reachable)
|
|
1463
|
+
return false;
|
|
1464
|
+
}
|
|
1465
|
+
return true;
|
|
1466
|
+
}
|
|
1467
|
+
function autoClearDisabledByEnv(env = process.env) {
|
|
1468
|
+
const raw = (env.PRODEX_NO_AUTO_CLEAR ?? "").trim().toLowerCase();
|
|
1469
|
+
return raw === "1" || raw === "true" || raw === "yes";
|
|
1470
|
+
}
|
|
1396
1471
|
export async function attemptBrowserAutoRecovery(stderr, options) {
|
|
1472
|
+
// Launching is right when the browser is gone and wrong when it is only deaf:
|
|
1473
|
+
// a second Chrome on the same profile joins the wedged one rather than
|
|
1474
|
+
// replacing it, and the wedged one keeps burning CPU while nobody looks. This
|
|
1475
|
+
// runs unattended for agents, so it is the path that let four days pass.
|
|
1476
|
+
const wedged = findWedgedBrowser({ ...(options.port !== undefined ? { port: options.port } : {}) });
|
|
1477
|
+
if (wedged.length > 0) {
|
|
1478
|
+
// Confirm the silence before ending anything: one missed poll is a busy
|
|
1479
|
+
// browser, several in a row is a dead one.
|
|
1480
|
+
const confirmedSilent = await confirmBrowserSilence(options.port);
|
|
1481
|
+
const plan = browserRecoveryPlan({
|
|
1482
|
+
reachable: false,
|
|
1483
|
+
wedgedPids: wedged,
|
|
1484
|
+
confirmedSilent,
|
|
1485
|
+
autoClearDisabled: autoClearDisabledByEnv()
|
|
1486
|
+
});
|
|
1487
|
+
if (plan === "reset-first") {
|
|
1488
|
+
const blocker = wedgedBrowserBlocker(wedged, resolveCdpPort(options.port));
|
|
1489
|
+
stderr(`recover: ${blocker.message}`);
|
|
1490
|
+
stderr(`recover: ${blocker.next_step}`);
|
|
1491
|
+
return false;
|
|
1492
|
+
}
|
|
1493
|
+
stderr(`recover: the browser stopped answering; ending it (pid ${wedged.join(", ")}) and starting a fresh one...`);
|
|
1494
|
+
await endWedgedBrowser(wedged);
|
|
1495
|
+
// Wait for the profile lock to actually clear rather than guessing at a
|
|
1496
|
+
// delay: the replacement launch fails outright if the old process still
|
|
1497
|
+
// holds it, which is how the first self-heal attempt ended.
|
|
1498
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
1499
|
+
if (findWedgedBrowser({ ...(options.port !== undefined ? { port: options.port } : {}) }).length === 0)
|
|
1500
|
+
break;
|
|
1501
|
+
await sleep(1_000);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1397
1504
|
stderr("recover: browser is not running - launching the dedicated ChatGPT browser (Ctrl+C aborts)...");
|
|
1398
1505
|
try {
|
|
1399
1506
|
// Reuse the profile the user last logged in with; launching the default
|
|
@@ -1869,8 +1976,14 @@ export async function printProductCheck(store, io, args, configCwd = io.cwd) {
|
|
|
1869
1976
|
if (browserStatus) {
|
|
1870
1977
|
const visibilityBlocker = chatGptVisibilityBlocker(browserStatus.visibilityState, browserStatus.url);
|
|
1871
1978
|
if (!browserStatus.reachable) {
|
|
1872
|
-
|
|
1873
|
-
|
|
1979
|
+
// An unreachable port has two very different causes, and prodex used to
|
|
1980
|
+
// report both as "not running": the browser really is gone, or it is still
|
|
1981
|
+
// there and has stopped answering. The second keeps burning CPU until
|
|
1982
|
+
// somebody notices, and nobody notices a message that says it is absent.
|
|
1983
|
+
const wedged = findWedgedBrowser({ ...(browserCommandOptions.port !== undefined ? { port: browserCommandOptions.port } : {}) });
|
|
1984
|
+
const blocker = wedged.length > 0 ? wedgedBrowserBlocker(wedged, browserCommandOptions.port ?? DEFAULT_CDP_PORT) : browserStatus.blocker;
|
|
1985
|
+
io.stdout(`chatgpt: ${blocker?.code ?? "unreachable"} - ${blocker?.message ?? "browser is not reachable"}`);
|
|
1986
|
+
const nextStep = productCheckBrowserNextStep(blocker?.next_step, sourceCli, browserCommandOptions);
|
|
1874
1987
|
if (nextStep)
|
|
1875
1988
|
io.stdout(`next: ${nextStep}`);
|
|
1876
1989
|
}
|