@youdie006/prodex 0.19.1 → 0.19.2

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.
@@ -1729,7 +1729,7 @@ export async function sendChatGptPrompt(options) {
1729
1729
  // into; a --project/--project-new hop lands on a page with its own counts.
1730
1730
  beforeSubmit = await evaluateOnPage(page, answerExpression());
1731
1731
  dbgSend(`baseline url=${beforeSubmit.url} user=${beforeSubmit.userMessageCount} assistant=${beforeSubmit.assistantMessageCount}`);
1732
- await insertComposerTextViaCdp(cdp, options.prompt);
1732
+ await insertComposerTextViaCdp(cdp, options.prompt, page);
1733
1733
  // The send button renders asynchronously after the prompt lands. Poll for it
1734
1734
  // BEFORE submitting so (a) submitButtonFound reflects whether the control
1735
1735
  // actually EXISTS - otherwise a successful Enter-key submit skips the fallback
@@ -2263,6 +2263,36 @@ export function prepareComposerExpression() {
2263
2263
  // just that it is non-empty): a failed clear would leave stale text prepended,
2264
2264
  // silently submitting a contaminated prompt. Whitespace is collapsed on both
2265
2265
  // sides because ProseMirror round-trips newlines as extra blank lines.
2266
+ /**
2267
+ * Insert the whole prompt with ONE in-page execCommand("insertText"). The
2268
+ * editor applies it as a single input event, so - unlike a chunked
2269
+ * Input.insertText sequence - nothing can interleave at a boundary and the
2270
+ * text lands byte-for-byte. Used for prompts too large to push through
2271
+ * Input.insertText in one CDP command.
2272
+ */
2273
+ export function insertComposerTextInPageExpression(text) {
2274
+ const textJson = JSON.stringify(text);
2275
+ return `(() => {
2276
+ ${composerExpressionHelpers()}
2277
+ const el = findChatGptComposerCandidate();
2278
+ if (!el) return { ok: false, reason: "No visible composer" };
2279
+ el.focus();
2280
+ if ("value" in el) {
2281
+ el.value = ${textJson};
2282
+ el.dispatchEvent(new Event("input", { bubbles: true }));
2283
+ return { ok: true };
2284
+ }
2285
+ const selection = window.getSelection();
2286
+ const all = document.createRange();
2287
+ all.selectNodeContents(el);
2288
+ selection.removeAllRanges();
2289
+ selection.addRange(all);
2290
+ document.execCommand("delete");
2291
+ const inserted = document.execCommand("insertText", false, ${textJson});
2292
+ if (!inserted) return { ok: false, reason: "The ChatGPT composer refused the prompt text" };
2293
+ return { ok: true };
2294
+ })()`;
2295
+ }
2266
2296
  export function composerTextStateExpression(expectedText) {
2267
2297
  const expectedJson = JSON.stringify(expectedText ?? null);
2268
2298
  return `(() => {
@@ -2283,7 +2313,7 @@ export function composerTextStateExpression(expectedText) {
2283
2313
  // Focus the composer, clear any leftover text submit-safely, type the prompt
2284
2314
  // with native CDP input so ProseMirror registers it, then verify the composer
2285
2315
  // holds exactly the prompt.
2286
- async function insertComposerTextViaCdp(cdp, text) {
2316
+ async function insertComposerTextViaCdp(cdp, text, page) {
2287
2317
  const prepared = await cdp.evaluate(prepareComposerExpression());
2288
2318
  if (!prepared.ok)
2289
2319
  throw new Error(prepared.reason ?? "Could not focus the ChatGPT composer");
@@ -2299,13 +2329,42 @@ async function insertComposerTextViaCdp(cdp, text) {
2299
2329
  await cdp.send("Input.dispatchKeyEvent", { type: "keyUp", key: "Backspace", code: "Backspace", windowsVirtualKeyCode: 8 });
2300
2330
  await sleep(100);
2301
2331
  }
2302
- // Insert in bounded chunks: a single multi-KB Input.insertText makes
2303
- // ProseMirror do one huge transaction, which on a heavy thread stalls past
2304
- // the 20s CDP command timeout and kills the send with "Chrome DevTools
2305
- // command timed out: Input.insertText" (field failure on long prompts,
2306
- // twice in one session). Each chunk gets its own command budget.
2307
- for (const chunk of chunkComposerText(text)) {
2308
- await cdp.send("Input.insertText", { text: chunk });
2332
+ // Insertion path, chosen by size:
2333
+ //
2334
+ // Short prompts go through Input.insertText - real key-level input events,
2335
+ // which is what ChatGPT's composer is built for.
2336
+ //
2337
+ // Long prompts do NOT. One giant insertText stalls ProseMirror past the CDP
2338
+ // command timeout, and splitting it into chunks corrupts the text at the
2339
+ // chunk boundaries: measured live on a 95 KB prompt, the composer ended up
2340
+ // the right LENGTH but with content shifted from the first boundary on
2341
+ // (first divergence at 3,938 chars with a 4,000-char chunk size), which is
2342
+ // what surfaced to users as "Composer text did not match after insertion".
2343
+ // Both failures come from crossing the CDP boundary mid-edit, so large text
2344
+ // is inserted by a single in-page execCommand instead: the editor applies it
2345
+ // as one input event and nothing can interleave. Measured: 67 KB inserted in
2346
+ // ~20s, verified byte-for-byte.
2347
+ if (text.length <= COMPOSER_INSERT_CHUNK_CHARS) {
2348
+ await cdp.send("Input.insertText", { text });
2349
+ }
2350
+ else {
2351
+ // A 67 KB insert measured ~20s in the page, which the default 20s CDP
2352
+ // command budget would cut off, so this one call gets its own connection
2353
+ // with a size-scaled budget instead of loosening the budget for every
2354
+ // command on the shared connection.
2355
+ const budgetMs = Math.max(60_000, Math.ceil(text.length / 1_000) * 1_000);
2356
+ const slowCdp = page ? await connectCdp(page.webSocketDebuggerUrl, budgetMs) : undefined;
2357
+ try {
2358
+ const target = slowCdp ?? cdp;
2359
+ if (slowCdp)
2360
+ await slowCdp.send("Runtime.enable");
2361
+ const inserted = await target.evaluate(insertComposerTextInPageExpression(text));
2362
+ if (!inserted?.ok)
2363
+ throw new Error(inserted?.reason ?? "Could not insert the prompt into the ChatGPT composer");
2364
+ }
2365
+ finally {
2366
+ slowCdp?.close();
2367
+ }
2309
2368
  }
2310
2369
  await sleep(200);
2311
2370
  const state = await cdp.evaluate(composerTextStateExpression(text));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youdie006/prodex",
3
- "version": "0.19.1",
3
+ "version": "0.19.2",
4
4
  "description": "Local receipt bus for coordinating Codex execution with ChatGPT Pro/Projects consultation.",
5
5
  "author": "youdie006",
6
6
  "license": "MIT",