@youdie006/prodex 0.40.0 → 0.40.1

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.
@@ -1602,6 +1602,18 @@ async function reloadPageAndAwaitComposer(page) {
1602
1602
  cdp.close();
1603
1603
  }
1604
1604
  }
1605
+ /**
1606
+ * The project a ChatGPT URL belongs to, as its `g-p-<id>` segment.
1607
+ *
1608
+ * The rebind check used to compare whole URLs, which also carry mode in a
1609
+ * query string - and mode is exactly what differs across the reload this
1610
+ * failure showed up in. Identity is the thing the check actually cares about:
1611
+ * that the composer came back bound to THIS project rather than the one the
1612
+ * tab came from.
1613
+ */
1614
+ export function projectIdentity(url) {
1615
+ return /\/g\/(g-p-[^/?#]+)/.exec(url)?.[1];
1616
+ }
1605
1617
  export function powerSliderPresentExpression() {
1606
1618
  return `Boolean(document.querySelector('[data-testid="composer-intelligence-picker-content"] [role="slider"]'))`;
1607
1619
  }
@@ -2524,12 +2536,25 @@ async function selectProject(cdp, options) {
2524
2536
  // prompt posted into the project the tab came from). A hard reload of the
2525
2537
  // project home rebinds the composer to THIS project before we send.
2526
2538
  const projectHome = await cdp.evaluate("location.href");
2539
+ const home = projectIdentity(projectHome);
2527
2540
  // Polled with no delay, the first check could run before the reload had
2528
2541
  // committed, on the old document, whose composer and URL both still
2529
2542
  // matched. The stamp keeps that document from passing as the new one.
2530
- const rebound = await reloadAndAwaitComposer(cdp, RELOAD_SETTLE_TIMEOUT_MS, `location.href === ${JSON.stringify(projectHome)}`);
2543
+ //
2544
+ // Identity, not the whole URL: the href also carries mode in a query
2545
+ // string, and comparing it byte-for-byte failed a reload that had landed
2546
+ // exactly where it was asked to.
2547
+ const rebound = await reloadAndAwaitComposer(cdp, RELOAD_SETTLE_TIMEOUT_MS, home ? `/\\/g\\/${home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![^/?#])/.test(location.href)` : "true");
2531
2548
  if (!rebound) {
2532
- throw new Error(`ChatGPT composer did not rebind after entering project "${options.project}"`);
2549
+ // Say what it saw. This failure is rare and was previously reported as
2550
+ // four words, which named neither the cause nor anything to try.
2551
+ const landed = await cdp.evaluate("location.href").catch(() => "unknown");
2552
+ const hasComposer = await cdp
2553
+ .evaluate(`Boolean(document.querySelector('#prompt-textarea,[contenteditable="true"],textarea'))`)
2554
+ .catch(() => false);
2555
+ throw new Error(`ChatGPT composer did not rebind after entering project "${options.project}": after reloading, the tab was on ` +
2556
+ `${projectIdentity(landed) ?? "no project"} (expected ${home ?? "that project"}) and the composer was ` +
2557
+ `${hasComposer ? "present but the page never settled" : "still missing"}.`);
2533
2558
  }
2534
2559
  }
2535
2560
  const composerReady = await waitForExpressionTrue(cdp, `Boolean(document.querySelector('#prompt-textarea,[contenteditable="true"],textarea'))`, PROJECT_NAVIGATION_TIMEOUT_MS);
package/dist/cli-pro.js CHANGED
@@ -1110,6 +1110,12 @@ export async function runAskProCommand(rest, io) {
1110
1110
  // Requiring --new-chat keeps that explicit rather than quietly turning a
1111
1111
  // continuation into a throwaway.
1112
1112
  const temporary = parsedAskPro.optionArgs.includes("--temporary");
1113
+ const temporaryConflict = temporaryProjectConflict({
1114
+ temporary,
1115
+ ...(explicitProject !== undefined ? { explicitProject } : {})
1116
+ });
1117
+ if (temporaryConflict)
1118
+ throw new Error(temporaryConflict);
1113
1119
  if (temporary && !newChat) {
1114
1120
  throw new Error("--temporary starts a throwaway chat, so it needs --new-chat. A temporary chat cannot be continued or recovered later.");
1115
1121
  }
@@ -1141,9 +1147,17 @@ export async function runAskProCommand(rest, io) {
1141
1147
  // fresh chat inside the project is exactly what "--new-chat + project"
1142
1148
  // produces, and the whole point of pinning a default project is that
1143
1149
  // consults stop landing in the general chat list. Only --target-url
1144
- // (pinned tab) and --project-new suppress it.
1150
+ // (pinned tab), --project-new, and --temporary suppress it.
1151
+ //
1152
+ // --temporary suppresses rather than conflicts: a pinned project must not
1153
+ // turn every throwaway send into an error, and attempting both is what
1154
+ // produced "composer did not rebind after entering project" - a temporary
1155
+ // chat is never saved, a project chat is, and entering a project leaves
1156
+ // temporary mode.
1145
1157
  const selectionProject = explicitProject ??
1146
- (normalizedTargetUrl || selectionProjectNew !== undefined || suppressProject ? undefined : browserDefaults?.project);
1158
+ (normalizedTargetUrl || selectionProjectNew !== undefined || suppressProject || temporary
1159
+ ? undefined
1160
+ : browserDefaults?.project);
1147
1161
  const reasoningAxisChosen = explicitProMode !== undefined || explicitEffort !== undefined;
1148
1162
  const selectionProMode = explicitProMode ?? (reasoningAxisChosen ? undefined : browserDefaults?.pro_mode);
1149
1163
  const selectionEffort = explicitEffort ?? (reasoningAxisChosen ? undefined : browserDefaults?.effort);
@@ -1796,6 +1810,19 @@ export function proSelectionVerified(selection) {
1796
1810
  return undefined;
1797
1811
  return /pro/i.test(selection.modelSlug);
1798
1812
  }
1813
+ /**
1814
+ * A temporary chat is not saved; a project chat is. Entering a project leaves
1815
+ * temporary mode, so asking for both is asking for two different things, and
1816
+ * prodex used to attempt both and die inside the project step with "composer
1817
+ * did not rebind". A pinned default project is suppressed instead of refused:
1818
+ * the per-call flag is the more specific instruction.
1819
+ */
1820
+ export function temporaryProjectConflict(input) {
1821
+ if (!input.temporary || input.explicitProject === undefined)
1822
+ return undefined;
1823
+ return (`--temporary and --project cannot be combined: a temporary chat is never saved, and a chat inside a project is. ` +
1824
+ `Drop --temporary to send into "${input.explicitProject}", or drop --project to send a throwaway chat.`);
1825
+ }
1799
1826
  export function browserSendBlockerFromError(error) {
1800
1827
  const blocker = typeof error === "object" && error !== null && "blocker" in error ? error.blocker : undefined;
1801
1828
  if (typeof blocker === "object" &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youdie006/prodex",
3
- "version": "0.40.0",
3
+ "version": "0.40.1",
4
4
  "description": "Local receipt bus for coordinating Codex execution with ChatGPT Pro/Projects consultation.",
5
5
  "author": "youdie006",
6
6
  "license": "MIT",