@youdie006/prodex 0.40.26 → 0.40.28
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/dist/chatgpt-browser.js +57 -8
- package/docs/releasing.md +98 -0
- package/package.json +1 -1
package/dist/chatgpt-browser.js
CHANGED
|
@@ -191,7 +191,40 @@ export const CHATGPT_BLOCKER_SCAN_EXCLUDED_ANCESTORS = `${'[data-message-author-
|
|
|
191
191
|
* sat in the thread for ten minutes while prodex waited for it. The model slug
|
|
192
192
|
* attribute went with it; where it is absent the model is simply not known.
|
|
193
193
|
*/
|
|
194
|
-
|
|
194
|
+
/**
|
|
195
|
+
* A message's text with its source pills spelled out.
|
|
196
|
+
*
|
|
197
|
+
* Measured 2026-10-06: ChatGPT renders each source as an inline pill
|
|
198
|
+
* ([data-chatgpt-copy-reference]) and innerText puts its label on a line of
|
|
199
|
+
* its own, so a page-read answer came back as "walnut-9" plus a line
|
|
200
|
+
* "cont-att", or ended in a bare "blog.rust-lang.org" that read as part of
|
|
201
|
+
* the answer. A web source becomes " [label](url)", as the transcript reader
|
|
202
|
+
* writes it; a file source has no link and becomes " [label]". The label is
|
|
203
|
+
* looked for on its own line first, so the same words earlier in the prose
|
|
204
|
+
* are left alone.
|
|
205
|
+
*/
|
|
206
|
+
export const CHATGPT_RENDERED_MESSAGE_TEXT_JS = `
|
|
207
|
+
const renderedMessageText = (root) => {
|
|
208
|
+
let text = root.innerText || "";
|
|
209
|
+
const newline = String.fromCharCode(10);
|
|
210
|
+
const isSpace = (ch) => ch === " " || ch === newline || ch === String.fromCharCode(9) || ch === String.fromCharCode(13);
|
|
211
|
+
let cursor = 0;
|
|
212
|
+
for (const pill of [...root.querySelectorAll('[data-chatgpt-copy-reference]')]) {
|
|
213
|
+
const label = (pill.innerText || "").trim();
|
|
214
|
+
if (!label) continue;
|
|
215
|
+
const onOwnLine = text.indexOf(newline + label, cursor);
|
|
216
|
+
const at = onOwnLine >= 0 ? onOwnLine + 1 : text.indexOf(label, cursor);
|
|
217
|
+
if (at < 0) continue;
|
|
218
|
+
let start = at;
|
|
219
|
+
while (start > cursor && isSpace(text[start - 1])) start -= 1;
|
|
220
|
+
const link = pill.querySelector('a[href^="http"]');
|
|
221
|
+
const rendered = link ? " [" + label + "](" + link.getAttribute("href") + ")" : " [" + label + "]";
|
|
222
|
+
text = text.slice(0, start) + rendered + text.slice(at + label.length);
|
|
223
|
+
cursor = start + rendered.length;
|
|
224
|
+
}
|
|
225
|
+
return text;
|
|
226
|
+
};`;
|
|
227
|
+
const CHATGPT_MESSAGE_NODES_JS = `${CHATGPT_RENDERED_MESSAGE_TEXT_JS}
|
|
195
228
|
// ChatGPT keeps a previously opened conversation mounted but hidden
|
|
196
229
|
// (measured 2026-10-06: two turns without a box on a project home), and
|
|
197
230
|
// counting them made a fresh chat look like an existing conversation.
|
|
@@ -811,8 +844,10 @@ async function navigateIdleChatGptPage(page, url) {
|
|
|
811
844
|
* navigation cannot leave the old thread's state in place. A false result
|
|
812
845
|
* must not be used as permission to send into stale content.
|
|
813
846
|
*/
|
|
814
|
-
// Measured 5.2-8.5 s for the new-chat page to settle (2026-09-30)
|
|
815
|
-
|
|
847
|
+
// Measured 5.2-8.5 s for the new-chat page to settle (2026-09-30), and over
|
|
848
|
+
// 15 s for back-to-back sends with the host at load average 28-32 on 8 cores
|
|
849
|
+
// (2026-10-06). It is only a ceiling: a ready page returns at once.
|
|
850
|
+
const FRESH_CHAT_TIMEOUT_MS = 30_000;
|
|
816
851
|
/** Poll until the document marked before a navigation has been replaced and the new one has a composer. */
|
|
817
852
|
async function waitForReloadedDocument(page, deadline) {
|
|
818
853
|
while (Date.now() < deadline) {
|
|
@@ -1717,6 +1752,13 @@ async function waitForExpressionTrue(cdp, expression, timeoutMs, intervalMs = 15
|
|
|
1717
1752
|
const MENU_OPEN_TIMEOUT_MS = 5_000;
|
|
1718
1753
|
const MENU_SETTLE_TIMEOUT_MS = 5_000;
|
|
1719
1754
|
const PROJECT_NAVIGATION_TIMEOUT_MS = 8_000;
|
|
1755
|
+
/**
|
|
1756
|
+
* How long the composer's own controls (model selector, tools button, file
|
|
1757
|
+
* input) may take to appear after the editor. Measured over 8 s for the model
|
|
1758
|
+
* selector on back-to-back sends with the host at load average 24-44
|
|
1759
|
+
* (2026-10-06). It is only a ceiling: a rendered control returns at once.
|
|
1760
|
+
*/
|
|
1761
|
+
const COMPOSER_RENDER_TIMEOUT_MS = 30_000;
|
|
1720
1762
|
export function menuOpenExpression() {
|
|
1721
1763
|
return `Boolean(document.querySelector('[data-testid="composer-intelligence-picker-content"],[role="menu"]:has([role="slider"])'))`;
|
|
1722
1764
|
}
|
|
@@ -2813,7 +2855,7 @@ async function selectModelReasoning(cdp, options, selectionWarnings = []) {
|
|
|
2813
2855
|
// the same pattern succeeded either side of it. The neighbouring waits for
|
|
2814
2856
|
// the same kind of render already allow six to eight.
|
|
2815
2857
|
let button = { ok: false };
|
|
2816
|
-
const buttonDeadline = Date.now() +
|
|
2858
|
+
const buttonDeadline = Date.now() + COMPOSER_RENDER_TIMEOUT_MS;
|
|
2817
2859
|
for (;;) {
|
|
2818
2860
|
button = await cdp.evaluate(modelButtonRectExpression());
|
|
2819
2861
|
if (button.ok && button.x !== undefined && button.y !== undefined)
|
|
@@ -3437,6 +3479,14 @@ export async function recoverChatGptAnswerFromThread(options) {
|
|
|
3437
3479
|
lastAnswer = "";
|
|
3438
3480
|
continue;
|
|
3439
3481
|
}
|
|
3482
|
+
// A thread that is still loading already has the target URL but no
|
|
3483
|
+
// turns (measured 2026-10-06), and judging the request on it refused a
|
|
3484
|
+
// recovery of the very request it was asked for.
|
|
3485
|
+
if (state.userMessageCount === 0) {
|
|
3486
|
+
stableRuns = 0;
|
|
3487
|
+
lastAnswer = "";
|
|
3488
|
+
continue;
|
|
3489
|
+
}
|
|
3440
3490
|
if (options.requestId && !chatGptRequestMarkerMatches(state.lastUserText ?? "", options.requestId)) {
|
|
3441
3491
|
throw new ChatGptBrowserBlockerError({
|
|
3442
3492
|
code: "request_mismatch",
|
|
@@ -5115,7 +5165,6 @@ export function insertComposerTextInPageExpression(text) {
|
|
|
5115
5165
|
export function composerFileInputSelector() {
|
|
5116
5166
|
return 'input[type="file"]:not([accept*="image"])';
|
|
5117
5167
|
}
|
|
5118
|
-
const COMPOSER_FILE_INPUT_TIMEOUT_MS = 15_000;
|
|
5119
5168
|
const COMPOSER_TOOLS_MENU_ATTEMPTS = 3;
|
|
5120
5169
|
export const PRODEX_ATTACH_INPUT_ATTRIBUTE = "data-prodex-attach-input";
|
|
5121
5170
|
/**
|
|
@@ -5238,7 +5287,7 @@ export async function attachFilesToComposer(cdp, absolutePaths, options = {}) {
|
|
|
5238
5287
|
// this point in that window sometimes. Wait for the input itself, before
|
|
5239
5288
|
// the chip check (chips render with it) and before reading the DOM tree.
|
|
5240
5289
|
const waitForComposerFileInput = async () => {
|
|
5241
|
-
const inputDeadline = Date.now() +
|
|
5290
|
+
const inputDeadline = Date.now() + COMPOSER_RENDER_TIMEOUT_MS;
|
|
5242
5291
|
for (;;) {
|
|
5243
5292
|
try {
|
|
5244
5293
|
if (await cdp.evaluate(markComposerFileInputExpression()) === true)
|
|
@@ -5463,7 +5512,7 @@ export async function enableComposerTools(cdp, labels) {
|
|
|
5463
5512
|
// The server-rendered editor shows before the app attaches the composer's
|
|
5464
5513
|
// controls (measured 2026-10-06), so a first miss is usually just early.
|
|
5465
5514
|
let button = await cdp.evaluate(composerToolsButtonRectExpression());
|
|
5466
|
-
const buttonDeadline = Date.now() +
|
|
5515
|
+
const buttonDeadline = Date.now() + COMPOSER_RENDER_TIMEOUT_MS;
|
|
5467
5516
|
while ((!button.ok || button.x === undefined || button.y === undefined) && Date.now() < buttonDeadline) {
|
|
5468
5517
|
await sleep(500);
|
|
5469
5518
|
button = await cdp.evaluate(composerToolsButtonRectExpression());
|
|
@@ -6019,7 +6068,7 @@ export function answerExpression() {
|
|
|
6019
6068
|
const tagged = node.closest('[data-message-model-slug]');
|
|
6020
6069
|
if (tagged) modelSlug = tagged.getAttribute('data-message-model-slug') || undefined;
|
|
6021
6070
|
}
|
|
6022
|
-
return { role, node, text: textNode
|
|
6071
|
+
return { role, node, text: renderedMessageText(textNode), modelSlug };
|
|
6023
6072
|
});
|
|
6024
6073
|
const assistantMessages = messages.filter((message) => message.role === "assistant");
|
|
6025
6074
|
const userMessages = messages.filter((message) => message.role === "user");
|
package/docs/releasing.md
CHANGED
|
@@ -16,6 +16,104 @@ Release target: `v0.40.13` on `main`. The GitHub Release records the resolved co
|
|
|
16
16
|
|
|
17
17
|
The release adds a guarded, opt-in transition and more accurate blocker reporting. It does not claim that headless Pro consultation is verified on either deployment target or that authentication can be retained indefinitely.
|
|
18
18
|
|
|
19
|
+
## Browser Container Rebuild 2026-10-06 (afternoon)
|
|
20
|
+
|
|
21
|
+
**Patches now committed**
|
|
22
|
+
- The patches the container runs on top of `feat/headless-browser-compatibility` `b5eac7d` are now committed on their own branch, `container/redesign-patches`:
|
|
23
|
+
- `0c3a9cc`: every patch from 2026-09-30 to 2026-10-06.
|
|
24
|
+
- `1233603`: the 30 s composer-render ceiling from this change.
|
|
25
|
+
- They had lived only in a scratch worktree.
|
|
26
|
+
- `feat/headless-browser-compatibility` itself is unchanged.
|
|
27
|
+
|
|
28
|
+
**Image**
|
|
29
|
+
- `sha256:ce0240c0e8a4` was built from `1233603`, and the branch suite passed (2002).
|
|
30
|
+
- The swap was gated on the send lock being free.
|
|
31
|
+
|
|
32
|
+
**Verified**
|
|
33
|
+
- Three back-to-back `--model Pro --pro-mode` new-chat sends answered (42-50 s each).
|
|
34
|
+
- Before this, a real consult from another session had been refused as `composer_not_ready` (`task_20261006_050125`) under host load average 24-44.
|
|
35
|
+
|
|
36
|
+
**Host load**
|
|
37
|
+
- The load came from an unrelated container, `apo-neo4j`, which had restarted 57,058 times because it could not resolve its own hostname. The user is handling it.
|
|
38
|
+
|
|
39
|
+
## 0.40.27 Release Record
|
|
40
|
+
|
|
41
|
+
Release commit `f84ce2b` on `main` (cut after PR #17, merge `9dad219`); tag `v0.40.27`.
|
|
42
|
+
|
|
43
|
+
**Publishing**
|
|
44
|
+
- `publish.yml` run 37419470087 passed all six platforms on the first attempt, then npm publish with SLSA v1 provenance and the GitHub Release (2026-10-06 05:57 UTC).
|
|
45
|
+
- `npm run release:verify` on the release commit passed on its second run. The first failed in package smoke, where the fake DevTools endpoint did not answer within 500 ms while the host load average was about 30 (8 cores).
|
|
46
|
+
|
|
47
|
+
**What it fixes**
|
|
48
|
+
- Source pills in page-read answers are spelled out as markdown links or `[label]`.
|
|
49
|
+
- Recovery waits for the thread's turns instead of refusing its own request right after navigating.
|
|
50
|
+
|
|
51
|
+
**Installation on 2026-10-06**
|
|
52
|
+
- Global npm, each verified with `prodex --version` = 0.40.27.
|
|
53
|
+
- WSL, M3, Mac mini: installed. Running services were not restarted: 5 stdio MCP servers on WSL and 4 on M3 keep older code until their clients reconnect. The Mac mini had none running.
|
|
54
|
+
- M4: not reachable (hostname does not resolve); not updated.
|
|
55
|
+
|
|
56
|
+
**Live check after install (installed 0.40.27, shared WSL browser)**
|
|
57
|
+
- All answered:
|
|
58
|
+
- a new-chat attachment
|
|
59
|
+
- a web-search answer, ending in a markdown source link
|
|
60
|
+
- a project send
|
|
61
|
+
- `pro browser recover` with the tab on another conversation, which returned the answer
|
|
62
|
+
|
|
63
|
+
**Browser container, same day**
|
|
64
|
+
- The pill and recovery fixes were adapted to the branch code.
|
|
65
|
+
- A consult from another session (task `task_20261006_050125`, 05:01 UTC) had been refused with `composer_not_ready`. Back-to-back `--model Pro` new-chat sends reproduced a related failure, `fresh_chat_not_ready`: the new-chat page took more than 15 s to settle with the host load average at 28-32. The branch's new-chat ceiling was raised to 30 s. It is only a ceiling.
|
|
66
|
+
- Image `sha256:2b76266ad524`. The earlier interrupted-looking consult `task_20261006_050200` had finished at 05:07, before the swap.
|
|
67
|
+
- The swap was gated on the send lock being free.
|
|
68
|
+
- Two branch tests failed in the pre-build run, and the build script did not stop on test failures, so the image was built and swapped anyway. One test advanced a fake clock 25 s against the new 30 s ceiling; it was fixed. The other was a load-sensitive project-sidebar test, which passed on rerun. After that, the full branch suite passed (2002). The image's runtime code is the code that passed.
|
|
69
|
+
- Verified: three back-to-back `--model Pro --pro-mode` new-chat sends answered (44-50 s each).
|
|
70
|
+
|
|
71
|
+
## 0.40.26 Release Record
|
|
72
|
+
|
|
73
|
+
Release commit `b413c24` on `main` (cut after PR #16, merge `af5cf20`); tag `v0.40.26`.
|
|
74
|
+
|
|
75
|
+
**Publishing**
|
|
76
|
+
- `publish.yml` run 37405732176 passed all six platforms on the first attempt, then npm publish with SLSA v1 provenance and the GitHub Release (2026-10-06 03:07 UTC).
|
|
77
|
+
- PR #16's own CI needed one rerun on macOS Intel for an unrelated 30 s timeout in `tests/mcp-consult.test.ts`.
|
|
78
|
+
- `npm run release:verify` passed on the release commit after a build.
|
|
79
|
+
|
|
80
|
+
**What it fixes**
|
|
81
|
+
- The ChatGPT page measured on 2026-10-06 broke several flows:
|
|
82
|
+
- Every project send was refused because a hidden, still-mounted conversation was counted.
|
|
83
|
+
- Attachments did not recognize timestamp-renamed uploads, picked a hidden composer form, and ran before the file input existed.
|
|
84
|
+
- Leftover chips were no longer detected.
|
|
85
|
+
- Composer tools were looked up or clicked before they worked.
|
|
86
|
+
- Found by a live matrix against the installed 0.40.25, where `--project` sends, project attachments, new-chat attachments after a repeat upload and `--tool create-image` failed.
|
|
87
|
+
|
|
88
|
+
**Installation on 2026-10-06**
|
|
89
|
+
- Global npm, each verified with `prodex --version` = 0.40.26.
|
|
90
|
+
- WSL, M3, Mac mini: installed. Running services were not restarted: 11 stdio MCP servers on WSL and 6 on M3 keep older code until their clients reconnect. The Mac mini had none running.
|
|
91
|
+
- M4: not reachable (hostname does not resolve); not updated.
|
|
92
|
+
|
|
93
|
+
**Live check after install (installed 0.40.26, shared WSL browser)**
|
|
94
|
+
All eight answered:
|
|
95
|
+
- `--project` plain and with `--attach`
|
|
96
|
+
- `--tool create-image`
|
|
97
|
+
- `--new-chat --attach`
|
|
98
|
+
- `--continue --attach`
|
|
99
|
+
- `--tool web-search --attach`
|
|
100
|
+
- `--temporary --attach`
|
|
101
|
+
- plain `--continue`
|
|
102
|
+
|
|
103
|
+
**Browser container, same day**
|
|
104
|
+
- Before the rebuild, the container build failed the same way: a project send was refused and a new-chat attachment timed out. Its tab then stopped answering CDP.
|
|
105
|
+
- The same fixes were adapted to the branch code, where the transcript reader and the hover-verified tools click differ. The image `sha256:8e5734ee733c` was rebuilt from `b5eac7d` with all patches so far. These are still uncommitted, and nothing was pushed to `feat/headless-browser-compatibility`.
|
|
106
|
+
- Branch suite: 1998 passed. The swap was gated on the send lock being free.
|
|
107
|
+
- Verified in the container, all seven answered:
|
|
108
|
+
- a project send
|
|
109
|
+
- a new-chat attachment
|
|
110
|
+
- an attachment on a continued thread
|
|
111
|
+
- a project attachment
|
|
112
|
+
- create-image
|
|
113
|
+
- web search with an attachment
|
|
114
|
+
- a temporary chat with an attachment
|
|
115
|
+
- An MCP client reconnect was needed after the swap.
|
|
116
|
+
|
|
19
117
|
## 0.40.25 Release Record
|
|
20
118
|
|
|
21
119
|
Release commit `3c66da3` on `main` (cut after PR #15, merge `9bd908c`); tag `v0.40.25`.
|