ask-pro 0.1.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/.codex-plugin/plugin.json +30 -0
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/assets/ask-pro_logo.png +0 -0
- package/dist/bin/ask-pro-cli.js +507 -0
- package/dist/scripts/run-cli.js +27 -0
- package/dist/src/ask-pro/atomicWrite.js +26 -0
- package/dist/src/ask-pro/browserRunner.js +796 -0
- package/dist/src/ask-pro/responseZip.js +349 -0
- package/dist/src/ask-pro/session.js +662 -0
- package/dist/src/ask-pro/sessionControllerLease.js +64 -0
- package/dist/src/ask-pro/toon.js +26 -0
- package/dist/src/ask-pro/zip.js +85 -0
- package/dist/src/browser/actions/assistantResponse.js +1245 -0
- package/dist/src/browser/actions/attachmentDataTransfer.js +140 -0
- package/dist/src/browser/actions/attachments.js +1720 -0
- package/dist/src/browser/actions/composerSendReadiness.js +369 -0
- package/dist/src/browser/actions/domEvents.js +31 -0
- package/dist/src/browser/actions/inputGuard.js +52 -0
- package/dist/src/browser/actions/modelPickerDom.js +68 -0
- package/dist/src/browser/actions/modelSelection.js +576 -0
- package/dist/src/browser/actions/navigation.js +510 -0
- package/dist/src/browser/actions/promptComposer.js +824 -0
- package/dist/src/browser/actions/remoteFileTransfer.js +37 -0
- package/dist/src/browser/actions/thinkingStatus.js +408 -0
- package/dist/src/browser/actions/thinkingTime.js +635 -0
- package/dist/src/browser/actions/windowState.js +47 -0
- package/dist/src/browser/attachRunning.js +31 -0
- package/dist/src/browser/chatgptModelCatalog.js +321 -0
- package/dist/src/browser/chromeLifecycle.js +807 -0
- package/dist/src/browser/config.js +110 -0
- package/dist/src/browser/constants.js +85 -0
- package/dist/src/browser/cookies.js +191 -0
- package/dist/src/browser/detect.js +337 -0
- package/dist/src/browser/domDebug.js +72 -0
- package/dist/src/browser/errors.js +20 -0
- package/dist/src/browser/format.js +16 -0
- package/dist/src/browser/index.js +2631 -0
- package/dist/src/browser/language.js +97 -0
- package/dist/src/browser/liveTabs.js +434 -0
- package/dist/src/browser/modelStrategy.js +13 -0
- package/dist/src/browser/pageActions.js +5 -0
- package/dist/src/browser/profilePaths.js +282 -0
- package/dist/src/browser/profileState.js +413 -0
- package/dist/src/browser/providerDomFlow.js +17 -0
- package/dist/src/browser/providers/chatgptDomProvider.js +50 -0
- package/dist/src/browser/reattach.js +534 -0
- package/dist/src/browser/reattachHelpers.js +387 -0
- package/dist/src/browser/utils.js +122 -0
- package/dist/src/browserMode.js +1 -0
- package/dist/src/version.js +39 -0
- package/package.json +114 -0
- package/scripts/refresh-local-plugin.mjs +179 -0
- package/scripts/refresh-local-plugin.ps1 +93 -0
- package/skills/ask-pro/SKILL.md +181 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { INPUT_SELECTORS, SEND_BUTTON_SELECTORS, STOP_BUTTON_SELECTOR, UPLOAD_STATUS_SELECTORS, } from "../constants.js";
|
|
2
|
+
import { buildClickDispatcher } from "./domEvents.js";
|
|
3
|
+
function normalizeToken(value) {
|
|
4
|
+
return value.toLowerCase().replace(/\s+/g, " ").trim();
|
|
5
|
+
}
|
|
6
|
+
function normalizeExpectedName(value) {
|
|
7
|
+
const baseName = value.split("/").pop()?.split("\\").pop() ?? value;
|
|
8
|
+
return normalizeToken(baseName);
|
|
9
|
+
}
|
|
10
|
+
function matchesExpected(raw, expected) {
|
|
11
|
+
if (raw.includes(expected)) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
const expectedNoExt = expected.replace(/\.[a-z0-9]{1,10}$/i, "");
|
|
15
|
+
if (expectedNoExt.length >= 6 && raw.includes(expectedNoExt)) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (raw.includes("…") || raw.includes("...")) {
|
|
19
|
+
const marker = raw.includes("…") ? "…" : "...";
|
|
20
|
+
const [prefixRaw, suffixRaw] = raw.split(marker);
|
|
21
|
+
const prefix = prefixRaw.trim();
|
|
22
|
+
const suffix = suffixRaw.trim();
|
|
23
|
+
const target = expectedNoExt.length >= 6 ? expectedNoExt : expected;
|
|
24
|
+
const matchesPrefix = !prefix || target.includes(prefix);
|
|
25
|
+
const matchesSuffix = !suffix || target.includes(suffix);
|
|
26
|
+
return matchesPrefix && matchesSuffix;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
function buildComposerScopeHelpersExpression() {
|
|
31
|
+
return `
|
|
32
|
+
const sendSelectors = ${JSON.stringify(SEND_BUTTON_SELECTORS)};
|
|
33
|
+
const stopSelector = ${JSON.stringify(STOP_BUTTON_SELECTOR)};
|
|
34
|
+
const promptSelectors = ${JSON.stringify(INPUT_SELECTORS)};
|
|
35
|
+
const attachmentSelectors = [
|
|
36
|
+
'input[type="file"]',
|
|
37
|
+
'[data-testid*="attachment"]',
|
|
38
|
+
'[data-testid*="upload"]',
|
|
39
|
+
'[aria-label*="Remove"]',
|
|
40
|
+
'[aria-label*="remove"]',
|
|
41
|
+
];
|
|
42
|
+
const attachmentChipSelectors = [
|
|
43
|
+
'[data-testid*="chip"]',
|
|
44
|
+
'[data-testid*="attachment"]',
|
|
45
|
+
'[data-testid*="upload"]',
|
|
46
|
+
'[data-testid*="file"]',
|
|
47
|
+
'[aria-label*="Remove"]',
|
|
48
|
+
'button[aria-label*="Remove"]',
|
|
49
|
+
];
|
|
50
|
+
const fileCountSelectors = [
|
|
51
|
+
'button',
|
|
52
|
+
'[role="button"]',
|
|
53
|
+
'[data-testid*="file"]',
|
|
54
|
+
'[data-testid*="upload"]',
|
|
55
|
+
'[data-testid*="attachment"]',
|
|
56
|
+
'[data-testid*="chip"]',
|
|
57
|
+
'[aria-label*="file"]',
|
|
58
|
+
'[title*="file"]',
|
|
59
|
+
'[aria-label*="attachment"]',
|
|
60
|
+
'[title*="attachment"]',
|
|
61
|
+
].join(',');
|
|
62
|
+
const countRegex = /(?:^|\\b)(\\d+)\\s+(?:files?|attachments?)\\b/;
|
|
63
|
+
const isVisible = (node) => {
|
|
64
|
+
if (!(node instanceof HTMLElement)) return false;
|
|
65
|
+
const rect = node.getBoundingClientRect();
|
|
66
|
+
if (rect.width <= 0 || rect.height <= 0) return false;
|
|
67
|
+
const style = window.getComputedStyle(node);
|
|
68
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
69
|
+
};
|
|
70
|
+
const isStopControl = (node) => {
|
|
71
|
+
if (!(node instanceof HTMLElement)) return false;
|
|
72
|
+
const label = [
|
|
73
|
+
node.getAttribute('data-testid') ?? '',
|
|
74
|
+
node.getAttribute('aria-label') ?? '',
|
|
75
|
+
node.getAttribute('title') ?? '',
|
|
76
|
+
node.textContent ?? '',
|
|
77
|
+
].join(' ').toLowerCase();
|
|
78
|
+
return node.matches(stopSelector) || label.includes('stop') || label.includes('cancel');
|
|
79
|
+
};
|
|
80
|
+
const findPromptNode = () => {
|
|
81
|
+
for (const selector of promptSelectors) {
|
|
82
|
+
const nodes = Array.from(document.querySelectorAll(selector));
|
|
83
|
+
for (const node of nodes) {
|
|
84
|
+
if (isVisible(node)) return node;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const selector of promptSelectors) {
|
|
88
|
+
const node = document.querySelector(selector);
|
|
89
|
+
if (node) return node;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
};
|
|
93
|
+
const locateComposerRoot = () => {
|
|
94
|
+
const promptNode = findPromptNode();
|
|
95
|
+
if (promptNode) {
|
|
96
|
+
const initial =
|
|
97
|
+
promptNode.closest('[data-testid*="composer"]') ??
|
|
98
|
+
promptNode.closest('form') ??
|
|
99
|
+
promptNode.parentElement ??
|
|
100
|
+
document.body;
|
|
101
|
+
let current = initial;
|
|
102
|
+
let fallback = initial;
|
|
103
|
+
while (current && current !== document.body) {
|
|
104
|
+
const hasSend = sendSelectors.some((selector) => current.querySelector(selector));
|
|
105
|
+
if (hasSend) {
|
|
106
|
+
fallback = current;
|
|
107
|
+
const hasAttachment = attachmentSelectors.some((selector) => current.querySelector(selector));
|
|
108
|
+
if (hasAttachment) {
|
|
109
|
+
return current;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
current = current.parentElement;
|
|
113
|
+
}
|
|
114
|
+
return fallback ?? initial;
|
|
115
|
+
}
|
|
116
|
+
return document.querySelector('form') ?? document.body;
|
|
117
|
+
};
|
|
118
|
+
const composerRoot = locateComposerRoot();
|
|
119
|
+
const composerScope = (() => {
|
|
120
|
+
if (!composerRoot) return document.body;
|
|
121
|
+
const parent = composerRoot.parentElement;
|
|
122
|
+
const parentHasSend = parent && sendSelectors.some((selector) => parent.querySelector(selector));
|
|
123
|
+
return parentHasSend ? parent : composerRoot;
|
|
124
|
+
})();
|
|
125
|
+
const findSendButton = () => {
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
const candidates = [];
|
|
128
|
+
const scopes = [composerScope, composerRoot, document.body];
|
|
129
|
+
for (const scope of scopes) {
|
|
130
|
+
if (!scope || typeof scope.querySelectorAll !== 'function') continue;
|
|
131
|
+
for (const selector of sendSelectors) {
|
|
132
|
+
for (const node of Array.from(scope.querySelectorAll(selector))) {
|
|
133
|
+
if (!(node instanceof HTMLElement) || seen.has(node)) continue;
|
|
134
|
+
seen.add(node);
|
|
135
|
+
candidates.push(node);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (candidates.length > 0) break;
|
|
139
|
+
}
|
|
140
|
+
const sendCandidates = candidates.filter((node) => !isStopControl(node));
|
|
141
|
+
return sendCandidates.find((node) => isVisible(node)) ?? sendCandidates[0] ?? null;
|
|
142
|
+
};
|
|
143
|
+
const collectAttachmentNodes = () => {
|
|
144
|
+
const nodes = [];
|
|
145
|
+
const seen = new Set();
|
|
146
|
+
for (const selector of attachmentChipSelectors) {
|
|
147
|
+
for (const node of Array.from(composerScope.querySelectorAll(selector))) {
|
|
148
|
+
if (!node || seen.has(node)) continue;
|
|
149
|
+
seen.add(node);
|
|
150
|
+
nodes.push(node);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return nodes;
|
|
154
|
+
};
|
|
155
|
+
const collectFileCount = (nodes) => {
|
|
156
|
+
let count = 0;
|
|
157
|
+
for (const node of nodes) {
|
|
158
|
+
if (!(node instanceof HTMLElement)) continue;
|
|
159
|
+
if (node.matches('textarea,input,[contenteditable="true"]')) continue;
|
|
160
|
+
const dataTestId = node.getAttribute?.('data-testid') ?? '';
|
|
161
|
+
const aria = node.getAttribute?.('aria-label') ?? '';
|
|
162
|
+
const title = node.getAttribute?.('title') ?? '';
|
|
163
|
+
const tooltip =
|
|
164
|
+
node.getAttribute?.('data-tooltip') ?? node.getAttribute?.('data-tooltip-content') ?? '';
|
|
165
|
+
const text = node.textContent ?? '';
|
|
166
|
+
const parent = node.parentElement;
|
|
167
|
+
const parentText = parent?.textContent ?? '';
|
|
168
|
+
const parentAria = parent?.getAttribute?.('aria-label') ?? '';
|
|
169
|
+
const parentTitle = parent?.getAttribute?.('title') ?? '';
|
|
170
|
+
const parentTooltip =
|
|
171
|
+
parent?.getAttribute?.('data-tooltip') ?? parent?.getAttribute?.('data-tooltip-content') ?? '';
|
|
172
|
+
const parentTestId = parent?.getAttribute?.('data-testid') ?? '';
|
|
173
|
+
const candidates = [
|
|
174
|
+
text,
|
|
175
|
+
aria,
|
|
176
|
+
title,
|
|
177
|
+
tooltip,
|
|
178
|
+
dataTestId,
|
|
179
|
+
parentText,
|
|
180
|
+
parentAria,
|
|
181
|
+
parentTitle,
|
|
182
|
+
parentTooltip,
|
|
183
|
+
parentTestId,
|
|
184
|
+
];
|
|
185
|
+
let hasFileHint = false;
|
|
186
|
+
for (const raw of candidates) {
|
|
187
|
+
if (!raw) continue;
|
|
188
|
+
const lowered = String(raw).toLowerCase();
|
|
189
|
+
if (lowered.includes('file') || lowered.includes('attachment')) {
|
|
190
|
+
hasFileHint = true;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!hasFileHint) continue;
|
|
195
|
+
for (const raw of candidates) {
|
|
196
|
+
if (!raw) continue;
|
|
197
|
+
const match = String(raw).toLowerCase().match(countRegex);
|
|
198
|
+
if (match) {
|
|
199
|
+
const parsed = Number(match[1]);
|
|
200
|
+
if (Number.isFinite(parsed)) {
|
|
201
|
+
count = Math.max(count, parsed);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return count;
|
|
207
|
+
};
|
|
208
|
+
`;
|
|
209
|
+
}
|
|
210
|
+
export function buildComposerSendReadinessExpression() {
|
|
211
|
+
return `(() => {
|
|
212
|
+
${buildComposerScopeHelpersExpression()}
|
|
213
|
+
const button = findSendButton();
|
|
214
|
+
const style = button ? window.getComputedStyle(button) : null;
|
|
215
|
+
const disabled = button
|
|
216
|
+
? button.hasAttribute('disabled') ||
|
|
217
|
+
button.getAttribute('aria-disabled') === 'true' ||
|
|
218
|
+
button.getAttribute('data-disabled') === 'true' ||
|
|
219
|
+
style.pointerEvents === 'none' ||
|
|
220
|
+
style.display === 'none' ||
|
|
221
|
+
style.visibility === 'hidden'
|
|
222
|
+
: null;
|
|
223
|
+
const uploadingSelectors = ${JSON.stringify(UPLOAD_STATUS_SELECTORS)};
|
|
224
|
+
const uploading = uploadingSelectors.some((selector) => {
|
|
225
|
+
return Array.from(composerScope.querySelectorAll(selector)).some((node) => {
|
|
226
|
+
const ariaBusy = node.getAttribute?.('aria-busy');
|
|
227
|
+
const dataState = node.getAttribute?.('data-state');
|
|
228
|
+
if (
|
|
229
|
+
ariaBusy === 'true' ||
|
|
230
|
+
dataState === 'loading' ||
|
|
231
|
+
dataState === 'uploading' ||
|
|
232
|
+
dataState === 'pending'
|
|
233
|
+
) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
const text = node.textContent?.toLowerCase?.() ?? '';
|
|
237
|
+
return /\\buploading\\b/.test(text) || /\\bprocessing\\b/.test(text);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
const attachmentNodes = collectAttachmentNodes();
|
|
241
|
+
const attachedNames = [];
|
|
242
|
+
for (const node of attachmentNodes) {
|
|
243
|
+
const text = node.textContent ?? '';
|
|
244
|
+
const aria = node.getAttribute?.('aria-label') ?? '';
|
|
245
|
+
const title = node.getAttribute?.('title') ?? '';
|
|
246
|
+
const parentText = node.parentElement?.parentElement?.innerText ?? '';
|
|
247
|
+
for (const value of [text, aria, title, parentText]) {
|
|
248
|
+
const normalized = value?.toLowerCase?.();
|
|
249
|
+
if (normalized) attachedNames.push(normalized);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const cardTexts = Array.from(composerScope.querySelectorAll('[aria-label*="Remove"]')).map((btn) =>
|
|
253
|
+
btn?.parentElement?.parentElement?.innerText?.toLowerCase?.() ?? '',
|
|
254
|
+
);
|
|
255
|
+
attachedNames.push(...cardTexts.filter(Boolean));
|
|
256
|
+
const inputNames = [];
|
|
257
|
+
const inputScope = Array.from(composerScope.querySelectorAll('input[type="file"]'));
|
|
258
|
+
const inputNodes = [];
|
|
259
|
+
const inputSeen = new Set();
|
|
260
|
+
for (const el of [...inputScope, ...Array.from(document.querySelectorAll('input[type="file"]'))]) {
|
|
261
|
+
if (!inputSeen.has(el)) {
|
|
262
|
+
inputSeen.add(el);
|
|
263
|
+
inputNodes.push(el);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
for (const input of inputNodes) {
|
|
267
|
+
if (!(input instanceof HTMLInputElement) || !input.files?.length) continue;
|
|
268
|
+
for (const file of Array.from(input.files)) {
|
|
269
|
+
if (file?.name) inputNames.push(file.name.toLowerCase());
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const localFileCountNodes = Array.from(composerScope.querySelectorAll(fileCountSelectors));
|
|
273
|
+
let fileCount = collectFileCount(localFileCountNodes);
|
|
274
|
+
if (!fileCount) {
|
|
275
|
+
fileCount = collectFileCount(Array.from(document.querySelectorAll(fileCountSelectors)));
|
|
276
|
+
}
|
|
277
|
+
const attachmentUiCount = attachmentNodes.length;
|
|
278
|
+
const filesAttached = attachedNames.length > 0 || fileCount > 0 || attachmentUiCount > 0;
|
|
279
|
+
return {
|
|
280
|
+
state: button ? (disabled ? 'disabled' : 'ready') : 'missing',
|
|
281
|
+
uploading,
|
|
282
|
+
filesAttached,
|
|
283
|
+
attachedNames,
|
|
284
|
+
inputNames,
|
|
285
|
+
fileCount,
|
|
286
|
+
attachmentUiCount,
|
|
287
|
+
};
|
|
288
|
+
})()`;
|
|
289
|
+
}
|
|
290
|
+
export function buildComposerSendClickExpression() {
|
|
291
|
+
return `(() => {
|
|
292
|
+
${buildClickDispatcher()}
|
|
293
|
+
${buildComposerScopeHelpersExpression()}
|
|
294
|
+
const button = findSendButton();
|
|
295
|
+
if (!button) return 'missing';
|
|
296
|
+
if (isStopControl(button)) return 'stop-button';
|
|
297
|
+
const style = window.getComputedStyle(button);
|
|
298
|
+
const disabled =
|
|
299
|
+
button.hasAttribute('disabled') ||
|
|
300
|
+
button.getAttribute('aria-disabled') === 'true' ||
|
|
301
|
+
button.getAttribute('data-disabled') === 'true' ||
|
|
302
|
+
style.pointerEvents === 'none' ||
|
|
303
|
+
style.display === 'none' ||
|
|
304
|
+
style.visibility === 'hidden';
|
|
305
|
+
if (disabled) return 'disabled';
|
|
306
|
+
dispatchClickSequence(button);
|
|
307
|
+
return 'clicked';
|
|
308
|
+
})()`;
|
|
309
|
+
}
|
|
310
|
+
export async function readComposerSendReadiness(Runtime) {
|
|
311
|
+
const response = await Runtime.evaluate({
|
|
312
|
+
expression: buildComposerSendReadinessExpression(),
|
|
313
|
+
returnByValue: true,
|
|
314
|
+
});
|
|
315
|
+
return response.result?.value ?? null;
|
|
316
|
+
}
|
|
317
|
+
export function evaluateComposerAttachmentEvidence(state, expectedNames = []) {
|
|
318
|
+
const expectedNormalized = expectedNames.map(normalizeExpectedName).filter(Boolean);
|
|
319
|
+
const attachedNames = (state.attachedNames ?? []).map(normalizeToken).filter(Boolean);
|
|
320
|
+
const inputNames = (state.inputNames ?? []).map(normalizeToken).filter(Boolean);
|
|
321
|
+
if (expectedNormalized.length === 0) {
|
|
322
|
+
const attached = Boolean(state.filesAttached || state.fileCount > 0 || state.attachmentUiCount > 0);
|
|
323
|
+
const input = inputNames.length > 0;
|
|
324
|
+
return {
|
|
325
|
+
expectedNormalized,
|
|
326
|
+
attachedNames,
|
|
327
|
+
inputNames,
|
|
328
|
+
attachedMatch: attached,
|
|
329
|
+
inputMatch: input,
|
|
330
|
+
fileCountSatisfied: attached,
|
|
331
|
+
attachmentUiSatisfied: attached,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
expectedNormalized,
|
|
336
|
+
attachedNames,
|
|
337
|
+
inputNames,
|
|
338
|
+
attachedMatch: expectedNormalized.every((expected) => attachedNames.some((raw) => matchesExpected(raw, expected))),
|
|
339
|
+
inputMatch: expectedNormalized.every((expected) => inputNames.some((raw) => matchesExpected(raw, expected))),
|
|
340
|
+
fileCountSatisfied: state.fileCount >= expectedNormalized.length,
|
|
341
|
+
attachmentUiSatisfied: state.attachmentUiCount >= expectedNormalized.length,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
export function hasAttachmentCompletionEvidence(state, expectedNames = []) {
|
|
345
|
+
const evidence = evaluateComposerAttachmentEvidence(state, expectedNames);
|
|
346
|
+
return (evidence.attachedMatch ||
|
|
347
|
+
evidence.inputMatch ||
|
|
348
|
+
evidence.fileCountSatisfied ||
|
|
349
|
+
evidence.attachmentUiSatisfied);
|
|
350
|
+
}
|
|
351
|
+
export function summarizeComposerSendReadiness(state, expectedNames = []) {
|
|
352
|
+
if (!state) {
|
|
353
|
+
return { state: "unavailable" };
|
|
354
|
+
}
|
|
355
|
+
const evidence = evaluateComposerAttachmentEvidence(state, expectedNames);
|
|
356
|
+
return {
|
|
357
|
+
state: state.state,
|
|
358
|
+
uploading: state.uploading,
|
|
359
|
+
filesAttached: state.filesAttached,
|
|
360
|
+
fileCount: state.fileCount,
|
|
361
|
+
attachmentUiCount: state.attachmentUiCount,
|
|
362
|
+
attachedNames: evidence.attachedNames.slice(0, 3),
|
|
363
|
+
inputNames: evidence.inputNames.slice(0, 3),
|
|
364
|
+
attachedMatch: evidence.attachedMatch,
|
|
365
|
+
inputMatch: evidence.inputMatch,
|
|
366
|
+
fileCountSatisfied: evidence.fileCountSatisfied,
|
|
367
|
+
attachmentUiSatisfied: evidence.attachmentUiSatisfied,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const CLICK_TYPES = [
|
|
2
|
+
"pointerover",
|
|
3
|
+
"pointerenter",
|
|
4
|
+
"mouseover",
|
|
5
|
+
"mouseenter",
|
|
6
|
+
"pointermove",
|
|
7
|
+
"mousemove",
|
|
8
|
+
"pointerdown",
|
|
9
|
+
"mousedown",
|
|
10
|
+
"pointerup",
|
|
11
|
+
"mouseup",
|
|
12
|
+
"click",
|
|
13
|
+
];
|
|
14
|
+
export function buildClickDispatcher(functionName = "dispatchClickSequence") {
|
|
15
|
+
const typesLiteral = JSON.stringify(CLICK_TYPES);
|
|
16
|
+
return `function ${functionName}(target){
|
|
17
|
+
if(!target || !(target instanceof EventTarget)) return false;
|
|
18
|
+
const types = ${typesLiteral};
|
|
19
|
+
for (const type of types) {
|
|
20
|
+
const common = { bubbles: true, cancelable: true, view: window };
|
|
21
|
+
let event;
|
|
22
|
+
if (type.startsWith('pointer') && 'PointerEvent' in window) {
|
|
23
|
+
event = new PointerEvent(type, { ...common, pointerId: 1, pointerType: 'mouse' });
|
|
24
|
+
} else {
|
|
25
|
+
event = new MouseEvent(type, common);
|
|
26
|
+
}
|
|
27
|
+
target.dispatchEvent(event);
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function createPostSubmitInputGuard(Input, logger) {
|
|
2
|
+
const input = Input;
|
|
3
|
+
let enabled = false;
|
|
4
|
+
const setIgnored = async (ignore) => {
|
|
5
|
+
if (typeof input.setIgnoreInputEvents !== "function") {
|
|
6
|
+
if (ignore) {
|
|
7
|
+
logger("[browser] CDP input guard unavailable; continuing with focus-only stop protection.");
|
|
8
|
+
}
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
await input.setIgnoreInputEvents({ ignore });
|
|
12
|
+
enabled = ignore;
|
|
13
|
+
logger(ignore
|
|
14
|
+
? "[browser] Enabled post-submit input guard"
|
|
15
|
+
: "[browser] Disabled post-submit input guard");
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
get enabled() {
|
|
19
|
+
return enabled;
|
|
20
|
+
},
|
|
21
|
+
async enable() {
|
|
22
|
+
if (enabled)
|
|
23
|
+
return;
|
|
24
|
+
try {
|
|
25
|
+
await setIgnored(true);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
enabled = false;
|
|
29
|
+
logger(`[browser] Failed to enable post-submit input guard: ${error instanceof Error ? error.message : String(error)}`);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
async disable() {
|
|
33
|
+
if (!enabled)
|
|
34
|
+
return true;
|
|
35
|
+
let lastError;
|
|
36
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
37
|
+
try {
|
|
38
|
+
await setIgnored(false);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
lastError = error;
|
|
43
|
+
if (attempt < 3) {
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, 100 * attempt));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
logger(`[browser] Failed to disable post-submit input guard after retries: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
49
|
+
return false;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { getChatGptModelKindTestIdTokens } from "../chatgptModelCatalog.js";
|
|
2
|
+
export function buildModelPickerDomHelpers() {
|
|
3
|
+
const kindTokensLiteral = JSON.stringify(getChatGptModelKindTestIdTokens());
|
|
4
|
+
return `
|
|
5
|
+
const EFFORT_LABELS = new Set([
|
|
6
|
+
'light',
|
|
7
|
+
'standard',
|
|
8
|
+
'extended',
|
|
9
|
+
'heavy',
|
|
10
|
+
'instant',
|
|
11
|
+
'medium',
|
|
12
|
+
'high',
|
|
13
|
+
'langer',
|
|
14
|
+
'sofort',
|
|
15
|
+
'mittel',
|
|
16
|
+
'hoch',
|
|
17
|
+
]);
|
|
18
|
+
const MODEL_KIND_TEST_ID_TOKENS = ${kindTokensLiteral};
|
|
19
|
+
const findModelButton = () => {
|
|
20
|
+
const candidates = Array.from(document.querySelectorAll(MODEL_BUTTON_SELECTOR));
|
|
21
|
+
if (candidates.length === 0) return null;
|
|
22
|
+
let best = null;
|
|
23
|
+
for (const candidate of candidates) {
|
|
24
|
+
const rawText = [
|
|
25
|
+
candidate.textContent ?? '',
|
|
26
|
+
candidate.getAttribute?.('aria-label') ?? '',
|
|
27
|
+
candidate.getAttribute?.('data-testid') ?? '',
|
|
28
|
+
].join(' ');
|
|
29
|
+
const label = normalize(rawText);
|
|
30
|
+
const testId = candidate.getAttribute?.('data-testid') ?? '';
|
|
31
|
+
const className = candidate.getAttribute?.('class') ?? '';
|
|
32
|
+
const hasMenu = candidate.getAttribute?.('aria-haspopup') === 'menu';
|
|
33
|
+
const isEffortOnly = label === 'pro' || label === 'thinking' || EFFORT_LABELS.has(label);
|
|
34
|
+
const rect = candidate.getBoundingClientRect?.();
|
|
35
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) continue;
|
|
36
|
+
let score = 0;
|
|
37
|
+
if (testId.includes('model-switcher')) score += 1000;
|
|
38
|
+
if (label.includes('model')) score += 300;
|
|
39
|
+
if (label.includes('gpt') || label.includes('chatgpt')) score += 200;
|
|
40
|
+
if (label.includes('auto')) score += 250;
|
|
41
|
+
if (label.includes('instant')) score += 250;
|
|
42
|
+
if (/\\b5\\b/.test(label) || /\\b5\\s+[0-9]\\b/.test(label)) score += 150;
|
|
43
|
+
if ((label.includes('thinking') || label.includes('pro')) && !isEffortOnly) score += 100;
|
|
44
|
+
if (className.includes('__composer-pill') && (label.includes('instant') || label.includes('medium') || label.includes('high'))) score += 120;
|
|
45
|
+
if (className.includes('__composer-pill') && hasMenu && isEffortOnly) score += 120;
|
|
46
|
+
if (isEffortOnly) score += 20;
|
|
47
|
+
score += 10;
|
|
48
|
+
if (!best || score > best.score) best = { candidate, score };
|
|
49
|
+
}
|
|
50
|
+
return best && best.score >= 100 ? best.candidate : null;
|
|
51
|
+
};
|
|
52
|
+
const modelKindFromLabel = (value) => {
|
|
53
|
+
const label = normalize(value);
|
|
54
|
+
if (label.includes('pro') && !label.includes('thinking')) return 'pro';
|
|
55
|
+
if (label.includes('thinking') && !label.includes('pro')) return 'thinking';
|
|
56
|
+
return null;
|
|
57
|
+
};
|
|
58
|
+
const modelKindFromTestId = (value) => {
|
|
59
|
+
const testId = String(value || '').toLowerCase();
|
|
60
|
+
for (const token of MODEL_KIND_TEST_ID_TOKENS.pro) {
|
|
61
|
+
if (testId.includes(token)) return 'pro';
|
|
62
|
+
}
|
|
63
|
+
for (const token of MODEL_KIND_TEST_ID_TOKENS.thinking) {
|
|
64
|
+
if (testId.includes(token)) return 'thinking';
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
};`;
|
|
68
|
+
}
|