surf-cli 2.17.0 → 2.19.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/README.md +135 -9
- package/agents/gpt-pro.md +2 -2
- package/native/browser-session-store.cjs +30 -2
- package/native/chatgpt-client-selection.cjs +79 -35
- package/native/chatgpt-client-ui.cjs +313 -198
- package/native/chatgpt-client.cjs +7 -2
- package/native/cli.cjs +443 -13
- package/native/doctor.cjs +11 -2
- package/native/endpoint.cjs +121 -36
- package/native/extract.cjs +362 -0
- package/native/file-transfer.cjs +5 -1
- package/native/host-helpers.cjs +40 -3
- package/native/host-sessions.cjs +10 -0
- package/native/host.cjs +451 -26
- package/native/mcp-server.cjs +26 -1
- package/native/oracle-cli.cjs +2 -2
- package/native/script-options.cjs +33 -0
- package/native/socket-permissions.cjs +114 -0
- package/native/stdin-frames.cjs +33 -0
- package/native/tool-scope.cjs +8 -4
- package/native/video-recorder.cjs +444 -0
- package/native/workflow-definition.cjs +5 -0
- package/package.json +6 -6
- package/scripts/install-native-host.cjs +53 -6
- package/skills/surf/SKILL.md +25 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { abortableDelay, throwIfAborted } = require("./abort.cjs");
|
|
2
2
|
const {
|
|
3
3
|
CHATGPT_EFFORT_CHOICES,
|
|
4
|
+
CHATGPT_EFFORT_VALUE,
|
|
4
5
|
boundedOptionLabels,
|
|
5
6
|
normalizeChatGPTEffortChoice,
|
|
6
7
|
normalizeChatGPTModelChoice,
|
|
@@ -495,243 +496,356 @@ function verificationError(kind, requested, items = [], invalid = false) {
|
|
|
495
496
|
return error;
|
|
496
497
|
}
|
|
497
498
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
return
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
.split(/\s+/)
|
|
511
|
-
.map((id) => document.getElementById(id)?.textContent || '')
|
|
512
|
-
.join(' ');
|
|
513
|
-
const text = (node.innerText || node.textContent || '').replace(/\s+/g, ' ').trim();
|
|
514
|
-
const aria = (node.getAttribute?.('aria-label') || '').replace(/\s+/g, ' ').trim();
|
|
515
|
-
const title = (node.getAttribute?.('title') || '').replace(/\s+/g, ' ').trim();
|
|
516
|
-
return [text, aria, labelledBy, title].filter(Boolean).join(' | ');
|
|
517
|
-
};
|
|
518
|
-
let nodes = Array.from(document.querySelectorAll(${JSON.stringify(selector)})).filter((node) => {
|
|
519
|
-
const value = normalize(labelFor(node));
|
|
520
|
-
if (kind !== 'model') {
|
|
521
|
-
const words = value.split(/\s+/).filter(Boolean);
|
|
522
|
-
const hasEffort = words.some((word) => effortOwnerLabels.has(word));
|
|
523
|
-
const looksLikeModel = node.getAttribute?.('data-testid') === 'model-switcher-dropdown-button' ||
|
|
524
|
-
value.includes('current model') || value.includes('gpt') || value.includes('instant');
|
|
525
|
-
return hasEffort && !looksLikeModel;
|
|
526
|
-
}
|
|
527
|
-
return value.includes('gpt') || value.includes('thinking') || value.includes('instant');
|
|
528
|
-
});
|
|
529
|
-
if (kind === 'model' && nodes.length === 0) {
|
|
530
|
-
nodes = Array.from(document.querySelectorAll(${JSON.stringify(selector)})).filter((node) => {
|
|
531
|
-
const value = normalize(labelFor(node));
|
|
532
|
-
return value.includes('pro');
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
if (kind === 'model' && nodes.length === 0) {
|
|
536
|
-
nodes = Array.from(document.querySelectorAll(${JSON.stringify(selector)})).filter((node) =>
|
|
537
|
-
node.getAttribute?.('aria-haspopup') === 'menu' || node.getAttribute?.('aria-expanded') !== null
|
|
538
|
-
);
|
|
499
|
+
|
|
500
|
+
function combinedPickerScript(click = false) {
|
|
501
|
+
return `(() => {
|
|
502
|
+
${buildClickDispatcher()}
|
|
503
|
+
const pickerSelector = ${JSON.stringify(SELECTORS.modelButton)};
|
|
504
|
+
const normalizeText = (value) => String(value || '').replace(/\\s+/g, ' ').trim();
|
|
505
|
+
const visible = (node) => {
|
|
506
|
+
if (!node || node.nodeType !== 1) return false;
|
|
507
|
+
for (let el = node; el && el.nodeType === 1; el = el.parentElement) {
|
|
508
|
+
if (el.hasAttribute?.('hidden') || el.hasAttribute?.('inert') || el.getAttribute?.('aria-hidden') === 'true') return false;
|
|
509
|
+
const style = window.getComputedStyle?.(el);
|
|
510
|
+
if (style && (style.display === 'none' || style.visibility === 'hidden')) return false;
|
|
539
511
|
}
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
512
|
+
const rect = node.getBoundingClientRect?.();
|
|
513
|
+
return !rect || (rect.width > 0 && rect.height > 0);
|
|
514
|
+
};
|
|
515
|
+
const labelledText = (node) => {
|
|
516
|
+
const labelledBy = String(node.getAttribute?.('aria-labelledby') || '')
|
|
517
|
+
.split(/\\s+/)
|
|
518
|
+
.map((id) => document.getElementById(id)?.textContent || '')
|
|
519
|
+
.join(' ');
|
|
520
|
+
return [node.innerText || node.textContent || '', node.getAttribute?.('aria-label') || '', labelledBy, node.getAttribute?.('title') || '']
|
|
521
|
+
.map(normalizeText)
|
|
522
|
+
.filter(Boolean)
|
|
523
|
+
.join(' | ');
|
|
524
|
+
};
|
|
525
|
+
const directSpanText = (node) => Array.from(node?.children || [])
|
|
526
|
+
.filter((child) => child.tagName === 'SPAN')
|
|
527
|
+
.filter(visible)
|
|
528
|
+
.map((span) => normalizeText(span.innerText || span.textContent || ''))
|
|
529
|
+
.filter(Boolean)
|
|
530
|
+
.filter((label, index, labels) => labels.indexOf(label) === index)
|
|
531
|
+
.join(' ');
|
|
532
|
+
const itemFor = (node, fallbackLabel) => {
|
|
533
|
+
const text = normalizeText(directSpanText(node) || node.innerText || node.textContent || '');
|
|
534
|
+
const aria = normalizeText(node.getAttribute?.('aria-label') || '');
|
|
535
|
+
const labelled = labelledText(node);
|
|
536
|
+
const label = [text, aria, labelled]
|
|
537
|
+
.filter(Boolean)
|
|
538
|
+
.filter((value, index, values) => values.indexOf(value) === index)
|
|
539
|
+
.join(' | ');
|
|
540
|
+
const modelText = text.toLowerCase();
|
|
541
|
+
const modelKey = /\\b5\\.6\\b/.test(modelText) && /\\bsol\\b/.test(modelText)
|
|
542
|
+
? 'gpt56sol'
|
|
543
|
+
: /\\b5\\.5\\b/.test(modelText)
|
|
544
|
+
? 'gpt55'
|
|
545
|
+
: (/\\bgpt\\s*6\\b/.test(modelText) || /^6(?:\\s|$)/.test(modelText))
|
|
546
|
+
? 'gpt6astra'
|
|
547
|
+
: /^latest$/.test(modelText)
|
|
548
|
+
? 'latest'
|
|
549
|
+
: null;
|
|
550
|
+
return {
|
|
551
|
+
role: node.getAttribute?.('role') || (node.tagName === 'BUTTON' ? 'button' : null),
|
|
552
|
+
label: (label || fallbackLabel || '').slice(0, 240),
|
|
553
|
+
displayLabel: (text || aria || fallbackLabel || '').slice(0, 80),
|
|
554
|
+
testId: node.getAttribute?.('data-testid') || null,
|
|
555
|
+
...(modelKey ? { modelKey } : {}),
|
|
556
|
+
};
|
|
557
|
+
};
|
|
558
|
+
const buttons = Array.from(document.querySelectorAll(pickerSelector)).filter((node) =>
|
|
559
|
+
visible(node) && (node.getAttribute?.('aria-haspopup') === 'menu' || node.getAttribute?.('aria-expanded') !== null)
|
|
560
|
+
);
|
|
561
|
+
const pickerButtons = buttons.map((node) => itemFor(node));
|
|
562
|
+
if (${click} && buttons.length === 1) dispatchClickSequence(buttons[0]);
|
|
563
|
+
|
|
564
|
+
const menus = Array.from(document.querySelectorAll('[role="menu"][data-radix-menu-content]')).filter(visible);
|
|
565
|
+
const menu = menus.find((node) => node.querySelector?.('[data-testid="composer-intelligence-picker-content"]')) || menus[0] || null;
|
|
566
|
+
if (!menu) return { pickerButtons, menuFound: false, modelItems: pickerButtons, effortItems: [] };
|
|
567
|
+
|
|
568
|
+
const content = menu.querySelector?.('[data-testid="composer-intelligence-picker-content"]') || menu;
|
|
569
|
+
const panels = Array.from(content.querySelectorAll?.('[data-testid="composer-model-picker-slider-simple-view"], [data-testid="composer-model-picker-slider-advanced-view"], [data-view="simple"], [data-view="advanced"]') || [])
|
|
570
|
+
.filter(visible);
|
|
571
|
+
const activePanel = panels.find((panel) => !panel.hasAttribute?.('inert')) || content;
|
|
572
|
+
const activeId = activePanel.getAttribute?.('data-testid') || '';
|
|
573
|
+
const view = activePanel.getAttribute?.('data-view') || (activeId.includes('advanced') ? 'advanced' : 'simple');
|
|
574
|
+
|
|
575
|
+
const modelToggle = Array.from(content.querySelectorAll?.('[role="menuitem"][aria-label="Select model"]') || [])
|
|
576
|
+
.filter(visible)[0] || null;
|
|
577
|
+
const modelToggleItem = modelToggle ? itemFor(modelToggle) : null;
|
|
578
|
+
const modelOptions = Array.from(activePanel.querySelectorAll?.('[role="menuitemradio"]') || [])
|
|
579
|
+
.filter(visible)
|
|
580
|
+
.map((node) => {
|
|
581
|
+
const item = itemFor(node);
|
|
582
|
+
const state = String(node.getAttribute?.('data-state') || '').toLowerCase();
|
|
550
583
|
return {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
displayLabel: (text || aria || labelledBy || title).slice(0, 80),
|
|
554
|
-
testId: node.getAttribute?.('data-testid') || null,
|
|
584
|
+
...item,
|
|
585
|
+
selected: node.getAttribute?.('aria-checked') === 'true' || state === 'checked',
|
|
555
586
|
};
|
|
556
|
-
})
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
587
|
+
})
|
|
588
|
+
.filter((item) => /\\b(latest|gpt|5\\.6|5\\.5)\\b/i.test(item.label));
|
|
589
|
+
|
|
590
|
+
const power = Array.from(content.querySelectorAll?.('[role="menuitem"][aria-label="Power"]') || [])
|
|
591
|
+
.filter(visible)[0] || null;
|
|
592
|
+
let effortItems = [];
|
|
593
|
+
if (power) {
|
|
594
|
+
const slider = power.querySelector?.('[data-model-reasoning-effort-slider] [role="slider"]') || power.querySelector?.('[role="slider"]');
|
|
595
|
+
if (slider) {
|
|
596
|
+
const describedBy = String(power.getAttribute?.('aria-describedby') || slider.getAttribute?.('aria-describedby') || '')
|
|
597
|
+
.split(/\\s+/)
|
|
598
|
+
.map((id) => normalizeText(document.getElementById(id)?.textContent || ''))
|
|
599
|
+
.filter(Boolean)
|
|
600
|
+
.join(' | ');
|
|
601
|
+
const ariaValueText = normalizeText(slider.getAttribute?.('aria-valuetext') || '');
|
|
602
|
+
const powerText = normalizeText(power.innerText || power.textContent || '');
|
|
603
|
+
effortItems = [{
|
|
604
|
+
role: 'slider',
|
|
605
|
+
label: [ariaValueText, describedBy, powerText].filter(Boolean).join(' | ').slice(0, 240),
|
|
606
|
+
displayLabel: (ariaValueText || describedBy || powerText).slice(0, 80),
|
|
607
|
+
value: Number(slider.getAttribute?.('aria-valuenow')),
|
|
608
|
+
min: Number(slider.getAttribute?.('aria-valuemin')),
|
|
609
|
+
max: Number(slider.getAttribute?.('aria-valuemax')),
|
|
610
|
+
selected: true,
|
|
611
|
+
}];
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return {
|
|
616
|
+
pickerButtons,
|
|
617
|
+
menuFound: true,
|
|
618
|
+
view,
|
|
619
|
+
modelToggle: modelToggleItem,
|
|
620
|
+
modelToggleExpanded: modelToggle?.getAttribute?.('aria-expanded') === 'true',
|
|
621
|
+
modelOptions,
|
|
622
|
+
modelItems: [...(modelToggleItem ? [modelToggleItem] : []), ...modelOptions.filter((item) => item.selected)],
|
|
623
|
+
effortItems,
|
|
624
|
+
};
|
|
625
|
+
})()`;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async function readCombinedPicker(cdp, click = false, signal) {
|
|
629
|
+
return evaluate(cdp, combinedPickerScript(click), signal);
|
|
561
630
|
}
|
|
562
631
|
|
|
563
|
-
async function
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
632
|
+
async function waitForCombinedMenu(cdp, timeoutMs, signal) {
|
|
633
|
+
const deadline = Date.now() + timeoutMs;
|
|
634
|
+
while (Date.now() < deadline) {
|
|
635
|
+
const result = await readCombinedPicker(cdp, false, signal);
|
|
636
|
+
if (result?.menuFound) return result;
|
|
637
|
+
await delay(100, signal);
|
|
638
|
+
}
|
|
639
|
+
return { menuFound: false, modelItems: [], modelOptions: [], effortItems: [] };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async function activateAdvancedModelView(cdp, signal) {
|
|
567
643
|
return evaluate(
|
|
568
644
|
cdp,
|
|
569
645
|
`(() => {
|
|
570
646
|
${buildClickDispatcher()}
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
.filter((item) => item.getAttribute?.('aria-haspopup') !== 'menu')
|
|
578
|
-
.map((item) => normalize((item.getAttribute?.('aria-label') || '') + ' ' + (item.textContent || '')));
|
|
579
|
-
return labels.some((label) => label.includes('gpt') || /^o[0-9]/.test(label));
|
|
580
|
-
}) : containers.find((container) => {
|
|
581
|
-
const label = normalize(container.querySelector?.(${JSON.stringify(SELECTORS.effortMenuLabel)})?.textContent);
|
|
582
|
-
const levels = new Set(Array.from(container.querySelectorAll(${JSON.stringify(itemSelector)}))
|
|
583
|
-
.flatMap((item) => normalize(item.textContent).split(/\\s+/))
|
|
584
|
-
.filter((word) => choices.includes(word)));
|
|
585
|
-
return label.includes('thinking time') || levels.size >= 2;
|
|
586
|
-
});
|
|
587
|
-
if (!menu && isModel && ${allowSubmenu}) {
|
|
588
|
-
for (const container of containers) {
|
|
589
|
-
const trigger = Array.from(container.querySelectorAll(${JSON.stringify(SELECTORS.effortSubmenuTrigger)}))
|
|
590
|
-
.find((item) => {
|
|
591
|
-
const label = normalize((item.getAttribute?.('aria-label') || '') + ' ' + (item.textContent || ''));
|
|
592
|
-
return label.includes('model') || label.includes('advanced');
|
|
593
|
-
});
|
|
594
|
-
if (trigger) {
|
|
595
|
-
dispatchClickSequence(trigger);
|
|
596
|
-
return { found: false, submenuOpened: true, items: [] };
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
if (!menu && !isModel && ${allowSubmenu}) {
|
|
601
|
-
for (const container of containers) {
|
|
602
|
-
const trigger = Array.from(container.querySelectorAll(${JSON.stringify(SELECTORS.effortSubmenuTrigger)}))
|
|
603
|
-
.find((item) => {
|
|
604
|
-
const label = normalize((item.getAttribute?.('aria-label') || '') + ' ' + (item.textContent || ''));
|
|
605
|
-
return label.includes('thinking time') || label.includes('reasoning effort');
|
|
606
|
-
});
|
|
607
|
-
if (trigger) {
|
|
608
|
-
dispatchClickSequence(trigger);
|
|
609
|
-
return { found: false, submenuOpened: true, items: [] };
|
|
610
|
-
}
|
|
647
|
+
const visible = (node) => {
|
|
648
|
+
if (!node || node.nodeType !== 1) return false;
|
|
649
|
+
for (let el = node; el && el.nodeType === 1; el = el.parentElement) {
|
|
650
|
+
if (el.hasAttribute?.('hidden') || el.hasAttribute?.('inert') || el.getAttribute?.('aria-hidden') === 'true') return false;
|
|
651
|
+
const style = window.getComputedStyle?.(el);
|
|
652
|
+
if (style && (style.display === 'none' || style.visibility === 'hidden')) return false;
|
|
611
653
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const selected = item.getAttribute?.('aria-checked') === 'true' ||
|
|
620
|
-
item.getAttribute?.('aria-selected') === 'true' || item.getAttribute?.('data-selected') === 'true' ||
|
|
621
|
-
['checked', 'selected', 'on'].includes(state) ||
|
|
622
|
-
Boolean(item.querySelector?.(${JSON.stringify(SELECTORS.selectedMenuIndicator)}));
|
|
623
|
-
return {
|
|
624
|
-
role: item.getAttribute?.('role') || (item.tagName === 'BUTTON' ? 'button' : null),
|
|
625
|
-
label,
|
|
626
|
-
testId: item.getAttribute?.('data-testid') || null,
|
|
627
|
-
selected,
|
|
628
|
-
};
|
|
629
|
-
});
|
|
630
|
-
return { found: true, items };
|
|
654
|
+
const rect = node.getBoundingClientRect?.();
|
|
655
|
+
return !rect || (rect.width > 0 && rect.height > 0);
|
|
656
|
+
};
|
|
657
|
+
const trigger = Array.from(document.querySelectorAll('[role="menuitem"][aria-label="Select model"]')).filter(visible)[0];
|
|
658
|
+
if (!trigger) return false;
|
|
659
|
+
if (trigger.getAttribute?.('aria-expanded') === 'true') return true;
|
|
660
|
+
return dispatchClickSequence(trigger);
|
|
631
661
|
})()`,
|
|
662
|
+
signal,
|
|
632
663
|
);
|
|
633
664
|
}
|
|
634
665
|
|
|
635
|
-
async function
|
|
636
|
-
const deadline = Date.now() + timeoutMs;
|
|
637
|
-
let allowSubmenu = true;
|
|
638
|
-
while (Date.now() < deadline) {
|
|
639
|
-
const result = await readMenu(cdp, kind, allowSubmenu);
|
|
640
|
-
if (result?.found) return result;
|
|
641
|
-
if (result?.submenuOpened) allowSubmenu = false;
|
|
642
|
-
await delay(100, signal);
|
|
643
|
-
}
|
|
644
|
-
return { found: false, items: [] };
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
async function clickMenuItem(cdp, kind, match) {
|
|
648
|
-
const isModel = kind === "model";
|
|
649
|
-
const menuSelector = isModel ? SELECTORS.modelMenu : SELECTORS.effortMenu;
|
|
650
|
-
const itemSelector = isModel ? SELECTORS.modelMenuItem : SELECTORS.effortMenuItem;
|
|
666
|
+
async function clickModelOption(cdp, match, signal) {
|
|
651
667
|
return evaluate(
|
|
652
668
|
cdp,
|
|
653
669
|
`(() => {
|
|
654
670
|
${buildClickDispatcher()}
|
|
655
671
|
const expectedTestId = ${JSON.stringify(match.testId)};
|
|
656
672
|
const expectedLabel = ${JSON.stringify(match.label)};
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
673
|
+
const normalizeText = (value) => String(value || '').replace(/\\s+/g, ' ').trim();
|
|
674
|
+
const visible = (node) => {
|
|
675
|
+
if (!node || node.nodeType !== 1) return false;
|
|
676
|
+
for (let el = node; el && el.nodeType === 1; el = el.parentElement) {
|
|
677
|
+
if (el.hasAttribute?.('hidden') || el.hasAttribute?.('inert') || el.getAttribute?.('aria-hidden') === 'true') return false;
|
|
678
|
+
const style = window.getComputedStyle?.(el);
|
|
679
|
+
if (style && (style.display === 'none' || style.visibility === 'hidden')) return false;
|
|
680
|
+
}
|
|
681
|
+
const rect = node.getBoundingClientRect?.();
|
|
682
|
+
return !rect || (rect.width > 0 && rect.height > 0);
|
|
683
|
+
};
|
|
684
|
+
const menu = Array.from(document.querySelectorAll('[role="menu"][data-radix-menu-content]')).filter(visible)[0];
|
|
685
|
+
const content = menu?.querySelector?.('[data-testid="composer-intelligence-picker-content"]') || menu;
|
|
686
|
+
const panels = Array.from(content?.querySelectorAll?.('[data-testid="composer-model-picker-slider-advanced-view"], [data-view="advanced"]') || []).filter(visible);
|
|
687
|
+
const panel = panels.find((node) => !node.hasAttribute?.('inert')) || content;
|
|
688
|
+
const matches = Array.from(panel?.querySelectorAll?.('[role="menuitemradio"]') || []).filter(visible).filter((item) => {
|
|
662
689
|
if (expectedTestId) return item.getAttribute?.('data-testid') === expectedTestId;
|
|
663
|
-
const
|
|
664
|
-
const label = (primary?.textContent || item.getAttribute?.('aria-label') || item.textContent || '')
|
|
665
|
-
.replace(/\\s+/g, ' ').trim().slice(0, 80);
|
|
690
|
+
const label = normalizeText(item.innerText || item.textContent || item.getAttribute?.('aria-label') || '').slice(0, 240);
|
|
666
691
|
return label === expectedLabel;
|
|
667
692
|
});
|
|
668
|
-
return matches.length
|
|
693
|
+
return matches.length === 1 ? dispatchClickSequence(matches[0]) : false;
|
|
669
694
|
})()`,
|
|
695
|
+
signal,
|
|
670
696
|
);
|
|
671
697
|
}
|
|
672
698
|
|
|
673
|
-
async function
|
|
699
|
+
async function closeCombinedPicker(cdp, inputCdp, signal) {
|
|
700
|
+
const hasMenu = (await readCombinedPicker(cdp, false, signal))?.menuFound;
|
|
701
|
+
if (!hasMenu) return;
|
|
702
|
+
if (inputCdp) {
|
|
703
|
+
await inputCdp("Input.dispatchKeyEvent", { type: "keyDown", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27 });
|
|
704
|
+
await inputCdp("Input.dispatchKeyEvent", { type: "keyUp", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27 });
|
|
705
|
+
} else {
|
|
706
|
+
await evaluate(cdp, `document.activeElement?.blur?.(); document.body?.click?.(); true`, signal);
|
|
707
|
+
}
|
|
708
|
+
const deadline = Date.now() + 1000;
|
|
709
|
+
while (Date.now() < deadline) {
|
|
710
|
+
await delay(50, signal);
|
|
711
|
+
if (!(await readCombinedPicker(cdp, false, signal))?.menuFound) return;
|
|
712
|
+
}
|
|
713
|
+
throw uiError("picker_close_failed", "ChatGPT picker did not close after Escape/outside click");
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async function verifyCurrentModel(cdp, inputCdp, desiredModel, timeoutMs = 8000, signal) {
|
|
674
717
|
throwIfAborted(signal);
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
)
|
|
683
|
-
|
|
718
|
+
const state = await readCombinedPicker(cdp, false, signal);
|
|
719
|
+
const verified = verifyChatGPTModelSelection(state?.modelItems, desiredModel);
|
|
720
|
+
if (verified) {
|
|
721
|
+
await closeCombinedPicker(cdp, inputCdp, signal);
|
|
722
|
+
return verified.displayLabel || verified.label;
|
|
723
|
+
}
|
|
724
|
+
let menu = state?.menuFound ? state : null;
|
|
725
|
+
if (!menu) {
|
|
726
|
+
const opened = await readCombinedPicker(cdp, true, signal);
|
|
727
|
+
if (opened?.pickerButtons?.length !== 1) throw verificationError("model", desiredModel, state?.modelItems);
|
|
728
|
+
await delay(150, signal);
|
|
729
|
+
menu = await waitForCombinedMenu(cdp, timeoutMs, signal);
|
|
730
|
+
}
|
|
731
|
+
let readback = verifyChatGPTModelSelection(menu.modelItems, desiredModel);
|
|
732
|
+
if (!readback) {
|
|
733
|
+
if (!(await activateAdvancedModelView(cdp, signal))) throw verificationError("model", desiredModel, menu.modelItems);
|
|
734
|
+
await delay(150, signal);
|
|
735
|
+
menu = await waitForCombinedMenu(cdp, timeoutMs, signal);
|
|
736
|
+
readback = verifyChatGPTModelSelection(menu.modelItems, desiredModel);
|
|
737
|
+
}
|
|
738
|
+
if (!readback) throw verificationError("model", desiredModel, menu.modelItems);
|
|
739
|
+
await closeCombinedPicker(cdp, inputCdp, signal);
|
|
740
|
+
return readback.displayLabel || readback.label;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
async function selectModel(cdp, inputCdp, desiredModel, timeoutMs = 8000, signal) {
|
|
744
|
+
throwIfAborted(signal);
|
|
745
|
+
const picker = await readCombinedPicker(cdp, true, signal);
|
|
746
|
+
if (picker?.pickerButtons?.length !== 1) throw verificationError("model", desiredModel);
|
|
747
|
+
await delay(200, signal);
|
|
748
|
+
let menu = await waitForCombinedMenu(cdp, timeoutMs, signal);
|
|
749
|
+
const current = verifyChatGPTModelSelection(menu.modelItems, desiredModel);
|
|
750
|
+
if (current) {
|
|
751
|
+
await closeCombinedPicker(cdp, inputCdp, signal);
|
|
752
|
+
return current.displayLabel || current.label;
|
|
753
|
+
}
|
|
684
754
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
755
|
+
if (!(await activateAdvancedModelView(cdp, signal))) throw verificationError("model", desiredModel, menu.modelItems);
|
|
756
|
+
await delay(150, signal);
|
|
757
|
+
menu = await waitForCombinedMenu(cdp, timeoutMs, signal);
|
|
758
|
+
const selectedCurrent = verifyChatGPTModelSelection(menu.modelItems, desiredModel);
|
|
759
|
+
if (selectedCurrent) {
|
|
760
|
+
await closeCombinedPicker(cdp, inputCdp, signal);
|
|
761
|
+
return selectedCurrent.displayLabel || selectedCurrent.label;
|
|
762
|
+
}
|
|
763
|
+
const match = resolveChatGPTModelMenuOption(menu.modelOptions, desiredModel);
|
|
764
|
+
if (!match || !(await clickModelOption(cdp, match, signal))) {
|
|
765
|
+
throw verificationError("model", desiredModel, menu.modelOptions);
|
|
688
766
|
}
|
|
689
767
|
await delay(200, signal);
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
768
|
+
return verifyCurrentModel(cdp, inputCdp, desiredModel, timeoutMs, signal);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
async function focusPowerControl(cdp, signal) {
|
|
772
|
+
return evaluate(
|
|
773
|
+
cdp,
|
|
774
|
+
`(() => {
|
|
775
|
+
const visible = (node) => {
|
|
776
|
+
if (!node || node.nodeType !== 1) return false;
|
|
777
|
+
for (let el = node; el && el.nodeType === 1; el = el.parentElement) {
|
|
778
|
+
if (el.hasAttribute?.('hidden') || el.hasAttribute?.('inert') || el.getAttribute?.('aria-hidden') === 'true') return false;
|
|
779
|
+
const style = window.getComputedStyle?.(el);
|
|
780
|
+
if (style && (style.display === 'none' || style.visibility === 'hidden')) return false;
|
|
781
|
+
}
|
|
782
|
+
const rect = node.getBoundingClientRect?.();
|
|
783
|
+
return !rect || (rect.width > 0 && rect.height > 0);
|
|
784
|
+
};
|
|
785
|
+
const power = Array.from(document.querySelectorAll('[role="menuitem"][aria-label="Power"]')).filter(visible)[0];
|
|
786
|
+
if (!power) return false;
|
|
787
|
+
power.focus?.();
|
|
788
|
+
return true;
|
|
789
|
+
})()`,
|
|
790
|
+
signal,
|
|
700
791
|
);
|
|
701
|
-
if (!readbackVerified) throw verificationError("model", desiredModel, readbackMenu.items);
|
|
702
|
-
return readbackVerified.label;
|
|
703
792
|
}
|
|
704
793
|
|
|
705
|
-
async function
|
|
794
|
+
async function dispatchEffortArrow(cdp, inputCdp, key, signal) {
|
|
795
|
+
if (inputCdp) {
|
|
796
|
+
const code = key === "ArrowRight" ? 39 : 37;
|
|
797
|
+
await inputCdp("Input.dispatchKeyEvent", { type: "keyDown", key, code: key, windowsVirtualKeyCode: code });
|
|
798
|
+
await inputCdp("Input.dispatchKeyEvent", { type: "keyUp", key, code: key, windowsVirtualKeyCode: code });
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
await evaluate(cdp, `(() => {
|
|
802
|
+
const key = ${JSON.stringify(key)};
|
|
803
|
+
const power = document.activeElement || Array.from(document.querySelectorAll('[role="menuitem"][aria-label="Power"]'))[0];
|
|
804
|
+
power?.dispatchEvent?.(new KeyboardEvent('keydown', { key, code: key, bubbles: true, cancelable: true }));
|
|
805
|
+
power?.dispatchEvent?.(new KeyboardEvent('keyup', { key, code: key, bubbles: true, cancelable: true }));
|
|
806
|
+
return true;
|
|
807
|
+
})()`, signal);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function strictVerifiedEffort(items, desiredEffort) {
|
|
811
|
+
const verified = verifyChatGPTEffortSelection(items, desiredEffort);
|
|
812
|
+
if (!verified) return null;
|
|
813
|
+
return verified.min === 0 && verified.max === 4 && Number.isInteger(verified.value) ? verified : null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async function selectEffort(cdp, inputCdp, desiredEffort, timeoutMs = 8000, signal) {
|
|
706
817
|
throwIfAborted(signal);
|
|
707
818
|
const normalizedEffort = normalizeChatGPTEffortChoice(desiredEffort);
|
|
708
819
|
if (!normalizedEffort) throw verificationError("effort", desiredEffort, [], true);
|
|
709
|
-
const
|
|
710
|
-
const currentVerified = verifyChatGPTEffortSelection(current?.items, normalizedEffort);
|
|
711
|
-
if (currentVerified) return currentVerified.displayLabel || currentVerified.label;
|
|
820
|
+
const targetValue = CHATGPT_EFFORT_VALUE.get(normalizedEffort);
|
|
712
821
|
|
|
713
|
-
const picker = await
|
|
714
|
-
if (picker?.
|
|
715
|
-
await delay(300, signal);
|
|
716
|
-
const menu = await waitForMenu(cdp, "effort", timeoutMs, signal);
|
|
717
|
-
const match = resolveChatGPTEffortMenuOption(menu.items, normalizedEffort);
|
|
718
|
-
if (!match || !(await clickMenuItem(cdp, "effort", match))) {
|
|
719
|
-
throw verificationError("effort", desiredEffort, menu.items);
|
|
720
|
-
}
|
|
822
|
+
const picker = await readCombinedPicker(cdp, true, signal);
|
|
823
|
+
if (picker?.pickerButtons?.length !== 1) throw verificationError("effort", desiredEffort);
|
|
721
824
|
await delay(200, signal);
|
|
722
|
-
|
|
723
|
-
const
|
|
724
|
-
if (
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
)
|
|
733
|
-
|
|
734
|
-
|
|
825
|
+
let menu = await waitForCombinedMenu(cdp, timeoutMs, signal);
|
|
826
|
+
const current = strictVerifiedEffort(menu.effortItems, normalizedEffort);
|
|
827
|
+
if (current) {
|
|
828
|
+
await closeCombinedPicker(cdp, inputCdp, signal);
|
|
829
|
+
return current.displayLabel || current.label;
|
|
830
|
+
}
|
|
831
|
+
const effort = menu.effortItems?.[0];
|
|
832
|
+
if (!effort || effort.min !== 0 || effort.max !== 4 || !Number.isInteger(effort.value) || !Number.isInteger(targetValue)) {
|
|
833
|
+
throw verificationError("effort", desiredEffort, menu.effortItems);
|
|
834
|
+
}
|
|
835
|
+
if (!verifyChatGPTEffortSelection(menu.effortItems, CHATGPT_EFFORT_CHOICES[effort.value])) {
|
|
836
|
+
throw verificationError("effort", desiredEffort, menu.effortItems);
|
|
837
|
+
}
|
|
838
|
+
if (!(await focusPowerControl(cdp, signal))) throw verificationError("effort", desiredEffort, menu.effortItems);
|
|
839
|
+
const direction = targetValue > effort.value ? "ArrowRight" : "ArrowLeft";
|
|
840
|
+
for (let index = 0; index < Math.abs(targetValue - effort.value); index += 1) {
|
|
841
|
+
await dispatchEffortArrow(cdp, inputCdp, direction, signal);
|
|
842
|
+
await delay(100, signal);
|
|
843
|
+
}
|
|
844
|
+
menu = await waitForCombinedMenu(cdp, timeoutMs, signal);
|
|
845
|
+
const verified = strictVerifiedEffort(menu.effortItems, normalizedEffort);
|
|
846
|
+
if (!verified) throw verificationError("effort", desiredEffort, menu.effortItems);
|
|
847
|
+
await closeCombinedPicker(cdp, inputCdp, signal);
|
|
848
|
+
return verified.displayLabel || verified.label;
|
|
735
849
|
}
|
|
736
850
|
|
|
737
851
|
async function typePrompt(cdp, inputCdp, prompt, signal) {
|
|
@@ -867,6 +981,7 @@ module.exports = {
|
|
|
867
981
|
selectEffort,
|
|
868
982
|
selectGitHubTool,
|
|
869
983
|
selectModel,
|
|
984
|
+
verifyCurrentModel,
|
|
870
985
|
verifyChatGPTEffortSelection,
|
|
871
986
|
verifyChatGPTModelSelection,
|
|
872
987
|
waitForChatGPTAttachment,
|
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
selectChatTab,
|
|
15
15
|
selectGitHubTool,
|
|
16
16
|
selectModel,
|
|
17
|
+
verifyCurrentModel,
|
|
17
18
|
typePrompt,
|
|
18
19
|
verifyChatGPTEffortSelection,
|
|
19
20
|
verifyChatGPTModelSelection,
|
|
@@ -165,7 +166,7 @@ async function dispatch(options) {
|
|
|
165
166
|
let modelVerified = null;
|
|
166
167
|
let effortVerified = null;
|
|
167
168
|
if (model) {
|
|
168
|
-
modelVerified = await selectModel(cdp, model, 8000, signal);
|
|
169
|
+
modelVerified = await selectModel(cdp, inputCdp, model, 8000, signal);
|
|
169
170
|
log(`Verified model: ${modelVerified}`);
|
|
170
171
|
}
|
|
171
172
|
if (file) {
|
|
@@ -214,9 +215,13 @@ async function dispatch(options) {
|
|
|
214
215
|
await typePrompt(cdp, inputCdp, prompt, signal);
|
|
215
216
|
log("Prompt typed");
|
|
216
217
|
if (effort) {
|
|
217
|
-
effortVerified = await selectEffort(cdp, effort, 8000, signal);
|
|
218
|
+
effortVerified = await selectEffort(cdp, inputCdp, effort, 8000, signal);
|
|
218
219
|
log(`Verified effort: ${effortVerified}`);
|
|
219
220
|
}
|
|
221
|
+
if (model) {
|
|
222
|
+
modelVerified = await verifyCurrentModel(cdp, inputCdp, model, 8000, signal);
|
|
223
|
+
log(`Reverified model before submit: ${modelVerified}`);
|
|
224
|
+
}
|
|
220
225
|
const baseline = normalizeResponseSnapshot(await readChatGPTResponseSnapshot(cdp));
|
|
221
226
|
if (beforeSubmit) await raceAbort(beforeSubmit, signal);
|
|
222
227
|
await clickSend(cdp, inputCdp, signal);
|