@youdie006/prodex 0.39.4 → 0.39.5

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.
@@ -613,14 +613,32 @@ export function hasChatGptPromptAcceptance(previous, state) {
613
613
  * they were getting Pro reasoning when they were not.
614
614
  */
615
615
  export function modelSelectionWarning(requestedModel, modelSlug) {
616
- if (!requestedModel || !modelSlug)
617
- return undefined;
618
- const wantsPro = /\bpro\b/i.test(requestedModel);
619
- if (!wantsPro)
616
+ return selectionMismatchWarning({
617
+ ...(requestedModel !== undefined ? { model: requestedModel } : {}),
618
+ ...(modelSlug !== undefined ? { modelSlug } : {})
619
+ });
620
+ }
621
+ /** Whether a requested label names ChatGPT's Pro step, in either locale. */
622
+ export function namesPro(label) {
623
+ if (!label)
624
+ return false;
625
+ return /\bpro\b/i.test(label) || label.includes("프로");
626
+ }
627
+ /**
628
+ * Compare what was asked for against the model that actually answered.
629
+ *
630
+ * Pro can be asked for on either axis - `--model Pro` or `--effort Pro` - and
631
+ * this only ever looked at the model, so an effort-shaped Pro request answered
632
+ * by a lesser model passed without a word. Measured: a consult asking for Pro
633
+ * came back from gpt-5-6-thinking and was recorded as a clean success.
634
+ */
635
+ export function selectionMismatchWarning(input) {
636
+ const asked = namesPro(input.model) ? input.model : namesPro(input.effort) ? input.effort : undefined;
637
+ if (!asked || !input.modelSlug)
620
638
  return undefined;
621
- if (/pro/i.test(modelSlug))
639
+ if (/pro/i.test(input.modelSlug))
622
640
  return undefined;
623
- return `model_mismatch: you asked for ${requestedModel}, but the answer came from "${modelSlug}". Check the model picker in the browser; the selection did not take.`;
641
+ return `model_mismatch: you asked for ${asked}, but the answer came from "${input.modelSlug}". Check the model picker in the browser; the selection did not take.`;
624
642
  }
625
643
  /**
626
644
  * The panel ChatGPT raises to ask which of two answers you prefer.
@@ -2248,6 +2266,14 @@ async function selectModelReasoning(cdp, options, selectionWarnings = []) {
2248
2266
  const noSuchStep = /has no "[^"]*" step/.test(message);
2249
2267
  if (!noSuchStep)
2250
2268
  throw error;
2269
+ // Asking for a step is a contract. Sending at whatever the slider
2270
+ // happened to be on is not a lesser version of that contract, it is
2271
+ // a different answer that the caller cannot use: measured, a Pro
2272
+ // request came back from gpt-5-6-thinking after minutes of waiting
2273
+ // and was thrown away. A caller that would rather have any answer
2274
+ // says so with --allow-model-fallback.
2275
+ if (!options.allowSelectionFallback)
2276
+ throw error;
2251
2277
  const offered = /It showed: (.*)$/.exec(message)?.[1]?.split(" / ").map((part) => part.trim()) ?? [];
2252
2278
  selectionWarnings.push(stepSelectionUnavailableWarning(plan.sliderLabel, offered));
2253
2279
  }
@@ -3108,7 +3134,7 @@ export async function sendChatGptPrompt(options) {
3108
3134
  answer: transcript.answer,
3109
3135
  modelHints: finalState?.modelHints ?? [],
3110
3136
  ...(transcript.modelSlug ? { modelSlug: transcript.modelSlug } : finalState?.modelSlug ? { modelSlug: finalState.modelSlug } : {}),
3111
- warnings: withDialogNote([...sendWarnings, modelSelectionWarning(options.model, transcript.modelSlug || finalState?.modelSlug)]).filter((warning) => Boolean(warning))
3137
+ warnings: withDialogNote([...sendWarnings, selectionMismatchWarning({ ...(options.model !== undefined ? { model: options.model } : {}), ...(options.effort !== undefined ? { effort: options.effort } : {}), ...((transcript.modelSlug || finalState?.modelSlug) ? { modelSlug: (transcript.modelSlug || finalState?.modelSlug) } : {}) })]).filter((warning) => Boolean(warning))
3112
3138
  };
3113
3139
  };
3114
3140
  // Deep research never reaches the DOM answer wait below: the report is
@@ -3256,7 +3282,7 @@ export async function sendChatGptPrompt(options) {
3256
3282
  answer: completed.answer.trim(),
3257
3283
  modelHints: completed.modelHints,
3258
3284
  ...(completed.modelSlug ? { modelSlug: completed.modelSlug } : {}),
3259
- warnings: withDialogNote([...sendWarnings, modelSelectionWarning(options.model, completed.modelSlug)]).filter((warning) => Boolean(warning))
3285
+ warnings: withDialogNote([...sendWarnings, selectionMismatchWarning({ ...(options.model !== undefined ? { model: options.model } : {}), ...(options.effort !== undefined ? { effort: options.effort } : {}), ...(completed.modelSlug !== undefined ? { modelSlug: completed.modelSlug } : {}) })]).filter((warning) => Boolean(warning))
3260
3286
  };
3261
3287
  }
3262
3288
  // Timed out while the answer was still streaming: salvage the partial text
@@ -3272,7 +3298,7 @@ export async function sendChatGptPrompt(options) {
3272
3298
  ...(completed.modelSlug ? { modelSlug: completed.modelSlug } : {}),
3273
3299
  warnings: withDialogNote([
3274
3300
  ...sendWarnings,
3275
- ...(modelSelectionWarning(options.model, completed.modelSlug) ? [modelSelectionWarning(options.model, completed.modelSlug)] : []),
3301
+ ...(selectionMismatchWarning({ ...(options.model !== undefined ? { model: options.model } : {}), ...(options.effort !== undefined ? { effort: options.effort } : {}), ...(completed.modelSlug !== undefined ? { modelSlug: completed.modelSlug } : {}) }) ? [selectionMismatchWarning({ ...(options.model !== undefined ? { model: options.model } : {}), ...(options.effort !== undefined ? { effort: options.effort } : {}), ...(completed.modelSlug !== undefined ? { modelSlug: completed.modelSlug } : {}) })] : []),
3276
3302
  `answer_incomplete: ChatGPT was still generating after ${formatDurationMs(timeoutMs)} (${timeoutMs}ms), so the answer below may be truncated. Raise --timeout-ms and retry for the full response.`
3277
3303
  ])
3278
3304
  };
package/dist/cli-args.js CHANGED
@@ -259,7 +259,11 @@ export const ASK_PRO_BOOLEAN_FLAGS = new Set([
259
259
  "--auto-login",
260
260
  "--no-auto-login",
261
261
  // Send outside any project for once, overriding a pinned default.
262
- "--no-project"
262
+ "--no-project",
263
+ // Send even when the requested model/effort could not be applied, rather
264
+ // than stopping. Off by default: an answer from a step nobody asked for is
265
+ // usually thrown away.
266
+ "--allow-model-fallback"
263
267
  ]);
264
268
  export const ASK_PRO_SELECTION_VALUE_FLAGS = ["--project", "--project-new", "--model", "--pro-mode", "--effort"];
265
269
  export const ASK_PRO_VALUE_FLAGS = new Set([
package/dist/cli-help.js CHANGED
@@ -26,7 +26,7 @@ Ask / consult commands:
26
26
  prodex pro browser models [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--timeout-ms 15000] # read-only list of model menu options
27
27
  prodex pro browser projects [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--timeout-ms 15000] # read-only list of sidebar project names (for --project)
28
28
  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] # recover a finished answer from a thread whose send timed out
29
- prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--temporary] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] [--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"|Max|Ultra|Pro] [--project "name" | --project-new "name"] "prompt" # explicit visible-browser send
29
+ prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] [--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"|Max|Ultra|Pro] [--project "name" | --project-new "name"] "prompt" # explicit visible-browser send
30
30
  prodex pro latest [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--json]
31
31
  prodex pro list [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--json]
32
32
  prodex pro show <task-id|latest> [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--json]
@@ -88,6 +88,7 @@ Optional visible-browser send defaults (applied by \`pro browser ask\` when the
88
88
  --model Composer model by its exact menu label. Only Pro applies on the current picker: it is a slider step, and the model rows cannot be clicked
89
89
  --pro-mode Pro sub-mode: 기본 (standard) or 확장 (extended)
90
90
  --effort Reasoning effort: 즉시 / 중간 / 높음 / 매우 높음 / Max / Ultra / Pro. Max and Ultra are rungs of ChatGPT's Work surface and only apply when the browser is already on Work (prodex does not switch to Work for them); every other value is sent on Chat, whose top step is Pro
91
+ --allow-model-fallback Send even when the requested model/effort could not be applied. Off by default: a send that could not reach what was asked for stops instead, because an answer from a step nobody asked for is usually unusable
91
92
  --project Sidebar project to enter before sending
92
93
  Clear a saved default with --clear-model / --clear-pro-mode / --clear-effort / --clear-project.
93
94
  --pro-mode and --effort are different model axes and cannot be combined. View saved defaults with \`prodex status\`.`);
@@ -168,7 +169,7 @@ Commands:
168
169
  prodex pro browser check [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo]
169
170
  prodex pro browser smoke [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo]
170
171
  prodex pro browser models [--source-cli /absolute/path/to/dist/cli.js]
171
- prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--target-url url --confirm-target] [--new-chat] [--temporary] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] [--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"|Max|Ultra|Pro] [--project "name" | --project-new "name"] "prompt"
172
+ prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--target-url url --confirm-target] [--new-chat] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] [--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"|Max|Ultra|Pro] [--project "name" | --project-new "name"] "prompt"
172
173
  prodex pro latest [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--json]
173
174
  prodex pro list [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--json]
174
175
  prodex pro show <task-id|latest> [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--json]
@@ -255,8 +256,8 @@ export function printProBrowserHelp(stdout, sourceCli) {
255
256
  : "prodex pro browser smoke [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 90000]";
256
257
  const selectionUsage = '[--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"|Max|Ultra|Pro] [--project "name" | --project-new "name"]';
257
258
  const askUsage = sourceCli
258
- ? `${cli} pro browser ask${sourceCliOption} [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--temporary] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] ${selectionUsage} "prompt"`
259
- : `prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--temporary] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] ${selectionUsage} "prompt"`;
259
+ ? `${cli} pro browser ask${sourceCliOption} [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] ${selectionUsage} "prompt"`
260
+ : `prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] ${selectionUsage} "prompt"`;
260
261
  const modelsUsage = sourceCli
261
262
  ? `${cli} pro browser models${sourceCliOption} [--port 9333] [--timeout-ms 15000]`
262
263
  : "prodex pro browser models [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--timeout-ms 15000]";
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, formatModelMenuOption, 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, statusMeansBrowserDead } from "./chatgpt-browser.js";
5
+ import { DEFAULT_CDP_PORT, resolveCdpPort, resolveConversationToDelete, resolveProjectToDelete, chatGptVisibilityBlocker, defaultChatGptProfileDir, formatDurationMs, getChatGptBrowserStatus, formatModelMenuOption, 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, statusMeansBrowserDead, namesPro } 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";
@@ -1110,13 +1110,22 @@ export async function runAskProCommand(rest, io) {
1110
1110
  };
1111
1111
  const browserPort = hasSendMode ? resolveCdpPort(readPortFlag(parsedAskPro.optionArgs, "--port")) : undefined;
1112
1112
  const busyWaitMs = readNonNegativeIntegerFlag(parsedAskPro.optionArgs, "--busy-wait-ms");
1113
+ const allowSelectionFallback = parsedAskPro.optionArgs.includes("--allow-model-fallback");
1113
1114
  // Pro extended can legitimately think for minutes, so its default timeout is
1114
1115
  // higher; an explicit --timeout-ms always wins.
1115
1116
  // Pro reasoning routinely runs for many minutes (a real consult measured
1116
1117
  // ~13 minutes). The elevated default used to be keyed to the removed
1117
1118
  // --pro-mode, so --model Pro sends fell back to 90s and chronically timed
1118
1119
  // out. Any effective Pro selection now defaults to 15 minutes.
1119
- const effectiveProSelection = selectionProMode !== undefined || (selectionModel !== undefined && /pro/i.test(selectionModel));
1120
+ // Pro can be asked for on either axis. Keying the raised budget to the
1121
+ // model alone gave `--effort Pro` five minutes for reasoning this file's
1122
+ // own comment calls 6-20 minutes, which manufactured send_timeout
1123
+ // blockers - 55 of the 267 blockers measured on one machine.
1124
+ const effectiveProSelection = isProSelection({
1125
+ ...(selectionModel !== undefined ? { model: selectionModel } : {}),
1126
+ ...(selectionProMode !== undefined ? { proMode: selectionProMode } : {}),
1127
+ ...(selectionEffort !== undefined ? { effort: selectionEffort } : {})
1128
+ });
1120
1129
  // Pro reasoning routinely runs 6-20 minutes; 15 min was still cutting long
1121
1130
  // answers off (field report), so a Pro selection defaults to 20 minutes.
1122
1131
  // With NO model selection at all (no flag, no saved default) the UI's
@@ -1202,6 +1211,7 @@ export async function runAskProCommand(rest, io) {
1202
1211
  model: selectionModel,
1203
1212
  proMode: selectionProMode,
1204
1213
  effort: selectionEffort,
1214
+ ...(allowSelectionFallback ? { allowSelectionFallback: true } : {}),
1205
1215
  onProgress: createBrowserSendProgressPrinter(io.stderr)
1206
1216
  }));
1207
1217
  // One-command recovery: interactive terminals (or explicit --auto-login)
@@ -1303,6 +1313,16 @@ export async function runAskProCommand(rest, io) {
1303
1313
  io.stderr(warning);
1304
1314
  if (consult.modelSlug)
1305
1315
  io.stderr(`model_used: ${consult.modelSlug}`);
1316
+ // "Can I count this as a Pro review?" - answered here rather than left
1317
+ // for whoever reads the receipt to work out from two other fields.
1318
+ const proVerified = proSelectionVerified({
1319
+ ...(selectionModel !== undefined ? { model: selectionModel } : {}),
1320
+ ...(selectionProMode !== undefined ? { proMode: selectionProMode } : {}),
1321
+ ...(selectionEffort !== undefined ? { effort: selectionEffort } : {}),
1322
+ ...(consult.modelSlug !== undefined ? { modelSlug: consult.modelSlug } : {})
1323
+ });
1324
+ if (proVerified !== undefined)
1325
+ io.stderr(`pro_verified: ${proVerified ? "yes" : "no"}`);
1306
1326
  let answerArtifactPath;
1307
1327
  const answerArtifactBytes = Buffer.byteLength(answerArtifactText, "utf8");
1308
1328
  if (answerArtifactBytes > MAX_FETCHABLE_RESULT_ARTIFACT_BYTES) {
@@ -1333,6 +1353,7 @@ export async function runAskProCommand(rest, io) {
1333
1353
  // What actually answered, straight from ChatGPT's own tag - the
1334
1354
  // receipt used to record only what prodex asked for.
1335
1355
  ...(consult.modelSlug ? { model_used: consult.modelSlug } : {}),
1356
+ ...(proVerified !== undefined ? { pro_verified: proVerified } : {}),
1336
1357
  warnings: persistenceWarnings
1337
1358
  }
1338
1359
  });
@@ -1498,6 +1519,7 @@ export async function performBrowserConsultForMcp(cwd, input, onProgress) {
1498
1519
  ...(input.attach ?? []).flatMap((file) => ["--attach", file]),
1499
1520
  ...(input.tools ?? []).flatMap((tool) => ["--tool", tool]),
1500
1521
  ...(input.new_chat ? ["--new-chat"] : []),
1522
+ ...(input.allow_model_fallback ? ["--allow-model-fallback"] : []),
1501
1523
  "--",
1502
1524
  input.prompt
1503
1525
  ];
@@ -1705,6 +1727,29 @@ export async function assertBrowserLaunchStayedAlive(opened, timeoutMs) {
1705
1727
  }
1706
1728
  throw new Error(`Chrome/Chromium did not expose a reachable DevTools endpoint after launch. Check the visible browser environment, profile lock, display access, or PRODEX_CHROME, then retry.`);
1707
1729
  }
1730
+ /** Whether a send asked for ChatGPT's Pro step, on either axis that can name it. */
1731
+ export function isProSelection(selection) {
1732
+ if (selection.proMode !== undefined)
1733
+ return true;
1734
+ return namesPro(selection.model) || namesPro(selection.effort);
1735
+ }
1736
+ /**
1737
+ * Whether a Pro request was actually answered by Pro, per ChatGPT's own tag on
1738
+ * the message. Undefined when Pro was not asked for, or when no tag came back
1739
+ * to check against - there is nothing to certify in either case.
1740
+ *
1741
+ * The receipt already carried the request and the answering model separately,
1742
+ * leaving the comparison to whoever read it later. The question a caller has
1743
+ * is "can I count this as a Pro review?", and a consult that asked for Pro and
1744
+ * was answered by gpt-5-6-thinking was recorded as a clean success.
1745
+ */
1746
+ export function proSelectionVerified(selection) {
1747
+ if (!isProSelection(selection))
1748
+ return undefined;
1749
+ if (!selection.modelSlug)
1750
+ return undefined;
1751
+ return /pro/i.test(selection.modelSlug);
1752
+ }
1708
1753
  export function browserSendBlockerFromError(error) {
1709
1754
  const blocker = typeof error === "object" && error !== null && "blocker" in error ? error.blocker : undefined;
1710
1755
  if (typeof blocker === "object" &&
@@ -1770,6 +1815,17 @@ export function browserSendBlockerFromError(error) {
1770
1815
  next_step: "Another prodex send holds the browser. Wait for it to finish and retry, or pass a longer --timeout-ms, which is also the queue budget."
1771
1816
  };
1772
1817
  }
1818
+ // The picker could not provide the step that was asked for. Retrying asks
1819
+ // the same picker the same question, so this is not retryable; the caller
1820
+ // either picks a step it offers or opts into whatever the slider is on.
1821
+ if (/has no "[^"]*" step/.test(message)) {
1822
+ return {
1823
+ code: "selection_not_applied",
1824
+ message,
1825
+ retryable: false,
1826
+ next_step: "ChatGPT's picker could not provide the model or effort that was asked for, so nothing was sent. Run `prodex pro browser models` to see the steps this account offers and ask for one of those, or pass --allow-model-fallback to send at whatever the picker is currently set to."
1827
+ };
1828
+ }
1773
1829
  // The picker's slider took focus before its key handler was attached and
1774
1830
  // ignored every press. Retrying is the cure; nothing in the browser is
1775
1831
  // broken, so the generic "resolve it manually" pointed at nothing.
package/dist/mcp.js CHANGED
@@ -187,7 +187,11 @@ export function createServer(cwd = process.cwd(), options = {}) {
187
187
  new_chat: z
188
188
  .boolean()
189
189
  .optional()
190
- .describe("Start a fresh thread. Omit to continue the current thread (preferred for follow-ups).")
190
+ .describe("Start a fresh thread. Omit to continue the current thread (preferred for follow-ups)."),
191
+ allow_model_fallback: z
192
+ .boolean()
193
+ .optional()
194
+ .describe("Send even when the requested model or effort could not be applied. Off by default, because an answer from a step nobody asked for is usually unusable: a consult asking for Pro that comes back from a lesser model cannot be cited as a Pro review. Pass true only when any answer beats no answer.")
191
195
  }
192
196
  }, async (input, extra) => {
193
197
  // Bridge send progress to MCP progress notifications, but only when
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youdie006/prodex",
3
- "version": "0.39.4",
3
+ "version": "0.39.5",
4
4
  "description": "Local receipt bus for coordinating Codex execution with ChatGPT Pro/Projects consultation.",
5
5
  "author": "youdie006",
6
6
  "license": "MIT",