@youdie006/prodex 0.27.3 → 0.28.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/dist/chatgpt-browser.js +115 -7
- package/package.json +1 -1
package/dist/chatgpt-browser.js
CHANGED
|
@@ -1915,6 +1915,23 @@ async function readTranscriptAnswer(page, conversationId, sentPrompt) {
|
|
|
1915
1915
|
? { classification: "answer", answer: { answer, modelSlug: transcript.modelSlug } }
|
|
1916
1916
|
: { classification: "pending" };
|
|
1917
1917
|
}
|
|
1918
|
+
// A page that has not reported the prompt posting within this long is worth
|
|
1919
|
+
// double-checking against the transcript; the probe is a couple of small fetches.
|
|
1920
|
+
const ACCEPTANCE_TRANSCRIPT_PROBE_AFTER_MS = 20_000;
|
|
1921
|
+
const ACCEPTANCE_TRANSCRIPT_PROBE_EVERY_MS = 10_000;
|
|
1922
|
+
/** Which conversation, if any, already holds the prompt this send posted. */
|
|
1923
|
+
async function findLandedConversation(page, prompt) {
|
|
1924
|
+
try {
|
|
1925
|
+
const candidates = await evaluateOnPage(page, recentConversationsExpression(4), {
|
|
1926
|
+
timeoutMs: 30_000
|
|
1927
|
+
});
|
|
1928
|
+
return pickLandedConversation(candidates ?? [], prompt);
|
|
1929
|
+
}
|
|
1930
|
+
catch {
|
|
1931
|
+
// Transcript unavailable: the caller falls back to the page.
|
|
1932
|
+
return undefined;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1918
1935
|
export async function sendChatGptPrompt(options) {
|
|
1919
1936
|
const port = resolveCdpPort(options.port);
|
|
1920
1937
|
const timeoutMs = options.timeoutMs ?? 90_000;
|
|
@@ -2135,8 +2152,27 @@ export async function sendChatGptPrompt(options) {
|
|
|
2135
2152
|
const acceptDeadline = computePromptAcceptanceDeadline(timeoutMs, started);
|
|
2136
2153
|
let accepted = false;
|
|
2137
2154
|
let finalState;
|
|
2155
|
+
// Seeded either from the url once ChatGPT rewrites it, or - when the page
|
|
2156
|
+
// never showed the prompt post - from the transcript that proves it did.
|
|
2157
|
+
let transcriptConversationId;
|
|
2158
|
+
// Ask the transcript early rather than only at the deadline. Acceptance runs
|
|
2159
|
+
// on the full send budget, so a page that stops reporting the prompt posting
|
|
2160
|
+
// used to burn all twenty minutes before saying anything - while the prompt
|
|
2161
|
+
// sat in a conversation the whole time.
|
|
2162
|
+
let nextTranscriptProbeAt = started + ACCEPTANCE_TRANSCRIPT_PROBE_AFTER_MS;
|
|
2138
2163
|
while (Date.now() < acceptDeadline) {
|
|
2139
2164
|
await sleep(500);
|
|
2165
|
+
if (Date.now() >= nextTranscriptProbeAt) {
|
|
2166
|
+
nextTranscriptProbeAt = Date.now() + ACCEPTANCE_TRANSCRIPT_PROBE_EVERY_MS;
|
|
2167
|
+
const landed = await findLandedConversation(page, options.prompt);
|
|
2168
|
+
if (landed) {
|
|
2169
|
+
transcriptConversationId = landed;
|
|
2170
|
+
accepted = true;
|
|
2171
|
+
sendWarnings.push("prompt_acceptance_unreadable: the page never showed the prompt posting, but the transcript has it - continuing on the conversation the transcript names.");
|
|
2172
|
+
dbgSend(`acceptance recovered from transcript conversation=${landed}`);
|
|
2173
|
+
break;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2140
2176
|
try {
|
|
2141
2177
|
finalState = await evaluateOnPage(page, answerExpression());
|
|
2142
2178
|
}
|
|
@@ -2186,7 +2222,19 @@ export async function sendChatGptPrompt(options) {
|
|
|
2186
2222
|
catch {
|
|
2187
2223
|
// best effort: fall back to submit-button signal only
|
|
2188
2224
|
}
|
|
2189
|
-
|
|
2225
|
+
// Before calling this a failed send: did the prompt actually land? Reading
|
|
2226
|
+
// acceptance off the page means a changed DOM reports "never posted" for a
|
|
2227
|
+
// prompt that posted fine, and the caller's retry asks ChatGPT the same
|
|
2228
|
+
// question twice. The transcript is the ground truth.
|
|
2229
|
+
const landed = await findLandedConversation(page, options.prompt);
|
|
2230
|
+
if (landed) {
|
|
2231
|
+
transcriptConversationId = landed;
|
|
2232
|
+
accepted = true;
|
|
2233
|
+
sendWarnings.push("prompt_acceptance_unreadable: the page never showed the prompt posting, but the transcript has it - continuing on the conversation the transcript names.");
|
|
2234
|
+
dbgSend(`acceptance recovered from transcript conversation=${landed}`);
|
|
2235
|
+
}
|
|
2236
|
+
if (!accepted)
|
|
2237
|
+
throw acceptanceTimeoutError({ timeoutMs, composerStillHasText, submitButtonFound });
|
|
2190
2238
|
}
|
|
2191
2239
|
// Pin the conversation the prompt actually landed in. The browser is shared
|
|
2192
2240
|
// (other agents, the user, tooling), and a tab that moves mid-wait made
|
|
@@ -2199,7 +2247,9 @@ export async function sendChatGptPrompt(options) {
|
|
|
2199
2247
|
// a finished answer was lost: the tab returned to the project page, the url
|
|
2200
2248
|
// still matched the pin taken before ChatGPT rewrote it, and the DOM reader
|
|
2201
2249
|
// sat on zero assistant messages until the budget ran out.
|
|
2202
|
-
|
|
2250
|
+
// Acceptance may already have adopted one from the transcript; otherwise take
|
|
2251
|
+
// it from the url ChatGPT rewrote to.
|
|
2252
|
+
transcriptConversationId ??= pinnedThreadUrl ? conversationIdFromThreadUrl(pinnedThreadUrl) : undefined;
|
|
2203
2253
|
const transcriptResult = (transcript) => {
|
|
2204
2254
|
emitProgress("answered", `transcript (${transcript.answer.length} chars)`);
|
|
2205
2255
|
return {
|
|
@@ -3218,12 +3268,70 @@ export function deepResearchUnreadableBlocker(threadUrl) {
|
|
|
3218
3268
|
};
|
|
3219
3269
|
}
|
|
3220
3270
|
/**
|
|
3221
|
-
* The
|
|
3222
|
-
*
|
|
3223
|
-
*
|
|
3224
|
-
*
|
|
3225
|
-
*
|
|
3271
|
+
* The most recently updated conversations, each with the prompt it opens with.
|
|
3272
|
+
*
|
|
3273
|
+
* Acceptance is otherwise read off the page: if the DOM changes shape, a prompt
|
|
3274
|
+
* that DID post looks like one that never left, the send fails, and the
|
|
3275
|
+
* caller's retry asks ChatGPT the same question twice. The transcript settles
|
|
3276
|
+
* it - the conversation either holds our prompt or it does not.
|
|
3277
|
+
*
|
|
3278
|
+
* Only the head of each prompt is returned: a research transcript runs to
|
|
3279
|
+
* hundreds of KB and none of that is needed to recognise it.
|
|
3226
3280
|
*/
|
|
3281
|
+
export function recentConversationsExpression(limit = 4) {
|
|
3282
|
+
return `(async () => {
|
|
3283
|
+
const out = [];
|
|
3284
|
+
let token = "";
|
|
3285
|
+
try {
|
|
3286
|
+
const session = await fetch("/api/auth/session", { credentials: "include" });
|
|
3287
|
+
if (!session.ok) return out;
|
|
3288
|
+
const parsed = await session.json();
|
|
3289
|
+
token = (parsed && parsed.accessToken) || "";
|
|
3290
|
+
} catch (error) {
|
|
3291
|
+
return out;
|
|
3292
|
+
}
|
|
3293
|
+
const headers = token ? { Authorization: "Bearer " + token } : {};
|
|
3294
|
+
let items = [];
|
|
3295
|
+
try {
|
|
3296
|
+
const response = await fetch("/backend-api/conversations?offset=0&limit=${limit}&order=updated", {
|
|
3297
|
+
credentials: "include",
|
|
3298
|
+
headers
|
|
3299
|
+
});
|
|
3300
|
+
if (!response.ok) return out;
|
|
3301
|
+
const listed = await response.json();
|
|
3302
|
+
items = (listed && listed.items) || [];
|
|
3303
|
+
} catch (error) {
|
|
3304
|
+
return out;
|
|
3305
|
+
}
|
|
3306
|
+
for (const item of items) {
|
|
3307
|
+
if (!item || !item.id) continue;
|
|
3308
|
+
try {
|
|
3309
|
+
const response = await fetch("/backend-api/conversation/" + item.id, { credentials: "include", headers });
|
|
3310
|
+
if (!response.ok) continue;
|
|
3311
|
+
const conversation = await response.json();
|
|
3312
|
+
const mapping = (conversation && conversation.mapping) || {};
|
|
3313
|
+
const chain = [];
|
|
3314
|
+
let nodeId = conversation && conversation.current_node;
|
|
3315
|
+
let guard = 0;
|
|
3316
|
+
while (nodeId && mapping[nodeId] && guard < 2000) {
|
|
3317
|
+
guard += 1;
|
|
3318
|
+
if (mapping[nodeId].message) chain.push(mapping[nodeId].message);
|
|
3319
|
+
nodeId = mapping[nodeId].parent;
|
|
3320
|
+
}
|
|
3321
|
+
const user = chain.find((entry) => entry && entry.author && entry.author.role === "user" && entry.content);
|
|
3322
|
+
const text = user ? (user.content.parts || []).filter((part) => typeof part === "string").join("") : "";
|
|
3323
|
+
out.push({ id: item.id, userText: text.slice(0, 600) });
|
|
3324
|
+
} catch (error) {
|
|
3325
|
+
// A conversation we cannot read is simply not a match.
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
return out;
|
|
3329
|
+
})()`;
|
|
3330
|
+
}
|
|
3331
|
+
/** Which of those conversations is the one this send posted into, if any. */
|
|
3332
|
+
export function pickLandedConversation(candidates, sentPrompt) {
|
|
3333
|
+
return candidates.find((candidate) => transcriptMatchesSentPrompt(candidate.userText, sentPrompt))?.id;
|
|
3334
|
+
}
|
|
3227
3335
|
export function transcriptAnswerExpression(conversationId) {
|
|
3228
3336
|
return `(async () => {
|
|
3229
3337
|
const fail = (reason, extra) => Object.assign({ ok: false, reason, status: "", endTurn: false, isComplete: false, text: "", modelSlug: "", references: [], userText: "" }, extra || {});
|