@youdie006/prodex 0.40.13 → 0.40.14

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/README.md CHANGED
@@ -73,10 +73,16 @@ For a one-time visible login followed by a verified headless handoff, use `prode
73
73
 
74
74
  Finish any native browser or OS confirmation before requesting the handoff: Chrome does not expose every native dialog through its page-control interface. The guard detects page targets and rendered dialogs, not every OS prompt. Incognito, guest, and explicitly selected Chrome sub-profiles are refused.
75
75
 
76
+ A Chrome account-connection chooser is separate from ChatGPT sign-in. If prodex confirms that chooser is displayed, choose for yourself whether to connect Chrome to the account; "Use Chrome without an account" declines that separate connection. A hidden or unreadable internal target still blocks the handoff but is not proof that a prompt is visible or that ChatGPT is logged out.
77
+
78
+ "Login readiness is not yet confirmed" does not mean the saved login was lost. Inspect the dedicated browser before signing in again. An explicit `login_required`, `cloudflare_check`, `captcha_required`, or `permission_required` during a headless/virtual-display wait stops promptly: use `prodex pro browser login --headed` to handle the reported step visibly. A usage limit or missing composer has its own next step instead of a blanket login instruction.
79
+
76
80
  While Pro thinks, progress goes to stderr: connecting, prompt sent, elapsed time while generating. A Pro selection raises the send budget to twenty minutes on its own; `--timeout-ms` overrides it. Answers are read from the rendered page, so formatting can differ from the original message. If the dedicated browser is not running, an interactive `ask` starts it, waits for your saved session, and retries once (`--no-auto-login` turns that off; scripts opt in with `--auto-login`).
77
81
 
78
82
  If the browser stops responding after your question was sent, prodex stops without sending it again. Use the `thread` and `request_id` from the error with `prodex pro browser recover --target-url <thread-url> --request-id <32hex>` (MCP: `pro_recover`). The request ID verifies that the recovered assistant answer follows that exact marked user turn. Legacy recovery without it remains available but returns `request_verified: false` and a warning.
79
83
 
84
+ If submission itself is unconfirmed, do not resend or simply increase the timeout: first inspect the original conversation for the `[prodex-request:...]` marker in the error. Missing login signals or text still in the composer do not prove that the question was never submitted.
85
+
80
86
  Useful flags on every send:
81
87
 
82
88
  | Flag | What it does |
@@ -6,7 +6,7 @@ import { ChatGptBrowserBlockerError, attachmentPresenceExpression, composerTextS
6
6
  function blocked(message) {
7
7
  throw new ChatGptBrowserBlockerError({
8
8
  code: "browser_handoff_blocked", message, retryable: false,
9
- next_step: "Keep only the intended idle ChatGPT tab open, finish any Chrome account or permission confirmation yourself, and retry `prodex pro browser login --background`. Do not close active work or log in again solely because the handoff stopped."
9
+ next_step: "Inspect the dedicated browser and keep only the intended idle ChatGPT tab open, then retry `prodex pro browser login --background`. Complete a login or permission step only if it is actually shown. Do not close active work or log in again solely because the handoff stopped."
10
10
  });
11
11
  }
12
12
  function browserIdentity(port, profileDir) {
@@ -62,8 +62,32 @@ async function singlePage(port) {
62
62
  if (!Array.isArray(response))
63
63
  blocked("The browser page list is invalid.");
64
64
  const pages = response.filter((page) => page?.type === "page");
65
- if (pages.length !== 1)
66
- blocked("The dedicated browser has other tabs or a Chrome confirmation surface; nothing was closed.");
65
+ if (pages.length !== 1) {
66
+ const chooser = pages.find((page) => page.url === "chrome://signin-dice-web-intercept.top-chrome/chrome-signin");
67
+ if (chooser) {
68
+ let state;
69
+ try {
70
+ const socket = localSocket(chooser.webSocketDebuggerUrl, port, "page");
71
+ const reply = await request(socket, "Runtime.evaluate", {
72
+ expression: `(() => { const r = document.body?.getBoundingClientRect(); return { visibilityState: document.visibilityState, width: r?.width, height: r?.height, isChooser: !!document.querySelector('chrome-signin-app') }; })()`,
73
+ returnByValue: true
74
+ });
75
+ if (!reply.exceptionDetails)
76
+ state = reply.result?.value;
77
+ }
78
+ catch { /* An unreadable internal target is not proof of a visible prompt. */ }
79
+ if (state?.visibilityState === "visible" && state.isChooser === true &&
80
+ typeof state.width === "number" && state.width > 0 && typeof state.height === "number" && state.height > 0) {
81
+ throw new ChatGptBrowserBlockerError({
82
+ code: "browser_account_confirmation", retryable: false,
83
+ message: "Chrome is showing its browser-account connection chooser. This is separate from ChatGPT login; no browser was closed.",
84
+ next_step: "Choose in the dedicated Chrome window yourself. 'Use Chrome without an account' declines Chrome account connection if you do not want it. Keep the ChatGPT tab open, then retry `prodex pro browser login --background`."
85
+ });
86
+ }
87
+ blocked("Chrome has an internal account target, but its chooser visibility was not confirmed. This does not establish a ChatGPT login problem; nothing was closed.");
88
+ }
89
+ blocked("The dedicated browser has additional page targets; nothing was closed.");
90
+ }
67
91
  const page = pages[0];
68
92
  const url = new URL(page.url);
69
93
  if (url.protocol !== "https:" || url.hostname !== "chatgpt.com" || url.username || url.password || url.port ||
@@ -702,6 +702,7 @@ export function formatDurationMs(ms) {
702
702
  export function acceptanceTimeoutError(ctx) {
703
703
  const uiLikelyChanged = ctx.composerStillHasText || !ctx.submitButtonFound;
704
704
  const took = `${formatDurationMs(ctx.timeoutMs)} (${ctx.timeoutMs}ms)`;
705
+ const inspect = `Do not resend automatically: submission was attempted but acceptance is unconfirmed. Inspect the original conversation${ctx.requestId ? ` for [prodex-request:${ctx.requestId}]` : ""} before another request.`;
705
706
  if (uiLikelyChanged) {
706
707
  const detail = [
707
708
  ctx.composerStillHasText ? "the composer still holds the prompt" : undefined,
@@ -709,13 +710,16 @@ export function acceptanceTimeoutError(ctx) {
709
710
  ]
710
711
  .filter(Boolean)
711
712
  .join(" and ");
712
- return new Error(`Timed out after ${took} and ChatGPT never registered the prompt (${detail}). ` +
713
- "The ChatGPT web UI may have changed, so prodex could not submit. Update prodex " +
714
- "(npm i -g @youdie006/prodex@latest); if it persists, report it at " +
715
- "https://github.com/youdie006/prodex/issues. You can also paste the prompt manually in the visible browser.");
713
+ return new ChatGptBrowserBlockerError({
714
+ code: "send_ui_changed", retryable: false,
715
+ message: `Timed out after ${took} without confirming prompt acceptance (${detail}). The ChatGPT web UI may have changed.`,
716
+ next_step: `${inspect} Update prodex (npm i -g @youdie006/prodex@latest); if it persists, report it at https://github.com/youdie006/prodex/issues.`
717
+ });
716
718
  }
717
- return new Error(`Timed out after ${took} waiting for ChatGPT to accept the prompt. ` +
718
- "Pro reasoning can run many minutes. Raise --timeout-ms and retry.");
719
+ return new ChatGptBrowserBlockerError({
720
+ code: "prompt_acceptance_unconfirmed", retryable: false,
721
+ message: `Timed out after ${took} waiting for ChatGPT to accept the prompt.`, next_step: inspect
722
+ });
719
723
  }
720
724
  export function hasPartialChatGptAnswer(previousAssistantMessageCount, state) {
721
725
  return state.assistantMessageCount > previousAssistantMessageCount && isUsableChatGptAnswer(state.answer);
@@ -948,7 +952,7 @@ export function detectChatGptBlocker(text, visibleButtonLabels = []) {
948
952
  code: "cloudflare_check",
949
953
  message: "ChatGPT is showing a Cloudflare or human-verification interstitial.",
950
954
  retryable: true,
951
- next_step: "Complete the visible browser check manually, then retry."
955
+ next_step: "Complete the visible browser check manually."
952
956
  };
953
957
  }
954
958
  if (hasLikelyChatGptLoginPrompt(haystack)) {
@@ -956,7 +960,7 @@ export function detectChatGptBlocker(text, visibleButtonLabels = []) {
956
960
  code: "login_required",
957
961
  message: "ChatGPT is asking you to log in.",
958
962
  retryable: true,
959
- next_step: "Log in manually in the visible browser, then retry."
963
+ next_step: "Log in manually in the visible browser."
960
964
  };
961
965
  }
962
966
  // Match real captcha / human-verification phrasing only. Bare words like "robot"/"로봇"/"자동화"
@@ -967,7 +971,7 @@ export function detectChatGptBlocker(text, visibleButtonLabels = []) {
967
971
  code: "captcha_required",
968
972
  message: "ChatGPT is asking for captcha or human verification.",
969
973
  retryable: true,
970
- next_step: "Solve it manually in the visible browser, then retry."
974
+ next_step: "Solve it manually in the visible browser."
971
975
  };
972
976
  }
973
977
  if (/message limit|usage limit|model limit|rate limit|you.?ve reached|try again later|limit resets|사용 한도|메시지 한도|모델 한도|요금 제한|나중에 다시/i.test(haystack)) {
@@ -983,7 +987,7 @@ export function detectChatGptBlocker(text, visibleButtonLabels = []) {
983
987
  code: "permission_required",
984
988
  message: "ChatGPT requires account verification or permission handling.",
985
989
  retryable: true,
986
- next_step: "Complete the visible account or permission prompt manually, then retry."
990
+ next_step: "Complete the visible account or permission prompt manually."
987
991
  };
988
992
  }
989
993
  return undefined;
@@ -992,7 +996,15 @@ export function detectChatGptPageBlocker(state) {
992
996
  // Blocker scan uses the nav-excluded sample so a sidebar chat title cannot
993
997
  // fake a blocker; fall back to the nav-included sample / full text when the
994
998
  // scan sample is absent (older callers).
995
- return detectChatGptBlocker(state.blockerScanTextSample ?? state.blockerTextSample ?? state.textSample, state.visibleButtonLabels);
999
+ const rendered = detectChatGptBlocker(state.blockerScanTextSample ?? state.blockerTextSample ?? state.textSample, state.visibleButtonLabels);
1000
+ if (rendered)
1001
+ return rendered;
1002
+ // The interstitial can have an empty body. Never use a conversation title
1003
+ // alone when the composer exists or its state was not actually checked.
1004
+ if (state.hasComposer === false && /^just a moment\.{0,3}$/i.test(state.title?.trim() ?? "")) {
1005
+ return detectChatGptBlocker("Just a moment", []);
1006
+ }
1007
+ return undefined;
996
1008
  }
997
1009
  export function inferChatGptPageLoggedInLikely(state) {
998
1010
  // The logged-in signals live in the sidebar - "New chat", "Projects", the
@@ -1026,6 +1038,17 @@ export function chatGptBlockerErrorFromAnswerState(state) {
1026
1038
  export function chatGptBlockerFromAnswerState(state) {
1027
1039
  return detectChatGptPageBlocker(state);
1028
1040
  }
1041
+ function submittedRequestBlocker(blocker, requestId, thread) {
1042
+ return {
1043
+ ...blocker,
1044
+ retryable: false,
1045
+ ...(thread ? { thread } : {}),
1046
+ next_step: `${blocker.next_step ?? "Inspect the visible browser."} Do not automatically resend. ` +
1047
+ (thread
1048
+ ? `Collect the original answer with \`prodex pro browser recover --target-url ${thread} --request-id ${requestId}\` after resolving the blocker.`
1049
+ : `Submission is unconfirmed; inspect the original conversation for [prodex-request:${requestId}] before another request.`)
1050
+ };
1051
+ }
1029
1052
  export function computePromptAcceptanceDeadline(timeoutMs, startedAt) {
1030
1053
  return startedAt + Math.max(1, timeoutMs);
1031
1054
  }
@@ -3508,6 +3531,7 @@ export async function sendChatGptPrompt(options) {
3508
3531
  let beforeSubmit;
3509
3532
  let boundProjectId;
3510
3533
  let submitButtonFound = false;
3534
+ let submissionAttempted = false;
3511
3535
  const sendWarnings = [...preflightWarnings];
3512
3536
  // Anything the page put in front of prodex was answered on the caller's
3513
3537
  // behalf. The note is read at return time - there are several return paths -
@@ -3654,6 +3678,8 @@ export async function sendChatGptPrompt(options) {
3654
3678
  const last = [...document.querySelectorAll('[data-message-author-role="user"]')].at(-1);
3655
3679
  return Boolean(last && (last.innerText || "").includes(${JSON.stringify(`[prodex-request:${requestId}]`)}));
3656
3680
  })()`;
3681
+ // A dispatch can reach Chrome even if its acknowledgement is lost.
3682
+ submissionAttempted = true;
3657
3683
  await cdp.send("Input.dispatchKeyEvent", enterKeyEvent("keyDown"));
3658
3684
  await cdp.send("Input.dispatchKeyEvent", enterKeyEvent("keyUp"));
3659
3685
  let promptPosted = await waitForExpressionTrue(cdp, promptPostedExpression, 1_500);
@@ -3677,6 +3703,13 @@ export async function sendChatGptPrompt(options) {
3677
3703
  await captureOnFailure(`send-${new Date().toISOString().replace(/[:.]/g, "-")}`);
3678
3704
  if (isCrashedTabError(error))
3679
3705
  throw attachSendWarnings(new ChatGptBrowserBlockerError(crashedTabBlocker(undefined, requestId)), sendWarnings);
3706
+ if (submissionAttempted) {
3707
+ const blocker = error instanceof ChatGptBrowserBlockerError ? error.blocker : {
3708
+ code: "prompt_acceptance_unconfirmed", retryable: false,
3709
+ message: `Browser control failed after submission was attempted: ${error instanceof Error ? error.message : String(error)}`
3710
+ };
3711
+ throw attachSendWarnings(new ChatGptBrowserBlockerError(submittedRequestBlocker(blocker, requestId)), sendWarnings);
3712
+ }
3680
3713
  throw attachSendWarnings(error, sendWarnings);
3681
3714
  }
3682
3715
  finally {
@@ -3701,7 +3734,7 @@ export async function sendChatGptPrompt(options) {
3701
3734
  }
3702
3735
  const runtimeBlocker = chatGptBlockerFromAnswerState(finalState);
3703
3736
  if (runtimeBlocker)
3704
- throw new ChatGptBrowserBlockerError(runtimeBlocker);
3737
+ throw new ChatGptBrowserBlockerError(submittedRequestBlocker(runtimeBlocker, requestId));
3705
3738
  dbgSend(`accept-poll url=${finalState.url} user=${finalState.userMessageCount} assistant=${finalState.assistantMessageCount} generating=${finalState.generating}`);
3706
3739
  if (requestMatches(finalState)) {
3707
3740
  if (normalizedTargetUrl)
@@ -3714,19 +3747,13 @@ export async function sendChatGptPrompt(options) {
3714
3747
  emitProgress("waiting", "prompt posting");
3715
3748
  }
3716
3749
  if (!accepted) {
3717
- // The session can expire mid-send (logged out during a long Pro wait), which
3718
- // otherwise surfaces as a cryptic "raise --timeout-ms" failure. Re-check the
3719
- // login state first and, if the session is gone, say so clearly so the user
3720
- // re-logs in instead of chasing a timeout.
3750
+ // Missing logged-in signals do not prove session expiry. Only rendered
3751
+ // blockers justify an authentication diagnosis after uncertain submission.
3721
3752
  try {
3722
3753
  const status = await evaluateOnPage(page, statusExpression());
3723
- if (!inferChatGptPageLoggedInLikely(status)) {
3724
- throw new ChatGptBrowserBlockerError({
3725
- code: "session_expired",
3726
- message: "The ChatGPT session is no longer logged in - it likely expired during the send.",
3727
- retryable: true,
3728
- next_step: "Run `prodex pro browser login`, log in, then retry."
3729
- });
3754
+ const blocker = detectChatGptPageBlocker(status);
3755
+ if (blocker) {
3756
+ throw new ChatGptBrowserBlockerError(submittedRequestBlocker(blocker, requestId));
3730
3757
  }
3731
3758
  }
3732
3759
  catch (error) {
@@ -3734,8 +3761,7 @@ export async function sendChatGptPrompt(options) {
3734
3761
  throw error;
3735
3762
  // best effort: a CDP eval failure here falls through to the generic timeout
3736
3763
  }
3737
- // A successful submit clears the composer, so text still sitting there means
3738
- // the send control did not register the prompt — the UI-changed signature.
3764
+ // Composer text is only a UI hint, not proof that submission failed.
3739
3765
  let composerStillHasText = false;
3740
3766
  try {
3741
3767
  const composerState = await evaluateOnPage(page, composerTextStateExpression());
@@ -3744,7 +3770,7 @@ export async function sendChatGptPrompt(options) {
3744
3770
  catch {
3745
3771
  // best effort: fall back to submit-button signal only
3746
3772
  }
3747
- throw acceptanceTimeoutError({ timeoutMs, composerStillHasText, submitButtonFound });
3773
+ throw acceptanceTimeoutError({ timeoutMs, composerStillHasText, submitButtonFound, requestId });
3748
3774
  }
3749
3775
  // Pin the conversation the prompt actually landed in. The browser is shared
3750
3776
  // (other agents, the user, tooling), and a tab that moves mid-wait made
@@ -3807,7 +3833,7 @@ export async function sendChatGptPrompt(options) {
3807
3833
  }
3808
3834
  const runtimeBlocker = chatGptBlockerFromAnswerState(finalState);
3809
3835
  if (runtimeBlocker)
3810
- throw new ChatGptBrowserBlockerError(runtimeBlocker);
3836
+ throw new ChatGptBrowserBlockerError(submittedRequestBlocker(runtimeBlocker, requestId, pinnedThreadUrl));
3811
3837
  emitProgress("waiting", finalState.generating ? "generating" : "stabilizing");
3812
3838
  if (!hasFreshChatGptAnswer(beforeSubmit.assistantMessageCount, finalState))
3813
3839
  continue;
package/dist/cli-pro.js CHANGED
@@ -2234,15 +2234,16 @@ export async function waitForChatGptLoginReady(stderr, options, deps = {}) {
2234
2234
  const pollMs = options.pollMs ?? 2_000;
2235
2235
  const hasInteractiveWindow = options.windowMode?.headless !== true && options.windowMode?.virtualDisplay !== true;
2236
2236
  const headedLoginCommand = options.headedLoginCommand ?? "prodex pro browser login --headed";
2237
- const headedLoginHint = ` No interactive window is available; run \`${headedLoginCommand}\` to complete login, captcha, or human verification visibly.`;
2238
2237
  const startedAt = now();
2239
2238
  stderr(hasInteractiveWindow
2240
- ? "login: waiting for a logged-in ChatGPT tab (finish login in the dedicated Chrome browser; Ctrl+C stops waiting)..."
2241
- : `login: waiting for a logged-in ChatGPT tab (no interactive window; use \`${headedLoginCommand}\` if login or verification is required; Ctrl+C stops waiting)...`);
2239
+ ? "login: waiting for ChatGPT readiness in the dedicated Chrome browser (complete any visible step it requests; Ctrl+C stops waiting)..."
2240
+ : `login: waiting for ChatGPT readiness (no interactive window; use \`${headedLoginCommand}\` to inspect any requested browser step visibly; Ctrl+C stops waiting)...`);
2242
2241
  let lastState = "";
2242
+ let lastStatus;
2243
2243
  let openMissingTabAttempts = 0;
2244
2244
  while (now() - startedAt < timeoutMs) {
2245
2245
  const status = await statusFn({ port: options.port, timeoutMs: 1_500 });
2246
+ lastStatus = status;
2246
2247
  // A running Chrome with no chatgpt.com tab leaves the user nothing to log
2247
2248
  // into, so prodex opens one. Keep trying while the tab is still missing:
2248
2249
  // one silent attempt that fails looks exactly like no attempt, which is how
@@ -2263,18 +2264,27 @@ export async function waitForChatGptLoginReady(stderr, options, deps = {}) {
2263
2264
  await sleepFn(pollMs);
2264
2265
  continue;
2265
2266
  }
2267
+ const blocker = status.blocker;
2268
+ const blockerNextStep = blocker?.next_step ? ` Next: ${blocker.next_step}` : "";
2269
+ const needsVisibleAuth = blocker !== undefined &&
2270
+ ["login_required", "cloudflare_check", "captcha_required", "permission_required"].includes(blocker.code);
2271
+ if (!hasInteractiveWindow && needsVisibleAuth) {
2272
+ stderr(`login: blocked - ${blocker.message}${blockerNextStep}`);
2273
+ stderr(`login: NOT READY - ${blocker.code} requires visible manual handling. No interactive window is available; run \`${headedLoginCommand}\` to handle it visibly.`);
2274
+ return false;
2275
+ }
2266
2276
  const state = !status.reachable
2267
2277
  ? "login: browser starting..."
2268
2278
  : status.blocker
2269
- ? `login: blocked - ${status.blocker.message}${hasInteractiveWindow ? "" : headedLoginHint}`
2279
+ ? `login: blocked - ${status.blocker.message}${blockerNextStep}`
2270
2280
  : !status.loggedInLikely
2271
2281
  ? hasInteractiveWindow
2272
- ? "login: waiting for ChatGPT login in the dedicated Chrome browser..."
2273
- : `login: waiting for a saved ChatGPT login.${headedLoginHint}`
2282
+ ? "login: ChatGPT is reachable, but login readiness is not yet confirmed; review any visible browser prompt..."
2283
+ : `login: ChatGPT is reachable, but login readiness is not yet confirmed; inspect it visibly with \`${headedLoginCommand}\` if needed.`
2274
2284
  : !status.hasComposer
2275
2285
  ? hasInteractiveWindow
2276
- ? "login: logged in; open a chat so the prompt composer is visible..."
2277
- : `login: logged in, but no prompt composer is ready.${headedLoginHint}`
2286
+ ? "login: login looks active; open a chat so the prompt composer is visible..."
2287
+ : `login: login looks active, but no prompt composer is ready; inspect it visibly with \`${headedLoginCommand}\`.`
2278
2288
  : "";
2279
2289
  if (state === "") {
2280
2290
  stderr(`login: READY - logged-in ChatGPT tab with composer detected (${Math.round((now() - startedAt) / 1000)}s).`);
@@ -2289,9 +2299,26 @@ export async function waitForChatGptLoginReady(stderr, options, deps = {}) {
2289
2299
  break;
2290
2300
  await sleepFn(Math.min(pollMs, Math.max(1, remainingMs)));
2291
2301
  }
2292
- stderr(hasInteractiveWindow
2293
- ? `login: not ready after ${Math.round(timeoutMs / 1000)}s. Finish login in the browser, then verify with \`prodex pro browser check\`.`
2294
- : `login: not ready after ${Math.round(timeoutMs / 1000)}s. Run \`${headedLoginCommand}\` to complete login, captcha, or human verification visibly, then retry.`);
2302
+ const timeoutPrefix = `login: not ready after ${Math.round(timeoutMs / 1000)}s`;
2303
+ if (lastStatus?.blocker) {
2304
+ const nextStep = lastStatus.blocker.next_step ? ` Next: ${lastStatus.blocker.next_step}` : "";
2305
+ stderr(`${timeoutPrefix}; last blocker ${lastStatus.blocker.code}: ${lastStatus.blocker.message}${nextStep}`);
2306
+ }
2307
+ else if (!lastStatus?.reachable) {
2308
+ stderr(hasInteractiveWindow
2309
+ ? `${timeoutPrefix}; browser startup was not confirmed. Check the dedicated Chrome browser, then verify with \`prodex pro browser check\`.`
2310
+ : `${timeoutPrefix}; browser startup was not confirmed. Inspect it visibly with \`${headedLoginCommand}\`, then retry.`);
2311
+ }
2312
+ else if (lastStatus.loggedInLikely && !lastStatus.hasComposer) {
2313
+ stderr(hasInteractiveWindow
2314
+ ? `${timeoutPrefix}; login looked active, but the prompt composer was not detected. Open a normal ChatGPT chat or Project thread, then verify with \`prodex pro browser check\`.`
2315
+ : `${timeoutPrefix}; login looked active, but the prompt composer was not detected. Inspect it visibly with \`${headedLoginCommand}\`, then retry.`);
2316
+ }
2317
+ else {
2318
+ stderr(hasInteractiveWindow
2319
+ ? `${timeoutPrefix}; ChatGPT was reachable, but login readiness was not yet confirmed. Review the dedicated Chrome browser, then verify with \`prodex pro browser check\`.`
2320
+ : `${timeoutPrefix}; ChatGPT was reachable, but login readiness was not yet confirmed. Inspect it visibly with \`${headedLoginCommand}\`; complete a manual step only if requested.`);
2321
+ }
2295
2322
  return false;
2296
2323
  }
2297
2324
  export async function assertBrowserLaunchStayedAlive(opened, timeoutMs) {
@@ -3150,7 +3177,7 @@ export function assertNoOrphanConsultResults(tasksById, results) {
3150
3177
  }
3151
3178
  export function browserReadinessNextStep(input) {
3152
3179
  if (!input.loggedInLikely) {
3153
- return "Log in manually in the visible ChatGPT browser, then retry.";
3180
+ return "ChatGPT login readiness is not yet confirmed. Review the visible ChatGPT browser state, then retry.";
3154
3181
  }
3155
3182
  if (!input.hasComposer) {
3156
3183
  return "Open a normal ChatGPT chat or Project thread, select the Pro/Thinking model, and retry.";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youdie006/prodex",
3
- "version": "0.40.13",
3
+ "version": "0.40.14",
4
4
  "description": "Local receipt bus for coordinating Codex execution with ChatGPT Pro/Projects consultation.",
5
5
  "author": "youdie006",
6
6
  "license": "MIT",