@youdie006/prodex 0.18.0 → 0.19.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/README.md +1 -1
- package/dist/chatgpt-browser.js +41 -7
- package/dist/cli-pro.js +16 -2
- package/dist/cli.js +6 -2
- package/dist/schema.js +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ prodex ask --file src/auth.ts "Review this for security holes"
|
|
|
35
35
|
|
|
36
36
|
`prodex ask` is the short form of `prodex pro browser ask`; the full form and every flag work identically. In an interactive terminal, `login` keeps watching the opened window and tells you exactly which manual step is still missing (log in, clear a check, open a chat) until it reports READY. If you skip `login` and the browser is not running, an interactive `ask` recovers on its own: it launches the dedicated browser, waits for your saved session to be READY, and retries the send once (disable with `--no-auto-login`; scripts opt in with `--auto-login`). While ChatGPT thinks, `prodex` prints progress to stderr (connecting, prompt sent, elapsed seconds while generating), so a multi-minute Pro answer never looks frozen.
|
|
37
37
|
|
|
38
|
-
The answer prints to your terminal and is saved under `.bridge/` for later (`prodex pro latest` re-prints it). Add `--new-chat` to send into a fresh chat (recommended for repeated consults - long threads eventually confuse send detection). For a structured second-opinion debate between your coding agent and GPT Pro, `prodex pro debate-prompt --topic "..."` prints a ready-to-paste orchestration prompt. `prodex` drives the picker you can see and
|
|
38
|
+
The answer prints to your terminal and is saved under `.bridge/` for later (`prodex pro latest` re-prints it). Add `--new-chat` to send into a fresh chat (recommended for repeated consults - long threads eventually confuse send detection). For a structured second-opinion debate between your coding agent and GPT Pro, `prodex pro debate-prompt --topic "..."` prints a ready-to-paste orchestration prompt. `prodex` drives the picker you can see and will not send into a tab it cannot read, so leave the dedicated window on a ChatGPT tab; it sends quietly in the background without stealing focus. Prefer no window at all? See [virtual display](#no-window-at-all-virtual-display-recommended). Pin per-repo defaults once - `prodex setup --model Pro --project "your-project"` - so every ask runs Pro (20-minute timeout) inside that project instead of whatever the ChatGPT UI last had selected; list exact sidebar project names with `prodex pro browser projects`. Pass `--file` more than once to attach several files. When the thread is still generating a previous answer (common right after a timed-out Pro send), the send automatically queues behind it up to the timeout budget; tune that with `--busy-wait-ms` (0 fails fast with a `response_in_progress` blocker). See [First Pro Login](#first-pro-login) for the full flow, and the [FAQ](#faq) if a send stops.
|
|
39
39
|
|
|
40
40
|
## Core Shape
|
|
41
41
|
|
package/dist/chatgpt-browser.js
CHANGED
|
@@ -477,6 +477,23 @@ async function waitForFreshChatGptPage(page, timeoutMs) {
|
|
|
477
477
|
export function hasChatGptPromptAcceptance(previous, state) {
|
|
478
478
|
return state.userMessageCount > previous.userMessageCount || state.assistantMessageCount > previous.assistantMessageCount;
|
|
479
479
|
}
|
|
480
|
+
/**
|
|
481
|
+
* Compare the model that was ASKED for against the model that actually
|
|
482
|
+
* produced the answer (ChatGPT tags each message with data-message-model-slug).
|
|
483
|
+
* prodex used to record only the request, so a model click that silently did
|
|
484
|
+
* not take - or no model pinned at all - was invisible, and the user believed
|
|
485
|
+
* they were getting Pro reasoning when they were not.
|
|
486
|
+
*/
|
|
487
|
+
export function modelSelectionWarning(requestedModel, modelSlug) {
|
|
488
|
+
if (!requestedModel || !modelSlug)
|
|
489
|
+
return undefined;
|
|
490
|
+
const wantsPro = /\bpro\b/i.test(requestedModel);
|
|
491
|
+
if (!wantsPro)
|
|
492
|
+
return undefined;
|
|
493
|
+
if (/pro/i.test(modelSlug))
|
|
494
|
+
return undefined;
|
|
495
|
+
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.`;
|
|
496
|
+
}
|
|
480
497
|
export function chatGptBusyBlocker(generating) {
|
|
481
498
|
if (!generating)
|
|
482
499
|
return undefined;
|
|
@@ -1570,6 +1587,7 @@ export async function recoverChatGptAnswerFromThread(options) {
|
|
|
1570
1587
|
title: state.title,
|
|
1571
1588
|
answer: state.answer.trim(),
|
|
1572
1589
|
modelHints: state.modelHints,
|
|
1590
|
+
...(state.modelSlug ? { modelSlug: state.modelSlug } : {}),
|
|
1573
1591
|
warnings: []
|
|
1574
1592
|
};
|
|
1575
1593
|
}
|
|
@@ -1842,7 +1860,8 @@ export async function sendChatGptPrompt(options) {
|
|
|
1842
1860
|
title: completed.title,
|
|
1843
1861
|
answer: completed.answer.trim(),
|
|
1844
1862
|
modelHints: completed.modelHints,
|
|
1845
|
-
|
|
1863
|
+
...(completed.modelSlug ? { modelSlug: completed.modelSlug } : {}),
|
|
1864
|
+
warnings: [...sendWarnings, modelSelectionWarning(options.model, completed.modelSlug)].filter((warning) => Boolean(warning))
|
|
1846
1865
|
};
|
|
1847
1866
|
}
|
|
1848
1867
|
// Timed out while the answer was still streaming: salvage the partial text
|
|
@@ -1855,14 +1874,19 @@ export async function sendChatGptPrompt(options) {
|
|
|
1855
1874
|
title: completed.title,
|
|
1856
1875
|
answer: completed.answer.trim(),
|
|
1857
1876
|
modelHints: completed.modelHints,
|
|
1877
|
+
...(completed.modelSlug ? { modelSlug: completed.modelSlug } : {}),
|
|
1858
1878
|
warnings: [
|
|
1859
1879
|
...sendWarnings,
|
|
1880
|
+
...(modelSelectionWarning(options.model, completed.modelSlug) ? [modelSelectionWarning(options.model, completed.modelSlug)] : []),
|
|
1860
1881
|
`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.`
|
|
1861
1882
|
]
|
|
1862
1883
|
};
|
|
1863
1884
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1885
|
+
// Carry the thread the prompt landed in: ChatGPT usually finishes the answer
|
|
1886
|
+
// after prodex gives up, and `pro browser recover --target-url` exists to
|
|
1887
|
+
// fetch it - but only if the caller knows which thread to point at.
|
|
1888
|
+
throw Object.assign(new Error(`Timed out after ${formatDurationMs(timeoutMs)} (${timeoutMs}ms) waiting for ChatGPT to respond. ` +
|
|
1889
|
+
"Pro reasoning can run many minutes. Raise --timeout-ms and retry."), completed?.url ? { thread: completed.url } : {});
|
|
1866
1890
|
}
|
|
1867
1891
|
export function modelMenuOptionsExpression() {
|
|
1868
1892
|
return `(() => {
|
|
@@ -2409,10 +2433,17 @@ export function answerExpression() {
|
|
|
2409
2433
|
return parts.join(String.fromCharCode(10));
|
|
2410
2434
|
};
|
|
2411
2435
|
const lines = text.split(String.fromCharCode(10)).map((line) => line.trim()).filter(Boolean);
|
|
2412
|
-
const messages = [...document.querySelectorAll('[data-message-author-role]')].map((node) =>
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2436
|
+
const messages = [...document.querySelectorAll('[data-message-author-role]')].map((node) => {
|
|
2437
|
+
// ChatGPT tags each message with the model that produced it, on the
|
|
2438
|
+
// message node or an ancestor depending on the build. This is the only
|
|
2439
|
+
// ground truth for "did the Pro selection actually take".
|
|
2440
|
+
let modelSlug = node.getAttribute('data-message-model-slug') || undefined;
|
|
2441
|
+
if (!modelSlug && typeof node.closest === "function") {
|
|
2442
|
+
const tagged = node.closest('[data-message-model-slug]');
|
|
2443
|
+
if (tagged) modelSlug = tagged.getAttribute('data-message-model-slug') || undefined;
|
|
2444
|
+
}
|
|
2445
|
+
return { role: node.getAttribute('data-message-author-role'), text: node.innerText || "", modelSlug };
|
|
2446
|
+
});
|
|
2416
2447
|
const assistantMessages = messages.filter((message) => message.role === "assistant");
|
|
2417
2448
|
const userMessages = messages.filter((message) => message.role === "user");
|
|
2418
2449
|
const assistant = assistantMessages.at(-1);
|
|
@@ -2436,6 +2467,9 @@ export function answerExpression() {
|
|
|
2436
2467
|
generating: placeholder || Boolean(document.querySelector(${streamingSelector})) || buttons.some((label) => generatingControlPattern.test(label)),
|
|
2437
2468
|
assistantMessageCount: assistantMessages.length,
|
|
2438
2469
|
userMessageCount: userMessages.length,
|
|
2470
|
+
// ChatGPT tags each assistant message with the model that produced it -
|
|
2471
|
+
// the only ground truth for "did the Pro selection actually take".
|
|
2472
|
+
modelSlug: assistant ? assistant.modelSlug : undefined,
|
|
2439
2473
|
modelHints: lines.filter((line) => /GPT|Pro|Thinking|ChatGPT|Extra High|Auto/i.test(line)).slice(0, 30)
|
|
2440
2474
|
};
|
|
2441
2475
|
})()`;
|
package/dist/cli-pro.js
CHANGED
|
@@ -962,7 +962,9 @@ export async function runAskProCommand(rest, io) {
|
|
|
962
962
|
// after the 2026-07 update reset that to Medium, consults meant for Pro
|
|
963
963
|
// quietly ran on a mid-tier model. Warn loudly and record it.
|
|
964
964
|
if (!selectionModel && !selectionProMode && !selectionEffort) {
|
|
965
|
-
persistenceWarnings.push("model_selection_warning: no model/effort was selected for this send (no per-ask flag, no saved default), so it used whatever the ChatGPT UI last had selected
|
|
965
|
+
persistenceWarnings.push("model_selection_warning: no model/effort was selected for this send (no per-ask flag, no saved default), so it used whatever the ChatGPT UI last had selected" +
|
|
966
|
+
(consult.modelSlug ? ` - it answered as "${consult.modelSlug}"` : "") +
|
|
967
|
+
". Pin one with `prodex setup --model Pro` or pass --model/--effort.");
|
|
966
968
|
}
|
|
967
969
|
// In-project threads carry the project slug in their URL
|
|
968
970
|
// (/g/g-p-<project>/c/<id>); a bare /c/<id> after requesting a project
|
|
@@ -976,6 +978,8 @@ export async function runAskProCommand(rest, io) {
|
|
|
976
978
|
// would otherwise treat a cut-off answer as complete.
|
|
977
979
|
for (const warning of persistenceWarnings)
|
|
978
980
|
io.stderr(warning);
|
|
981
|
+
if (consult.modelSlug)
|
|
982
|
+
io.stderr(`model_used: ${consult.modelSlug}`);
|
|
979
983
|
let answerArtifactPath;
|
|
980
984
|
const answerArtifactBytes = Buffer.byteLength(answerArtifactText, "utf8");
|
|
981
985
|
if (answerArtifactBytes > MAX_FETCHABLE_RESULT_ARTIFACT_BYTES) {
|
|
@@ -1003,6 +1007,9 @@ export async function runAskProCommand(rest, io) {
|
|
|
1003
1007
|
...(answerArtifactPath ? { artifact_path: answerArtifactPath } : {}),
|
|
1004
1008
|
thread: consult.url,
|
|
1005
1009
|
...(Object.keys(selectionMetadata).length > 0 ? { selection: selectionMetadata } : {}),
|
|
1010
|
+
// What actually answered, straight from ChatGPT's own tag - the
|
|
1011
|
+
// receipt used to record only what prodex asked for.
|
|
1012
|
+
...(consult.modelSlug ? { model_used: consult.modelSlug } : {}),
|
|
1006
1013
|
warnings: persistenceWarnings
|
|
1007
1014
|
}
|
|
1008
1015
|
});
|
|
@@ -1292,6 +1299,9 @@ export function browserSendBlockerFromError(error) {
|
|
|
1292
1299
|
}
|
|
1293
1300
|
// Match the raw ms whether the message uses the old "after 90000ms" form or
|
|
1294
1301
|
// the newer human-readable "after 20 min (1200000ms)" form.
|
|
1302
|
+
const thread = typeof error === "object" && error !== null && "thread" in error && typeof error.thread === "string"
|
|
1303
|
+
? (error.thread)
|
|
1304
|
+
: undefined;
|
|
1295
1305
|
const timedOut = message.match(/Timed out after [\s\S]*?(\d+)\s*ms/);
|
|
1296
1306
|
if (timedOut) {
|
|
1297
1307
|
// Suggest a concrete doubled budget so the user can paste a rerun command
|
|
@@ -1302,7 +1312,11 @@ export function browserSendBlockerFromError(error) {
|
|
|
1302
1312
|
code: "send_timeout",
|
|
1303
1313
|
message,
|
|
1304
1314
|
retryable: true,
|
|
1305
|
-
|
|
1315
|
+
...(thread ? { thread } : {}),
|
|
1316
|
+
next_step: `Rerun with a bigger budget (${formatDurationMs(suggestedMs)}): \`prodex pro browser ask --timeout-ms ${suggestedMs} "<same prompt>"\`.` +
|
|
1317
|
+
(thread
|
|
1318
|
+
? ` ChatGPT often finishes after prodex gives up - fetch that answer instead of re-asking: \`prodex pro browser recover --target-url ${thread}\`.`
|
|
1319
|
+
: "")
|
|
1306
1320
|
};
|
|
1307
1321
|
}
|
|
1308
1322
|
// A CDP command timeout means the page's renderer stalled, which in the
|
package/dist/cli.js
CHANGED
|
@@ -303,9 +303,13 @@ repo: ${cwd}
|
|
|
303
303
|
${cli} pro browser login${sourceCliOption} # opens visible browser
|
|
304
304
|
${cli} pro browser login --dry-run${sourceCliOption} # preview, no browser opens
|
|
305
305
|
In an interactive terminal, login waits and narrates until your ChatGPT session is READY.
|
|
306
|
+
Sign in once, then keep the browser off your screen for good:
|
|
307
|
+
${cli} pro browser login --virtual-display${sourceCliOption} # no window anywhere (needs Xvfb: sudo apt install -y xvfb x11-xkb-utils xauth)
|
|
308
|
+
${cli} pro browser login --minimized${sourceCliOption} # no install; keeps the window minimized
|
|
309
|
+
Not --headless: Cloudflare rejects headless browsers, so ChatGPT never loads in one.
|
|
306
310
|
Pin per-repo defaults first - otherwise sends silently use whatever the ChatGPT UI last had selected:
|
|
307
311
|
${cli} pro browser projects${sourceCliOption} # read-only: exact sidebar project names
|
|
308
|
-
${cli} setup --cwd ${quotedCwd} --model Pro --project "your-project" # every ask: Pro (
|
|
312
|
+
${cli} setup --cwd ${quotedCwd} --model Pro --project "your-project" # every ask: Pro (20-minute timeout) inside that project
|
|
309
313
|
cd ${quotedCwd}
|
|
310
314
|
${cli} ask --new-chat "Review this repo"${sourceCliOption} # short form of pro browser ask
|
|
311
315
|
${proAskCommand} # dry-run/manual preview
|
|
@@ -314,7 +318,7 @@ repo: ${cwd}
|
|
|
314
318
|
${cli} pro browser help${sourceCliOption}
|
|
315
319
|
${cli} pro browser check${sourceCliOption} --cwd ${quotedCwd}
|
|
316
320
|
${cli} pro browser smoke${sourceCliOption} --cwd ${quotedCwd}
|
|
317
|
-
Sharing the browser with other agents?
|
|
321
|
+
Sharing the browser with other agents? Sends queue behind an in-flight response automatically; pass --busy-wait-ms 0 to fail fast instead.
|
|
318
322
|
|
|
319
323
|
2. Let coding agents consult ChatGPT (stdio MCP: Claude, Codex, Cursor, ...):
|
|
320
324
|
${cli} claude config --cwd ${quotedCwd}${sourceCliOption}
|
package/dist/schema.js
CHANGED
|
@@ -35,7 +35,11 @@ export const BlockerSchema = z.object({
|
|
|
35
35
|
code: z.string(),
|
|
36
36
|
message: z.string(),
|
|
37
37
|
retryable: z.boolean().default(false),
|
|
38
|
-
next_step: z.string().optional()
|
|
38
|
+
next_step: z.string().optional(),
|
|
39
|
+
// The ChatGPT thread the prompt landed in, recorded on send failures so
|
|
40
|
+
// `pro browser recover --target-url` has something to point at: ChatGPT
|
|
41
|
+
// usually finishes the answer after prodex has given up waiting.
|
|
42
|
+
thread: z.string().optional()
|
|
39
43
|
});
|
|
40
44
|
export const TaskSchema = z.object({
|
|
41
45
|
schema_version: z.literal(SCHEMA_VERSION),
|