@youdie006/prodex 0.40.46 → 0.40.47
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 +39 -11
- package/docs/releasing.md +20 -0
- package/package.json +1 -1
package/dist/chatgpt-browser.js
CHANGED
|
@@ -315,11 +315,21 @@ export const CHATGPT_RENDERED_MESSAGE_TEXT_JS = `
|
|
|
315
315
|
// The renderer web-search answers use since 2026-10-10 marks a source as a
|
|
316
316
|
// badge inside a popover trigger; its urls only exist once it is opened.
|
|
317
317
|
const isSourceBadge = (node) => attr(node, "data-d-component") === "popover-trigger" && findAll(node, (n) => attr(n, "data-pill") !== null).length > 0;
|
|
318
|
+
// ChatGPT's link component appends utm_source=chatgpt.com to every link
|
|
319
|
+
// it renders (measured 2026-10-28), a parameter the answer never wrote.
|
|
320
|
+
const untracked = (href) => {
|
|
321
|
+
const hashAt = href.indexOf("#");
|
|
322
|
+
const base = hashAt === -1 ? href : href.slice(0, hashAt);
|
|
323
|
+
const queryAt = base.indexOf("?");
|
|
324
|
+
if (queryAt === -1) return href;
|
|
325
|
+
const params = base.slice(queryAt + 1).split("&").filter((param) => param !== "utm_source=chatgpt.com");
|
|
326
|
+
return base.slice(0, queryAt) + (params.length > 0 ? "?" + params.join("&") : "") + (hashAt === -1 ? "" : href.slice(hashAt));
|
|
327
|
+
};
|
|
318
328
|
const pill = (node) => {
|
|
319
329
|
const label = collapse(node.textContent || "").trim();
|
|
320
330
|
if (!label) return "";
|
|
321
331
|
const link = findAll(node, (n) => tagOf(n) === "a" && (attr(n, "href") || "").startsWith("http"))[0];
|
|
322
|
-
return link ? "[" + label + "](" + attr(link, "href") + ")" : "[" + label + "]";
|
|
332
|
+
return link ? "[" + label + "](" + untracked(attr(link, "href")) + ")" : "[" + label + "]";
|
|
323
333
|
};
|
|
324
334
|
const inline = (node, keep) => {
|
|
325
335
|
const keepLines = keepsLineBreaks(node, keep === true);
|
|
@@ -344,13 +354,25 @@ export const CHATGPT_RENDERED_MESSAGE_TEXT_JS = `
|
|
|
344
354
|
if (attr(node, "data-markdown-copy") === "inline-code" || tag === "code") return BT + (node.textContent || "") + BT;
|
|
345
355
|
if (tag === "br") return NL;
|
|
346
356
|
const inner = inline(node, keep);
|
|
347
|
-
// The newer renderer marks bold as span[data-d-default-strong]
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
357
|
+
// The newer renderer marks bold as span[data-d-default-strong], italic
|
|
358
|
+
// as span[data-d-font-style=italic] and strikethrough as
|
|
359
|
+
// span[data-d-text-decoration=line-through] (measured 2026-10-28; only
|
|
360
|
+
// bold was read, so the other two came back as plain words). One
|
|
361
|
+
// element can carry several, so each applies in turn.
|
|
362
|
+
const strong = tag === "strong" || tag === "b" || attr(node, "data-d-default-strong") !== null;
|
|
363
|
+
const italic = tag === "em" || tag === "i" || attr(node, "data-d-font-style") === "italic";
|
|
364
|
+
const struck = tag === "del" || tag === "s" || (attr(node, "data-d-text-decoration") || "").includes("line-through");
|
|
365
|
+
if (strong || italic || struck) {
|
|
366
|
+
let marked = inner.trim();
|
|
367
|
+
if (!marked) return "";
|
|
368
|
+
if (strong) marked = "**" + marked + "**";
|
|
369
|
+
if (italic) marked = "*" + marked + "*";
|
|
370
|
+
if (struck) marked = "~~" + marked + "~~";
|
|
371
|
+
return marked;
|
|
372
|
+
}
|
|
351
373
|
if (tag === "a") {
|
|
352
374
|
const href = attr(node, "href") || "";
|
|
353
|
-
return href.startsWith("http") && inner.trim() ? "[" + inner.trim() + "](" + href + ")" : inner;
|
|
375
|
+
return href.startsWith("http") && inner.trim() ? "[" + inner.trim() + "](" + untracked(href) + ")" : inner;
|
|
354
376
|
}
|
|
355
377
|
return inner;
|
|
356
378
|
};
|
|
@@ -6281,9 +6303,8 @@ export function pickLandedConversation(candidates, sentPrompt) {
|
|
|
6281
6303
|
* nothing is the safe answer here.
|
|
6282
6304
|
*/
|
|
6283
6305
|
export function transcriptContainsWholeSentPrompt(userText, sentPrompt) {
|
|
6284
|
-
const normalize = (value) => value
|
|
6306
|
+
const normalize = (value) => undoMarkdownEscapes(value)
|
|
6285
6307
|
.replace(AUTOLINKED_URL_PATTERN, "$1")
|
|
6286
|
-
.replace(/\\([\\`*_{}[\]()#+\-.!>~|])/g, "$1")
|
|
6287
6308
|
.replace(/\s+/g, " ")
|
|
6288
6309
|
.trim();
|
|
6289
6310
|
const seen = normalize(userText);
|
|
@@ -6298,6 +6319,14 @@ const NORMALIZED_PROMPT_MATCH_CHARS = 120;
|
|
|
6298
6319
|
// halves differently ("Function\_(x)" vs "Function_\(x\)", measured
|
|
6299
6320
|
// 2026-10-21), so the pattern is applied after markdown escapes are undone.
|
|
6300
6321
|
const AUTOLINKED_URL_PATTERN = /\[(https?:\/\/[^\]\s]+)\]\(\1\)/g;
|
|
6322
|
+
// The composer escapes more than the markdown it would otherwise misread: an
|
|
6323
|
+
// autolinked url's href kept "\\&" where its text had "&" (measured
|
|
6324
|
+
// 2026-10-28). Any ASCII punctuation can carry a markdown escape, so every such
|
|
6325
|
+
// escape is undone, on both sides alike.
|
|
6326
|
+
const MARKDOWN_ESCAPE_PATTERN = /\\([!-\/:-@[-`{-~])/g;
|
|
6327
|
+
function undoMarkdownEscapes(value) {
|
|
6328
|
+
return value.replace(MARKDOWN_ESCAPE_PATTERN, "$1");
|
|
6329
|
+
}
|
|
6301
6330
|
/**
|
|
6302
6331
|
* ChatGPT displays a sent turn's HTML entities decoded (measured 2026-10-19:
|
|
6303
6332
|
* "& <b>" was shown as "& <b>"), so both sides are compared decoded.
|
|
@@ -6314,7 +6343,7 @@ function decodeHtmlEntities(value) {
|
|
|
6314
6343
|
});
|
|
6315
6344
|
}
|
|
6316
6345
|
function normalizeChatGptPromptText(value) {
|
|
6317
|
-
const unescaped = decodeHtmlEntities(value)
|
|
6346
|
+
const unescaped = undoMarkdownEscapes(decodeHtmlEntities(value)).replace(AUTOLINKED_URL_PATTERN, "$1");
|
|
6318
6347
|
const renderedFences = unescaped.replace(/(^|\r?\n)```text[ \t]*\r?\n([\s\S]*?)\r?\n```(?=\r?\n|$)/g, "$1text\n$2");
|
|
6319
6348
|
return renderedFences.replace(/\s+/g, " ").trim();
|
|
6320
6349
|
}
|
|
@@ -6345,8 +6374,7 @@ export function chatGptRequestMatchesUserTurn(userText, sentPrompt, requestId) {
|
|
|
6345
6374
|
return withoutFenceLines(userText).includes(withoutFenceLines(sentPrompt));
|
|
6346
6375
|
}
|
|
6347
6376
|
function withoutFenceLines(value) {
|
|
6348
|
-
return decodeHtmlEntities(value)
|
|
6349
|
-
.replace(/\\([\\`*_{}[\]()#+\-.!>~|])/g, "$1")
|
|
6377
|
+
return undoMarkdownEscapes(decodeHtmlEntities(value))
|
|
6350
6378
|
.replace(AUTOLINKED_URL_PATTERN, "$1")
|
|
6351
6379
|
.replace(/^[ \t]*```[\w-]*[ \t]*$/gm, "")
|
|
6352
6380
|
.replace(/\s+/g, "");
|
package/docs/releasing.md
CHANGED
|
@@ -35,6 +35,26 @@ The release adds a guarded, opt-in transition and more accurate blocker reportin
|
|
|
35
35
|
**Open**
|
|
36
36
|
- A long-running headless browser ends up behind Cloudflare's check (also noted on WSL, where the Xvfb display is used instead). On macOS there is no virtual display, and a minimized window counts as hidden, so the remaining no-headless option there is a window kept behind others. Changing the headless preference is a design decision that was not made here.
|
|
37
37
|
|
|
38
|
+
## 0.40.46 Release Record
|
|
39
|
+
|
|
40
|
+
Release commit `807f245` on `main` (cut after PRs #69 and #70, last merge `9253ee9`); tag `v0.40.46`. A batch released on the user's go-ahead: terminal-column widths in the interactive picker (#69) and line breaks kept inside answers from ChatGPT's newer `white-space: pre-line` renderer (#70).
|
|
41
|
+
|
|
42
|
+
**Publishing**
|
|
43
|
+
- `npm run release:verify` passed on the release commit after a build, with nothing else running on the machine.
|
|
44
|
+
- `publish.yml` run 40645773352 passed all six platforms, then npm publish with SLSA v1 provenance and the GitHub Release, which lists the two entries from `CHANGELOG.md`.
|
|
45
|
+
|
|
46
|
+
**Installed**
|
|
47
|
+
- WSL, M3, Mac mini: `prodex --version` reports 0.40.46, and each installed `dist/chatgpt-browser.js` carries the line-break reader. Running services were not restarted: no host stdio MCP servers on WSL, 1 on M3, none on the Mac mini.
|
|
48
|
+
- The maintainer watchdog checkout was pulled to `807f245` and rebuilt.
|
|
49
|
+
|
|
50
|
+
**Container**
|
|
51
|
+
- `container/redesign-patches` `5d021d1` ports #69 and `ad24ce2` ports #70 (the #70 cherry-pick conflicted in `src/chatgpt-browser.ts` and the PR diff was applied instead; tsc, build, the suite and `release:verify` pass on it).
|
|
52
|
+
- The images were still not rebuilt: `docker compose build` is refused by the agent's permission classifier on WSL and over ssh on M3. The running WSL container is the #49-#58 port and the M3 container the 0.40.42 port.
|
|
53
|
+
|
|
54
|
+
**Verified**
|
|
55
|
+
- Before the release, on the live tab: the reader in 0.40.45 returned "alpha beta" for an answer showing "alpha" and "beta" on two lines; the #70 build returned "alpha\nbeta" from the same page, with the list after it unchanged.
|
|
56
|
+
- After installing, the tab no longer showed a multi-line answer, so the line-break fix was not re-read live on the installed build; the installed reader ran on the current answer without error.
|
|
57
|
+
|
|
38
58
|
## 0.40.45 Release Record
|
|
39
59
|
|
|
40
60
|
Release commit `5e5f965` on `main` (cut after PRs #59 and #63-#68, last merge `52243f4`); tag `v0.40.45`. A batch released on the user's go-ahead: machine-wide send pacing (#59), accurate advice for a busy tab (#63), word-bounded project-name redaction (#64), the IPv6 loopback message (#65), no cancelling of finished sessions (#66), a blank saved default project stopped before the browser (#67), and the project-delete preview (#68).
|