@youdie006/prodex 0.16.32 → 0.17.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 +11 -0
- package/dist/browser-send-lock.js +1 -1
- package/dist/chatgpt-browser.js +64 -7
- package/dist/cli-help.js +7 -7
- package/dist/cli-pro.js +143 -28
- package/dist/cli.js +4 -1
- package/dist/mcp.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -256,6 +256,17 @@ prodex setup --interactive # asks model / Pro sub-mode or effort / project
|
|
|
256
256
|
|
|
257
257
|
The saved default above lives in the repo's `.bridge/config.local.json`, so it only applies when `prodex` runs from that repo. A coding agent often starts the MCP as `prodex mcp` with no `--cwd` (it reads whatever directory the agent launched in), so a per-repo default is missed and consults land in the general chat. For a default that applies from **any** directory, set environment variables instead — `PRODEX_DEFAULT_PROJECT` and `PRODEX_DEFAULT_MODEL` (also `PRODEX_DEFAULT_PRO_MODE`, `PRODEX_DEFAULT_EFFORT`) — in the agent's MCP `env` block or your shell. Use your own project name (list them with `prodex pro browser projects`); with no project set, consults simply go to the general chat. A per-repo config still wins field-by-field over the env fallback.
|
|
258
258
|
|
|
259
|
+
### No window: headless mode
|
|
260
|
+
|
|
261
|
+
`prodex pro browser login --headless` (or `PRODEX_HEADLESS=1`, which also covers the MCP server and its auto-recovery) runs the dedicated browser with no visible window. Two constraints are real, not cosmetic:
|
|
262
|
+
|
|
263
|
+
- **Sign in headed first.** Nobody can log in to a window that does not exist, so headless reuses a profile you already signed into. The headless login verifies the saved session and tells you to run the headed login once if it is not there.
|
|
264
|
+
- **One mode at a time.** A single Chrome profile cannot serve a headed and a headless instance simultaneously; close the running one before switching (prodex refuses the switch instead of silently reusing the wrong mode).
|
|
265
|
+
|
|
266
|
+
Cloudflare treats a fresh headless profile as suspicious ("Just a moment..." interstitial). A profile with an established logged-in session normally passes; if yours gets challenged, run headed for that session — `prodex pro browser check` reports it as a blocker rather than hanging.
|
|
267
|
+
|
|
268
|
+
If a consult finds the browser closed, prodex now relaunches it in the same mode you last used and retries once — including from the MCP server, which has no terminal to prompt in. `PRODEX_NO_AUTO_LOGIN=1` turns that off.
|
|
269
|
+
|
|
259
270
|
Whatever selection is applied is recorded on the consult receipt (`metadata.selection`); receipt display output redacts the project name, keeping only the model axes visible. `prodex` only clicks the picker you can see; it never selects a model, effort, or project silently outside the visible browser.
|
|
260
271
|
|
|
261
272
|
For a source checkout, keep the explicit send and inspection commands source-aware too:
|
|
@@ -115,7 +115,7 @@ export async function withBrowserSendLock(waitMs, onWait, fn) {
|
|
|
115
115
|
continue;
|
|
116
116
|
}
|
|
117
117
|
if (Date.now() >= deadline) {
|
|
118
|
-
throw new Error(`Another prodex browser send is in progress (pid ${holder.pid}).
|
|
118
|
+
throw new Error(`Another prodex browser send is in progress (pid ${holder.pid}) and did not finish within the wait budget. Retry once it finishes, or raise --timeout-ms (which is also the queue budget).`);
|
|
119
119
|
}
|
|
120
120
|
if (!waited) {
|
|
121
121
|
waited = true;
|
package/dist/chatgpt-browser.js
CHANGED
|
@@ -167,13 +167,19 @@ export async function readLastBrowserLoginLaunch() {
|
|
|
167
167
|
const parsed = JSON.parse(await readFile(lastBrowserLoginPath(), "utf8"));
|
|
168
168
|
return {
|
|
169
169
|
...(typeof parsed.profile_dir === "string" && parsed.profile_dir.length > 0 ? { profile_dir: parsed.profile_dir } : {}),
|
|
170
|
-
...(typeof parsed.port === "number" && Number.isInteger(parsed.port) ? { port: parsed.port } : {})
|
|
170
|
+
...(typeof parsed.port === "number" && Number.isInteger(parsed.port) ? { port: parsed.port } : {}),
|
|
171
|
+
...(typeof parsed.headless === "boolean" ? { headless: parsed.headless } : {})
|
|
171
172
|
};
|
|
172
173
|
}
|
|
173
174
|
catch {
|
|
174
175
|
return undefined;
|
|
175
176
|
}
|
|
176
177
|
}
|
|
178
|
+
// Headless Chrome defaults to an 800x600 viewport, at which ChatGPT collapses
|
|
179
|
+
// the sidebar - and the sidebar carries the logged-in signals that
|
|
180
|
+
// inferLoggedInLikely reads ("New chat"/"Projects"). Pin a desktop width so a
|
|
181
|
+
// headless session is not misread as logged out.
|
|
182
|
+
const HEADLESS_WINDOW_SIZE = "1440,900";
|
|
177
183
|
export function buildChromeLaunchArgs(options) {
|
|
178
184
|
return [
|
|
179
185
|
"--remote-debugging-address=127.0.0.1",
|
|
@@ -181,10 +187,21 @@ export function buildChromeLaunchArgs(options) {
|
|
|
181
187
|
`--user-data-dir=${options.profileDir}`,
|
|
182
188
|
"--no-first-run",
|
|
183
189
|
"--no-default-browser-check",
|
|
184
|
-
"--new-window",
|
|
190
|
+
...(options.headless ? ["--headless=new", `--window-size=${HEADLESS_WINDOW_SIZE}`] : ["--new-window"]),
|
|
185
191
|
options.url
|
|
186
192
|
];
|
|
187
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Headless is opt-in: an explicit option wins, otherwise PRODEX_HEADLESS
|
|
196
|
+
* (1/true/yes) decides. The env var is the practical switch because the MCP
|
|
197
|
+
* server and its auto-recovery launch the browser with no CLI flags.
|
|
198
|
+
*/
|
|
199
|
+
export function resolveHeadlessPreference(explicit, env = process.env) {
|
|
200
|
+
if (typeof explicit === "boolean")
|
|
201
|
+
return explicit;
|
|
202
|
+
const raw = (env.PRODEX_HEADLESS ?? "").trim().toLowerCase();
|
|
203
|
+
return raw === "1" || raw === "true" || raw === "yes";
|
|
204
|
+
}
|
|
188
205
|
export function inferLoggedInLikely(text, visibleButtonLabels = []) {
|
|
189
206
|
// Only sign-up prompts and explicit login/sign-up buttons count as logged-out signals. Bare
|
|
190
207
|
// "Log in"/"로그인" substrings appear in the menus and footers of a logged-in page, so matching
|
|
@@ -638,10 +655,12 @@ export function openChatGptBrowser(options = {}) {
|
|
|
638
655
|
const command = resolveChromeCommand();
|
|
639
656
|
const port = resolveCdpPort(options.port);
|
|
640
657
|
const profileDir = options.profileDir ?? defaultChatGptProfileDir();
|
|
658
|
+
const headless = resolveHeadlessPreference(options.headless);
|
|
641
659
|
const args = buildChromeLaunchArgs({
|
|
642
660
|
port,
|
|
643
661
|
profileDir,
|
|
644
|
-
url: options.url ?? "https://chatgpt.com/"
|
|
662
|
+
url: options.url ?? "https://chatgpt.com/",
|
|
663
|
+
...(headless ? { headless } : {})
|
|
645
664
|
});
|
|
646
665
|
const child = spawn(command, args, { detached: true, stdio: "ignore", env: browserLaunchEnv() });
|
|
647
666
|
let earlyExit;
|
|
@@ -709,7 +728,14 @@ export async function getChatGptBrowserStatus(options = {}) {
|
|
|
709
728
|
blocker: chatGptPageMissingBlocker()
|
|
710
729
|
};
|
|
711
730
|
}
|
|
712
|
-
|
|
731
|
+
// Bound the status read by the CALLER's budget, not the 20s default CDP
|
|
732
|
+
// command timeout: a very heavy ChatGPT thread answers slowly, and
|
|
733
|
+
// `pro browser check --timeout-ms 5000` measured 65 seconds because every
|
|
734
|
+
// evaluate silently used the default. Agents read that silence as a hung
|
|
735
|
+
// bridge and start "recovering" a browser that is merely busy.
|
|
736
|
+
const state = await evaluateOnPage(page.page, statusExpression(), {
|
|
737
|
+
...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {})
|
|
738
|
+
});
|
|
713
739
|
const loggedInLikely = inferChatGptPageLoggedInLikely(state);
|
|
714
740
|
const blocker = chatGptVisibilityBlocker(state.visibilityState, state.url) ?? detectChatGptPageBlocker(state) ?? chatGptBusyBlocker(state.generating);
|
|
715
741
|
return {
|
|
@@ -1308,7 +1334,7 @@ export async function recoverChatGptAnswerFromThread(options) {
|
|
|
1308
1334
|
code: "browser_unreachable",
|
|
1309
1335
|
message: `No Chrome DevTools endpoint is reachable on 127.0.0.1:${port}.`,
|
|
1310
1336
|
retryable: true,
|
|
1311
|
-
next_step: "Run `prodex pro browser login
|
|
1337
|
+
next_step: "Run `prodex pro browser login` to reopen the dedicated window - it reuses the saved session (no manual login unless it expired) and returns immediately when run non-interactively - then retry."
|
|
1312
1338
|
});
|
|
1313
1339
|
}
|
|
1314
1340
|
const cdp = await connectCdp(page.page.webSocketDebuggerUrl);
|
|
@@ -1790,7 +1816,7 @@ async function findChatGptPage(port, timeoutMs, targetUrl) {
|
|
|
1790
1816
|
code: "browser_unreachable",
|
|
1791
1817
|
message: `No Chrome DevTools endpoint is reachable on 127.0.0.1:${port}.`,
|
|
1792
1818
|
retryable: true,
|
|
1793
|
-
next_step: "Run `prodex pro browser login
|
|
1819
|
+
next_step: "Run `prodex pro browser login` to reopen the dedicated window - it reuses the saved session (no manual login unless it expired) and returns immediately when run non-interactively - then retry.",
|
|
1794
1820
|
...(error instanceof Error ? { detail: error.message } : {})
|
|
1795
1821
|
}
|
|
1796
1822
|
};
|
|
@@ -2076,12 +2102,43 @@ async function insertComposerTextViaCdp(cdp, text) {
|
|
|
2076
2102
|
await cdp.send("Input.dispatchKeyEvent", { type: "keyUp", key: "Backspace", code: "Backspace", windowsVirtualKeyCode: 8 });
|
|
2077
2103
|
await sleep(100);
|
|
2078
2104
|
}
|
|
2079
|
-
|
|
2105
|
+
// Insert in bounded chunks: a single multi-KB Input.insertText makes
|
|
2106
|
+
// ProseMirror do one huge transaction, which on a heavy thread stalls past
|
|
2107
|
+
// the 20s CDP command timeout and kills the send with "Chrome DevTools
|
|
2108
|
+
// command timed out: Input.insertText" (field failure on long prompts,
|
|
2109
|
+
// twice in one session). Each chunk gets its own command budget.
|
|
2110
|
+
for (const chunk of chunkComposerText(text)) {
|
|
2111
|
+
await cdp.send("Input.insertText", { text: chunk });
|
|
2112
|
+
}
|
|
2080
2113
|
await sleep(200);
|
|
2081
2114
|
const state = await cdp.evaluate(composerTextStateExpression(text));
|
|
2082
2115
|
if (!state.ok)
|
|
2083
2116
|
throw new Error(state.reason ?? "Composer stayed empty after text insertion");
|
|
2084
2117
|
}
|
|
2118
|
+
export const COMPOSER_INSERT_CHUNK_CHARS = 4_000;
|
|
2119
|
+
/**
|
|
2120
|
+
* Split composer text into insertText-sized pieces. Splits on code points (not
|
|
2121
|
+
* UTF-16 units) so a surrogate pair - an emoji, or any astral character - can
|
|
2122
|
+
* never be cut in half and arrive as two replacement characters.
|
|
2123
|
+
*/
|
|
2124
|
+
export function chunkComposerText(text, size = COMPOSER_INSERT_CHUNK_CHARS) {
|
|
2125
|
+
if (text.length === 0)
|
|
2126
|
+
return [];
|
|
2127
|
+
if (text.length <= size)
|
|
2128
|
+
return [text];
|
|
2129
|
+
const chunks = [];
|
|
2130
|
+
let current = "";
|
|
2131
|
+
for (const character of text) {
|
|
2132
|
+
if (current.length + character.length > size) {
|
|
2133
|
+
chunks.push(current);
|
|
2134
|
+
current = "";
|
|
2135
|
+
}
|
|
2136
|
+
current += character;
|
|
2137
|
+
}
|
|
2138
|
+
if (current.length > 0)
|
|
2139
|
+
chunks.push(current);
|
|
2140
|
+
return chunks;
|
|
2141
|
+
}
|
|
2085
2142
|
export function submitExpression() {
|
|
2086
2143
|
return `(() => {
|
|
2087
2144
|
${composerExpressionHelpers()}
|
package/dist/cli-help.js
CHANGED
|
@@ -19,14 +19,14 @@ Ask / consult commands:
|
|
|
19
19
|
prodex ask [same flags as pro browser ask] "prompt" # top-level shortcut for pro browser ask
|
|
20
20
|
prodex pro ask [--dry-run] [--cwd /absolute/path/to/repo] [--file path] "prompt" # dry-run preview
|
|
21
21
|
prodex pro debate-prompt [--topic "..."] [--rounds 2] [--source-cli /absolute/path/to/dist/cli.js] # print an agent prompt for a structured GPT Pro debate
|
|
22
|
-
prodex pro browser login [--cwd /absolute/path/to/repo] [--dry-run] [--source-cli /absolute/path/to/dist/cli.js] [--profile-dir path] [--port 9333] [--url https://chatgpt.com/...] [--launch-timeout-ms 5000] [--wait|--no-wait] [--wait-timeout-ms 300000] # preview/open visible browser login
|
|
22
|
+
prodex pro browser login [--cwd /absolute/path/to/repo] [--dry-run] [--source-cli /absolute/path/to/dist/cli.js] [--profile-dir path] [--port 9333] [--url https://chatgpt.com/...] [--launch-timeout-ms 5000] [--wait|--no-wait] [--headless] [--wait-timeout-ms 300000] # preview/open visible browser login
|
|
23
23
|
prodex pro browser help [--source-cli /absolute/path/to/dist/cli.js]
|
|
24
24
|
prodex pro browser check [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 1500]
|
|
25
25
|
prodex pro browser smoke [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 90000]
|
|
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
|
|
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] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"] [--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]
|
|
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]
|
|
@@ -162,7 +162,7 @@ Commands:
|
|
|
162
162
|
prodex pro ask [--dry-run] [--cwd /absolute/path/to/repo] [--file path] "prompt"
|
|
163
163
|
prodex pro debate-prompt [--topic "..."] [--rounds 2] [--source-cli /absolute/path/to/dist/cli.js]
|
|
164
164
|
prodex pro browser help [--source-cli /absolute/path/to/dist/cli.js]
|
|
165
|
-
prodex pro browser login [--cwd /absolute/path/to/repo] [--dry-run] [--source-cli /absolute/path/to/dist/cli.js] [--launch-timeout-ms 5000] [--wait|--no-wait] [--wait-timeout-ms 300000]
|
|
165
|
+
prodex pro browser login [--cwd /absolute/path/to/repo] [--dry-run] [--source-cli /absolute/path/to/dist/cli.js] [--launch-timeout-ms 5000] [--wait|--no-wait] [--headless] [--wait-timeout-ms 300000]
|
|
166
166
|
prodex pro browser check [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo]
|
|
167
167
|
prodex pro browser smoke [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo]
|
|
168
168
|
prodex pro browser models [--source-cli /absolute/path/to/dist/cli.js]
|
|
@@ -242,8 +242,8 @@ export function printProBrowserHelp(stdout, sourceCli) {
|
|
|
242
242
|
const cli = formatCliCommand(sourceCli);
|
|
243
243
|
const sourceCliOption = formatSourceCliOption(sourceCli);
|
|
244
244
|
const loginUsage = sourceCli
|
|
245
|
-
? `${cli} pro browser login${sourceCliOption} [--cwd /absolute/path/to/repo] [--dry-run] [--profile-dir path] [--port 9333] [--url https://chatgpt.com/...] [--launch-timeout-ms 5000] [--wait|--no-wait] [--wait-timeout-ms 300000]`
|
|
246
|
-
: "prodex pro browser login [--cwd /absolute/path/to/repo] [--dry-run] [--source-cli /absolute/path/to/dist/cli.js] [--profile-dir path] [--port 9333] [--url https://chatgpt.com/...] [--launch-timeout-ms 5000] [--wait|--no-wait] [--wait-timeout-ms 300000]";
|
|
245
|
+
? `${cli} pro browser login${sourceCliOption} [--cwd /absolute/path/to/repo] [--dry-run] [--profile-dir path] [--port 9333] [--url https://chatgpt.com/...] [--launch-timeout-ms 5000] [--wait|--no-wait] [--headless] [--wait-timeout-ms 300000]`
|
|
246
|
+
: "prodex pro browser login [--cwd /absolute/path/to/repo] [--dry-run] [--source-cli /absolute/path/to/dist/cli.js] [--profile-dir path] [--port 9333] [--url https://chatgpt.com/...] [--launch-timeout-ms 5000] [--wait|--no-wait] [--headless] [--wait-timeout-ms 300000]";
|
|
247
247
|
const checkUsage = sourceCli
|
|
248
248
|
? `${cli} pro browser check${sourceCliOption} [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 1500]`
|
|
249
249
|
: "prodex pro browser check [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 1500]";
|
|
@@ -252,8 +252,8 @@ export function printProBrowserHelp(stdout, sourceCli) {
|
|
|
252
252
|
: "prodex pro browser smoke [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 90000]";
|
|
253
253
|
const selectionUsage = '[--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"] [--project "name" | --project-new "name"]';
|
|
254
254
|
const askUsage = sourceCli
|
|
255
|
-
? `${cli} pro browser ask${sourceCliOption} [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms
|
|
256
|
-
: `prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms
|
|
255
|
+
? `${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] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] ${selectionUsage} "prompt"`
|
|
256
|
+
: `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] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] ${selectionUsage} "prompt"`;
|
|
257
257
|
const modelsUsage = sourceCli
|
|
258
258
|
? `${cli} pro browser models${sourceCliOption} [--port 9333] [--timeout-ms 15000]`
|
|
259
259
|
: "prodex pro browser models [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--timeout-ms 15000]";
|
package/dist/cli-pro.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { buildDryRunBundle } from "./bundle.js";
|
|
4
|
-
import { DEFAULT_CDP_PORT, resolveCdpPort, chatGptVisibilityBlocker, defaultChatGptProfileDir, formatDurationMs, getChatGptBrowserStatus, listChatGptModelOptions, listChatGptSidebarProjects, normalizeChatGptTargetUrl, openChatGptBrowser, parseProMode, parseReasoningEffort, readLastBrowserLoginLaunch, recordBrowserLoginLaunch, recoverChatGptAnswerFromThread, sendChatGptPrompt } from "./chatgpt-browser.js";
|
|
4
|
+
import { DEFAULT_CDP_PORT, resolveCdpPort, chatGptVisibilityBlocker, defaultChatGptProfileDir, formatDurationMs, getChatGptBrowserStatus, listChatGptModelOptions, listChatGptSidebarProjects, normalizeChatGptTargetUrl, openChatGptBrowser, parseProMode, parseReasoningEffort, readLastBrowserLoginLaunch, resolveHeadlessPreference, recordBrowserLoginLaunch, recoverChatGptAnswerFromThread, sendChatGptPrompt } from "./chatgpt-browser.js";
|
|
5
5
|
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";
|
|
6
6
|
import { printProBrowserHelp, printProHelp } from "./cli-help.js";
|
|
7
7
|
import { listRawResultsForInspection, listTasksForInspection } from "./cli-ledger.js";
|
|
@@ -185,11 +185,11 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
185
185
|
if (browserSubcommand === "login") {
|
|
186
186
|
if (printProBrowserHelpIfRequested(browserArgs, "pro browser login", io, {
|
|
187
187
|
valueFlags: ["--cwd", "--profile-dir", "--port", "--url", "--source-cli", "--launch-timeout-ms", "--wait-timeout-ms"],
|
|
188
|
-
booleanFlags: ["--dry-run", "--wait", "--no-wait"]
|
|
188
|
+
booleanFlags: ["--dry-run", "--wait", "--no-wait", "--headless"]
|
|
189
189
|
})) {
|
|
190
190
|
return 0;
|
|
191
191
|
}
|
|
192
|
-
assertOnlyOptions(browserArgs, "pro browser login", ["--cwd", "--profile-dir", "--port", "--url", "--source-cli", "--launch-timeout-ms", "--wait-timeout-ms"], ["--dry-run", "--wait", "--no-wait"]);
|
|
192
|
+
assertOnlyOptions(browserArgs, "pro browser login", ["--cwd", "--profile-dir", "--port", "--url", "--source-cli", "--launch-timeout-ms", "--wait-timeout-ms"], ["--dry-run", "--wait", "--no-wait", "--headless"]);
|
|
193
193
|
if (browserArgs.includes("--wait") && browserArgs.includes("--no-wait")) {
|
|
194
194
|
throw new Error("pro browser login cannot combine --wait and --no-wait");
|
|
195
195
|
}
|
|
@@ -221,24 +221,51 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
221
221
|
// again: Chrome's singleton would just open ANOTHER window (the recurring
|
|
222
222
|
// "extra windows" problem, which then blocks sends as
|
|
223
223
|
// ambiguous_chatgpt_tabs). Reuse the running instance instead.
|
|
224
|
+
const headless = resolveHeadlessPreference(browserArgs.includes("--headless") ? true : undefined);
|
|
224
225
|
const alreadyRunning = (await getChatGptBrowserStatus({ port })).reachable;
|
|
226
|
+
if (alreadyRunning) {
|
|
227
|
+
// One Chrome profile cannot serve a headed and a headless instance at
|
|
228
|
+
// once, and reusing the running one would silently ignore the
|
|
229
|
+
// requested mode. Say so instead of pretending the switch took.
|
|
230
|
+
const previous = await readLastBrowserLoginLaunch();
|
|
231
|
+
const runningHeadless = previous?.port === port ? previous.headless === true : undefined;
|
|
232
|
+
if (runningHeadless !== undefined && runningHeadless !== headless) {
|
|
233
|
+
throw new Error(`A ${runningHeadless ? "headless" : "headed"} ChatGPT browser is already running on port ${port}, but ${headless ? "headless" : "headed"} was requested. Close it first (\`pkill -f "remote-debugging-port=${port}"\`), then rerun.`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
225
236
|
const opened = alreadyRunning
|
|
226
237
|
? { profileDir: profileDir ?? defaultChatGptProfileDir(), port }
|
|
227
|
-
: openChatGptBrowser({ port, profileDir, url: loginUrl });
|
|
238
|
+
: openChatGptBrowser({ port, profileDir, url: loginUrl, ...(headless ? { headless } : {}) });
|
|
228
239
|
if (!alreadyRunning) {
|
|
229
240
|
await assertBrowserLaunchStayedAlive(opened, launchTimeoutMs);
|
|
230
241
|
}
|
|
231
|
-
// Remember this launch so ask auto-recovery reuses the same profile
|
|
232
|
-
|
|
242
|
+
// Remember this launch so ask auto-recovery reuses the same profile
|
|
243
|
+
// AND the same window mode.
|
|
244
|
+
await recordBrowserLoginLaunch({ profile_dir: opened.profileDir, port: opened.port, headless });
|
|
233
245
|
printBrowserLoginGuide(io.stdout, {
|
|
234
246
|
opened: !alreadyRunning,
|
|
235
247
|
reused: alreadyRunning,
|
|
248
|
+
headless,
|
|
236
249
|
loginUrl,
|
|
237
250
|
profileDir: opened.profileDir,
|
|
238
251
|
port: opened.port,
|
|
239
252
|
sourceCli,
|
|
240
253
|
commandOptions
|
|
241
254
|
});
|
|
255
|
+
if (headless) {
|
|
256
|
+
// Nobody can sign in to a window that does not exist, so a headless
|
|
257
|
+
// launch is only useful when the profile is already logged in.
|
|
258
|
+
// Verify it here (bounded, no human to wait for) instead of letting
|
|
259
|
+
// the first consult fail with a confusing not-logged-in blocker.
|
|
260
|
+
const headlessReady = await waitForChatGptLoginReady(io.stderr, { port: opened.port, timeoutMs: 30_000 });
|
|
261
|
+
if (!headlessReady) {
|
|
262
|
+
io.stdout("");
|
|
263
|
+
io.stdout(`headless: the profile is not signed in. Run \`${formatBrowserLoginCommand(sourceCli, commandOptions)}\` WITHOUT --headless once, sign in, close that window, then rerun with --headless.`);
|
|
264
|
+
return 1;
|
|
265
|
+
}
|
|
266
|
+
io.stdout("headless: signed-in session confirmed - consults will run with no visible window.");
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
242
269
|
// Guided wait: interactive terminals walk the user to a verified READY
|
|
243
270
|
// state instead of returning while login is still unfinished. Scripts
|
|
244
271
|
// and agents (non-TTY) keep the immediate return unless --wait is
|
|
@@ -277,8 +304,7 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
277
304
|
}
|
|
278
305
|
}
|
|
279
306
|
if (browserSubcommand === "open" || browserSubcommand === "status" || browserSubcommand === "doctor") {
|
|
280
|
-
|
|
281
|
-
throw new Error(`Use \`prodex pro browser ${replacement}\` for explicit browser automation.`);
|
|
307
|
+
throw new Error(`Use \`prodex pro browser ${legacyBrowserSubcommandReplacement(browserSubcommand)}\` for explicit browser automation.`);
|
|
282
308
|
}
|
|
283
309
|
if (browserSubcommand === "smoke") {
|
|
284
310
|
if (printProBrowserHelpIfRequested(browserArgs, "pro browser smoke", io, { valueFlags: ["--cwd", "--port", "--timeout-ms", "--source-cli"] }))
|
|
@@ -426,7 +452,10 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
426
452
|
throw unknownSubcommandError("pro browser", browserSubcommand, ["login", "ask", "smoke", "check", "models", "projects", "recover"]);
|
|
427
453
|
}
|
|
428
454
|
if (subcommand === "open" || subcommand === "status" || subcommand === "smoke" || subcommand === "check" || subcommand === "doctor") {
|
|
429
|
-
|
|
455
|
+
// Point at a subcommand that EXISTS: `pro status` used to say "use `pro
|
|
456
|
+
// browser status`", which itself errors with "use `pro browser check`" -
|
|
457
|
+
// a two-hop dead end that made an agent abandon the diagnosis.
|
|
458
|
+
throw new Error(`Use \`prodex pro browser ${legacyBrowserSubcommandReplacement(subcommand)}\` for explicit browser automation.`);
|
|
430
459
|
}
|
|
431
460
|
if (subcommand === "list") {
|
|
432
461
|
if (printHelpIfRequested(proArgs, "pro list", io.stdout, printProHelp, { valueFlags: ["--cwd", "--source-cli"] }))
|
|
@@ -531,6 +560,20 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
531
560
|
export async function runConsultsCommand(rest, io) {
|
|
532
561
|
throw new Error("The legacy `consults` alias is retired. Use `prodex pro list`, `prodex pro latest`, or `prodex pro show <task-id|latest>`.");
|
|
533
562
|
}
|
|
563
|
+
function autoLoginDisabledByEnv(env = process.env) {
|
|
564
|
+
const raw = (env.PRODEX_NO_AUTO_LOGIN ?? "").trim().toLowerCase();
|
|
565
|
+
return raw === "1" || raw === "true" || raw === "yes";
|
|
566
|
+
}
|
|
567
|
+
// Retired browser subcommands map to the one that replaced them. Every value
|
|
568
|
+
// here must be a subcommand that actually exists, so the error is a single hop
|
|
569
|
+
// to a runnable command.
|
|
570
|
+
function legacyBrowserSubcommandReplacement(subcommand) {
|
|
571
|
+
if (subcommand === "open")
|
|
572
|
+
return "login";
|
|
573
|
+
if (subcommand === "smoke")
|
|
574
|
+
return "smoke";
|
|
575
|
+
return "check";
|
|
576
|
+
}
|
|
534
577
|
// File marker + human-pacing gate for visible-browser sends. Reads the previous
|
|
535
578
|
// send's start time from .bridge/last-browser-send, waits out any remaining
|
|
536
579
|
// minimum interval (auto-throttle, not an error), then stamps the new send.
|
|
@@ -702,7 +745,11 @@ export async function runAskProCommand(rest, io) {
|
|
|
702
745
|
const effectiveProSelection = selectionProMode !== undefined || (selectionModel !== undefined && /pro/i.test(selectionModel));
|
|
703
746
|
// Pro reasoning routinely runs 6-20 minutes; 15 min was still cutting long
|
|
704
747
|
// answers off (field report), so a Pro selection defaults to 20 minutes.
|
|
705
|
-
|
|
748
|
+
// With NO model selection at all (no flag, no saved default) the UI's
|
|
749
|
+
// current model is unknown and may be Pro, so the floor is 5 minutes: the
|
|
750
|
+
// old 90s guess made repos without saved defaults time out on ordinary
|
|
751
|
+
// consults (observed in several field sessions).
|
|
752
|
+
const defaultBrowserTimeoutMs = effectiveProSelection ? 1_200_000 : 300_000;
|
|
706
753
|
const browserTimeoutMs = hasSendMode
|
|
707
754
|
? (readPositiveIntegerFlag(parsedAskPro.optionArgs, "--timeout-ms") ?? defaultBrowserTimeoutMs)
|
|
708
755
|
: undefined;
|
|
@@ -759,7 +806,12 @@ export async function runAskProCommand(rest, io) {
|
|
|
759
806
|
}
|
|
760
807
|
throw new Error(formatBlockedConsultRecordedMessage(blocker.message, task.id, sourceCli, { cwd: targetCwd }));
|
|
761
808
|
}
|
|
762
|
-
|
|
809
|
+
// Queue behind another prodex send by default (same budget as the send
|
|
810
|
+
// itself). MCP consults cannot pass --busy-wait-ms at all, so the old
|
|
811
|
+
// fail-fast default made pro_consult die instantly with advice the
|
|
812
|
+
// caller had no way to follow; --busy-wait-ms 0 opts back into failing
|
|
813
|
+
// fast. A dead holder is still reaped immediately.
|
|
814
|
+
const sendOnce = () => withBrowserSendLock(busyWaitMs ?? browserTimeoutMs ?? 0, (detail) => io.stderr(`progress: ${detail}`), () => sendChatGptPrompt({
|
|
763
815
|
port: browserPort,
|
|
764
816
|
prompt: bundle.text,
|
|
765
817
|
targetUrl: normalizedTargetUrl,
|
|
@@ -1013,6 +1065,12 @@ export async function performBrowserConsultForMcp(cwd, input, onProgress) {
|
|
|
1013
1065
|
const stderrLines = [];
|
|
1014
1066
|
const argv = [
|
|
1015
1067
|
"--send",
|
|
1068
|
+
// MCP callers have no terminal, so the interactive auto-recovery gate
|
|
1069
|
+
// never fired for them: a closed browser made every pro_consult fail with
|
|
1070
|
+
// a step the agent had to shell out for (the single most common field
|
|
1071
|
+
// failure). Recovery reuses the saved profile and window mode, and
|
|
1072
|
+
// PRODEX_NO_AUTO_LOGIN=1 turns it off.
|
|
1073
|
+
...(autoLoginDisabledByEnv() ? ["--no-auto-login"] : ["--auto-login"]),
|
|
1016
1074
|
...(input.model !== undefined ? ["--model", input.model] : []),
|
|
1017
1075
|
...(input.pro_mode !== undefined ? ["--pro-mode", input.pro_mode] : []),
|
|
1018
1076
|
...(input.effort !== undefined ? ["--effort", input.effort] : []),
|
|
@@ -1057,9 +1115,14 @@ export async function attemptBrowserAutoRecovery(stderr, options) {
|
|
|
1057
1115
|
// profile for a custom-profile user would wait on the wrong (logged-out)
|
|
1058
1116
|
// profile or, worse, silently send to a different account.
|
|
1059
1117
|
const lastLogin = await readLastBrowserLoginLaunch();
|
|
1118
|
+
// Relaunch in the SAME window mode the user chose: silently reopening a
|
|
1119
|
+
// visible window for someone running headless would be exactly the
|
|
1120
|
+
// surprise window they turned headless to avoid.
|
|
1121
|
+
const headless = resolveHeadlessPreference(lastLogin?.headless);
|
|
1060
1122
|
const opened = openChatGptBrowser({
|
|
1061
1123
|
...(options.port !== undefined ? { port: options.port } : {}),
|
|
1062
|
-
...(lastLogin?.profile_dir ? { profileDir: lastLogin.profile_dir } : {})
|
|
1124
|
+
...(lastLogin?.profile_dir ? { profileDir: lastLogin.profile_dir } : {}),
|
|
1125
|
+
...(headless ? { headless } : {})
|
|
1063
1126
|
});
|
|
1064
1127
|
await assertBrowserLaunchStayedAlive(opened);
|
|
1065
1128
|
const ready = await waitForChatGptLoginReady(stderr, { port: opened.port, timeoutMs: 120_000 });
|
|
@@ -1164,6 +1227,18 @@ export function browserSendBlockerFromError(error) {
|
|
|
1164
1227
|
next_step: `Rerun with a bigger budget (${formatDurationMs(suggestedMs)}): \`prodex pro browser ask --timeout-ms ${suggestedMs} "<same prompt>"\`.`
|
|
1165
1228
|
};
|
|
1166
1229
|
}
|
|
1230
|
+
// A CDP command timeout means the page's renderer stalled, which in the
|
|
1231
|
+
// field means a very long thread (or a long prompt landing on one). Generic
|
|
1232
|
+
// "resolve the browser issue manually" advice gave the caller nothing to do.
|
|
1233
|
+
const cdpTimeout = message.match(/Chrome DevTools command timed out: (\S+)/);
|
|
1234
|
+
if (cdpTimeout) {
|
|
1235
|
+
return {
|
|
1236
|
+
code: "browser_cdp_timeout",
|
|
1237
|
+
message,
|
|
1238
|
+
retryable: true,
|
|
1239
|
+
next_step: "The ChatGPT tab stopped responding (usually a very long thread). Retry with `--new-chat` for a fresh, light thread, or reload the tab in the browser first."
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1167
1242
|
return {
|
|
1168
1243
|
code: "browser_send_failed",
|
|
1169
1244
|
message,
|
|
@@ -1239,33 +1314,52 @@ export async function getConsult(store, taskId, options = {}) {
|
|
|
1239
1314
|
return isConsultRecord(record) ? record : undefined;
|
|
1240
1315
|
}
|
|
1241
1316
|
export async function latestTrustedConsult(store, options = { readOnly: true }) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1317
|
+
// Verify lazily, newest first, and stop at the first trusted consult.
|
|
1318
|
+
// Verifying the whole history to report ONE record made `pro browser check`
|
|
1319
|
+
// spend 42 of its 46 seconds in latest_pro on a repo with real history
|
|
1320
|
+
// (measured live) - each record is a full receipt scan - and agents read
|
|
1321
|
+
// that silence as a hung bridge.
|
|
1322
|
+
const records = await listConsultRecordsNewestFirst(store, options);
|
|
1323
|
+
let firstUntrusted;
|
|
1324
|
+
for (const record of records) {
|
|
1325
|
+
try {
|
|
1326
|
+
return { ...record, result: await store.getFinalizedResultReadOnly(record.result.task_id) };
|
|
1327
|
+
}
|
|
1328
|
+
catch (error) {
|
|
1329
|
+
if (isUntrustedResultError(error)) {
|
|
1330
|
+
firstUntrusted ??= error;
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
throw error;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
if (firstUntrusted)
|
|
1337
|
+
throw firstUntrusted;
|
|
1249
1338
|
return undefined;
|
|
1250
1339
|
}
|
|
1251
1340
|
export function legacyChatGptNamespaceError(subcommand) {
|
|
1252
1341
|
const prefix = subcommand ? `Unknown legacy chatgpt subcommand: ${subcommand}.` : "The legacy `chatgpt` namespace is hidden.";
|
|
1253
1342
|
return new Error(`${prefix} Use \`prodex pro browser help\` for visible-browser commands.`);
|
|
1254
1343
|
}
|
|
1255
|
-
|
|
1344
|
+
// Consult records (unverified) newest first, with the ledger-integrity checks
|
|
1345
|
+
// that must run regardless of how many records the caller ends up verifying.
|
|
1346
|
+
async function listConsultRecordsNewestFirst(store, options = { readOnly: true }) {
|
|
1256
1347
|
const [tasks, results] = options.readOnly === false
|
|
1257
1348
|
? await Promise.all([store.listTasks(), store.listResults()])
|
|
1258
1349
|
: await Promise.all([listTasksForInspection(store), listRawResultsForInspection(store)]);
|
|
1259
1350
|
const tasksById = new Map(tasks.map((task) => [task.id, task]));
|
|
1260
1351
|
assertNoMissingTerminalConsultResults(tasks, results);
|
|
1261
1352
|
assertNoOrphanConsultResults(tasksById, results);
|
|
1262
|
-
|
|
1353
|
+
return results
|
|
1263
1354
|
.map((result) => {
|
|
1264
1355
|
const task = tasksById.get(result.task_id);
|
|
1265
1356
|
return task ? { task, result } : undefined;
|
|
1266
1357
|
})
|
|
1267
1358
|
.filter((record) => Boolean(record && isConsultRecord(record)))
|
|
1268
1359
|
.sort((a, b) => b.result.created_at.localeCompare(a.result.created_at));
|
|
1360
|
+
}
|
|
1361
|
+
export async function listConsultListEntries(store, options = { readOnly: true }) {
|
|
1362
|
+
const records = await listConsultRecordsNewestFirst(store, options);
|
|
1269
1363
|
const entries = [];
|
|
1270
1364
|
for (const record of records) {
|
|
1271
1365
|
try {
|
|
@@ -1282,7 +1376,7 @@ export async function listConsultListEntries(store, options = { readOnly: true }
|
|
|
1282
1376
|
return entries;
|
|
1283
1377
|
}
|
|
1284
1378
|
export function printBrowserLoginGuide(stdout, input) {
|
|
1285
|
-
const windowAvailable = input.opened || input.reused === true;
|
|
1379
|
+
const windowAvailable = (input.opened || input.reused === true) && input.headless !== true;
|
|
1286
1380
|
const loginCommand = formatBrowserLoginCommand(input.sourceCli, input.commandOptions);
|
|
1287
1381
|
const runtimeCommandOptions = {
|
|
1288
1382
|
...(input.commandOptions?.cwd ? { cwd: input.commandOptions.cwd } : {}),
|
|
@@ -1291,11 +1385,20 @@ export function printBrowserLoginGuide(stdout, input) {
|
|
|
1291
1385
|
const checkCommand = formatBrowserCheckCommand(input.sourceCli, runtimeCommandOptions);
|
|
1292
1386
|
const smokeCommand = formatBrowserSmokeCommand(input.sourceCli, runtimeCommandOptions);
|
|
1293
1387
|
stdout("ChatGPT Pro browser login");
|
|
1294
|
-
stdout(input.reused
|
|
1295
|
-
?
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1388
|
+
stdout(input.headless && (input.opened || input.reused)
|
|
1389
|
+
? input.reused
|
|
1390
|
+
? "Headless ChatGPT browser is already running - reusing it (no window)."
|
|
1391
|
+
: "Started the dedicated ChatGPT browser headless (no window). It reuses the profile you signed in with."
|
|
1392
|
+
: input.reused
|
|
1393
|
+
? "Chrome is already running for ChatGPT - reusing it (no new window opened)."
|
|
1394
|
+
: input.opened
|
|
1395
|
+
? "Opened the dedicated Chrome window for ChatGPT."
|
|
1396
|
+
: "Dry run: no browser was opened.");
|
|
1397
|
+
if (input.headless && (input.opened || input.reused)) {
|
|
1398
|
+
stdout("");
|
|
1399
|
+
stdout(`Next: run \`${checkCommand}\` to confirm the session, then consult as usual.`);
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1299
1402
|
stdout("");
|
|
1300
1403
|
stdout("Steps:");
|
|
1301
1404
|
if (windowAvailable) {
|
|
@@ -1366,7 +1469,10 @@ export async function printProductCheck(store, io, args, configCwd = io.cwd) {
|
|
|
1366
1469
|
}
|
|
1367
1470
|
catch (error) {
|
|
1368
1471
|
if (isMissingFileError(error)) {
|
|
1369
|
-
|
|
1472
|
+
// Name it optional: browser consults need no config at all, but agents
|
|
1473
|
+
// reading "config: missing" in an otherwise healthy check concluded
|
|
1474
|
+
// prodex was unconfigured and went chasing `setup` (field sessions).
|
|
1475
|
+
io.stdout(`config: missing - run \`${formatSetupCommand(sourceCli, { cwd: setupHintCwd })}\` (optional - only the HTTP MCP surface needs it; browser consults work without it)`);
|
|
1370
1476
|
}
|
|
1371
1477
|
else {
|
|
1372
1478
|
io.stdout(`config: failed ${sourceAwareSetupMessage(errorMessage(error), sourceCli, { cwd: setupHintCwd })}`);
|
|
@@ -1412,6 +1518,15 @@ export async function printProductCheck(store, io, args, configCwd = io.cwd) {
|
|
|
1412
1518
|
if (nextStep)
|
|
1413
1519
|
io.stdout(`next: ${nextStep}`);
|
|
1414
1520
|
}
|
|
1521
|
+
else if (browserStatus.blocker?.code === "response_in_progress") {
|
|
1522
|
+
// Busy is not broken: the session is healthy and answering, and sends
|
|
1523
|
+
// queue behind it automatically. Reporting it as "blocked" made agents
|
|
1524
|
+
// conclude the browser was down and start relaunching/logging in (field
|
|
1525
|
+
// failure, observed across several repos).
|
|
1526
|
+
io.stdout(`chatgpt: busy ${browserStatus.blocker.code} - ${browserStatus.blocker.message}`);
|
|
1527
|
+
io.stdout("next: No action needed - a send queues behind it automatically (--busy-wait-ms 0 fails fast instead).");
|
|
1528
|
+
chatgptReady = true;
|
|
1529
|
+
}
|
|
1415
1530
|
else if (browserStatus.blocker) {
|
|
1416
1531
|
const visibilityText = browserStatus.blocker.code === "tab_not_visible" ? ` visibility=${browserStatus.visibilityState ?? "unknown"}` : "";
|
|
1417
1532
|
io.stdout(`chatgpt: blocked ${browserStatus.blocker.code}${visibilityText} - ${browserStatus.blocker.message}`);
|
package/dist/cli.js
CHANGED
|
@@ -929,7 +929,10 @@ async function runDoctor(store, io, sourceCli, setupHintCwd) {
|
|
|
929
929
|
}
|
|
930
930
|
catch (error) {
|
|
931
931
|
if (isMissingFileError(error)) {
|
|
932
|
-
|
|
932
|
+
// Name it optional: browser consults need no config at all, but agents
|
|
933
|
+
// reading "config: missing" in an otherwise healthy check concluded
|
|
934
|
+
// prodex was unconfigured and went chasing `setup` (field sessions).
|
|
935
|
+
io.stdout(`config: missing - run \`${formatSetupCommand(sourceCli, { cwd: setupHintCwd })}\` (optional - only the HTTP MCP surface needs it; browser consults work without it)`);
|
|
933
936
|
}
|
|
934
937
|
else {
|
|
935
938
|
ok = false;
|
package/dist/mcp.js
CHANGED
|
@@ -142,7 +142,10 @@ export function createServer(cwd = process.cwd(), options = {}) {
|
|
|
142
142
|
inputSchema: {
|
|
143
143
|
prompt: McpBridgeTextSchema.min(1),
|
|
144
144
|
model: McpShortTextSchema.optional(),
|
|
145
|
-
pro_mode:
|
|
145
|
+
// pro_mode is deliberately NOT advertised: ChatGPT's 2026-07 update
|
|
146
|
+
// removed Pro sub-modes, and agents that saw the field passed
|
|
147
|
+
// nonsense ("true") and got a hard validation error instead of an
|
|
148
|
+
// answer. Unknown keys are stripped, so a stale caller still works.
|
|
146
149
|
effort: McpShortTextSchema.optional(),
|
|
147
150
|
project: McpShortTextSchema.optional(),
|
|
148
151
|
timeout_ms: z.number().int().positive().max(3_600_000).optional(),
|