codex-chatgpt-control 0.5.0-alpha.1 → 0.5.1-alpha.2
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/CHANGELOG.md +22 -0
- package/contracts/v1/fixtures/backend-capabilities.json +1 -0
- package/contracts/v1/fixtures/command-descriptors.json +29 -2
- package/contracts/v1/fixtures/doctor-bridge-upload.json +2 -2
- package/contracts/v1/fixtures/doctor-scenario-preflight.json +26 -4
- package/contracts/v1/fixtures/files-attach-indeterminate.json +30 -0
- package/contracts/v1/fixtures/help-root.json +1 -1
- package/contracts/v1/fixtures/messages-stop-indeterminate.json +31 -0
- package/contracts/v1/fixtures/messages-stop-needs-confirmation.json +20 -0
- package/contracts/v1/fixtures/messages-stop-noop.json +19 -0
- package/contracts/v1/fixtures/messages-stop-success.json +21 -0
- package/contracts/v1/fixtures/primitive-bootstrap-blocker.json +3 -3
- package/contracts/v1/fixtures/reports-create-redacted.json +3 -3
- package/contracts/v1/fixtures/reports-summarize-redacted.json +1 -1
- package/contracts/v1/fixtures/run-browser-bridge-blocker.json +12 -12
- package/contracts/v1/fixtures/stream-blocked.ndjson +2 -2
- package/contracts/v1/manifest.json +25 -0
- package/contracts/v1/parity-suite.json +19 -1
- package/dist/codex-chatgpt-control-backend.mjs +3654 -603
- package/dist/codex-chatgpt-control.bundle.mjs +3627 -546
- package/dist/src/backend/client.d.ts +2 -1
- package/dist/src/backend/client.js +1 -0
- package/dist/src/backend/protocol.d.ts +1 -1
- package/dist/src/backend/protocol.js +1 -0
- package/dist/src/backend/session.js +2 -0
- package/dist/src/browser/attach.d.ts +1 -0
- package/dist/src/browser/attach.js +69 -35
- package/dist/src/browser/chatgpt-url.d.ts +8 -0
- package/dist/src/browser/chatgpt-url.js +28 -0
- package/dist/src/browser/downloads.d.ts +1 -1
- package/dist/src/browser/downloads.js +13 -4
- package/dist/src/browser/page-state.js +211 -4
- package/dist/src/client.d.ts +2 -1
- package/dist/src/client.js +2 -1
- package/dist/src/commands/configuration.js +330 -73
- package/dist/src/commands/context.d.ts +5 -1
- package/dist/src/commands/context.js +2 -2
- package/dist/src/commands/conversation.js +13 -1
- package/dist/src/commands/doctor.js +24 -2
- package/dist/src/commands/experience.d.ts +1 -0
- package/dist/src/commands/experience.js +156 -23
- package/dist/src/commands/files.d.ts +1 -0
- package/dist/src/commands/files.js +877 -170
- package/dist/src/commands/messages.d.ts +2 -1
- package/dist/src/commands/messages.js +310 -2
- package/dist/src/commands/modes.js +197 -52
- package/dist/src/commands/registry.js +8 -0
- package/dist/src/commands/sequence.js +3 -1
- package/dist/src/commands/session.d.ts +4 -1
- package/dist/src/commands/session.js +34 -3
- package/dist/src/dom/generation-state.d.ts +6 -1
- package/dist/src/dom/generation-state.js +206 -45
- package/dist/src/dom/locale/am.d.ts +23 -1
- package/dist/src/dom/locale/am.js +23 -1
- package/dist/src/dom/locale/ar.d.ts +21 -1
- package/dist/src/dom/locale/ar.js +24 -4
- package/dist/src/dom/locale/bg.d.ts +19 -1
- package/dist/src/dom/locale/bg.js +22 -4
- package/dist/src/dom/locale/bn.d.ts +23 -1
- package/dist/src/dom/locale/bn.js +23 -1
- package/dist/src/dom/locale/bs.d.ts +17 -1
- package/dist/src/dom/locale/bs.js +21 -5
- package/dist/src/dom/locale/ca.d.ts +20 -1
- package/dist/src/dom/locale/ca.js +25 -6
- package/dist/src/dom/locale/cs.d.ts +17 -1
- package/dist/src/dom/locale/cs.js +17 -1
- package/dist/src/dom/locale/da.d.ts +17 -1
- package/dist/src/dom/locale/da.js +18 -2
- package/dist/src/dom/locale/de.d.ts +16 -1
- package/dist/src/dom/locale/de.js +18 -3
- package/dist/src/dom/locale/el.d.ts +18 -1
- package/dist/src/dom/locale/el.js +21 -4
- package/dist/src/dom/locale/en.d.ts +2 -1
- package/dist/src/dom/locale/en.js +2 -1
- package/dist/src/dom/locale/es-419.d.ts +18 -1
- package/dist/src/dom/locale/es-419.js +18 -1
- package/dist/src/dom/locale/es-ES.d.ts +18 -1
- package/dist/src/dom/locale/es-ES.js +18 -1
- package/dist/src/dom/locale/et.d.ts +16 -1
- package/dist/src/dom/locale/et.js +16 -1
- package/dist/src/dom/locale/fa.d.ts +21 -1
- package/dist/src/dom/locale/fa.js +23 -3
- package/dist/src/dom/locale/fi.d.ts +18 -1
- package/dist/src/dom/locale/fi.js +18 -1
- package/dist/src/dom/locale/fr-CA.d.ts +15 -1
- package/dist/src/dom/locale/fr-CA.js +15 -1
- package/dist/src/dom/locale/fr-FR.d.ts +18 -1
- package/dist/src/dom/locale/fr-FR.js +22 -5
- package/dist/src/dom/locale/gu.d.ts +23 -1
- package/dist/src/dom/locale/gu.js +27 -5
- package/dist/src/dom/locale/hi.d.ts +23 -1
- package/dist/src/dom/locale/hi.js +25 -3
- package/dist/src/dom/locale/hr.d.ts +16 -1
- package/dist/src/dom/locale/hr.js +19 -4
- package/dist/src/dom/locale/hu.d.ts +17 -1
- package/dist/src/dom/locale/hu.js +20 -4
- package/dist/src/dom/locale/hy.d.ts +23 -1
- package/dist/src/dom/locale/hy.js +26 -4
- package/dist/src/dom/locale/id.d.ts +17 -1
- package/dist/src/dom/locale/id.js +19 -3
- package/dist/src/dom/locale/index.js +1 -0
- package/dist/src/dom/locale/is.d.ts +21 -1
- package/dist/src/dom/locale/is.js +21 -1
- package/dist/src/dom/locale/it.d.ts +15 -1
- package/dist/src/dom/locale/it.js +20 -6
- package/dist/src/dom/locale/ja.d.ts +19 -1
- package/dist/src/dom/locale/ja.js +23 -5
- package/dist/src/dom/locale/ka.d.ts +23 -1
- package/dist/src/dom/locale/ka.js +23 -1
- package/dist/src/dom/locale/kk.d.ts +23 -1
- package/dist/src/dom/locale/kk.js +25 -3
- package/dist/src/dom/locale/kn.d.ts +22 -1
- package/dist/src/dom/locale/kn.js +26 -5
- package/dist/src/dom/locale/ko.d.ts +17 -1
- package/dist/src/dom/locale/ko.js +17 -1
- package/dist/src/dom/locale/lt.d.ts +18 -1
- package/dist/src/dom/locale/lt.js +20 -3
- package/dist/src/dom/locale/lv.d.ts +18 -1
- package/dist/src/dom/locale/lv.js +18 -1
- package/dist/src/dom/locale/mk.d.ts +22 -1
- package/dist/src/dom/locale/mk.js +26 -5
- package/dist/src/dom/locale/ml.d.ts +23 -1
- package/dist/src/dom/locale/ml.js +25 -3
- package/dist/src/dom/locale/mn.d.ts +23 -1
- package/dist/src/dom/locale/mn.js +23 -1
- package/dist/src/dom/locale/mr.d.ts +23 -1
- package/dist/src/dom/locale/mr.js +25 -3
- package/dist/src/dom/locale/ms.d.ts +20 -1
- package/dist/src/dom/locale/ms.js +20 -1
- package/dist/src/dom/locale/my.d.ts +22 -1
- package/dist/src/dom/locale/my.js +24 -3
- package/dist/src/dom/locale/nb.d.ts +17 -1
- package/dist/src/dom/locale/nb.js +17 -1
- package/dist/src/dom/locale/nl.d.ts +20 -2
- package/dist/src/dom/locale/nl.js +22 -4
- package/dist/src/dom/locale/pa.d.ts +23 -1
- package/dist/src/dom/locale/pa.js +27 -5
- package/dist/src/dom/locale/pl.d.ts +17 -1
- package/dist/src/dom/locale/pl.js +19 -3
- package/dist/src/dom/locale/pt-BR.d.ts +18 -1
- package/dist/src/dom/locale/pt-BR.js +20 -3
- package/dist/src/dom/locale/pt-PT.d.ts +18 -1
- package/dist/src/dom/locale/pt-PT.js +22 -5
- package/dist/src/dom/locale/ro.d.ts +15 -1
- package/dist/src/dom/locale/ro.js +18 -4
- package/dist/src/dom/locale/ru.d.ts +23 -1
- package/dist/src/dom/locale/ru.js +24 -2
- package/dist/src/dom/locale/sk.d.ts +17 -1
- package/dist/src/dom/locale/sk.js +19 -3
- package/dist/src/dom/locale/sl.d.ts +21 -1
- package/dist/src/dom/locale/sl.js +24 -4
- package/dist/src/dom/locale/so.d.ts +22 -1
- package/dist/src/dom/locale/so.js +24 -3
- package/dist/src/dom/locale/sq.d.ts +21 -1
- package/dist/src/dom/locale/sq.js +25 -5
- package/dist/src/dom/locale/sr.d.ts +24 -1
- package/dist/src/dom/locale/sr.js +25 -2
- package/dist/src/dom/locale/sv.d.ts +16 -1
- package/dist/src/dom/locale/sv.js +16 -1
- package/dist/src/dom/locale/sw.d.ts +18 -1
- package/dist/src/dom/locale/sw.js +18 -1
- package/dist/src/dom/locale/ta.d.ts +23 -1
- package/dist/src/dom/locale/ta.js +27 -5
- package/dist/src/dom/locale/te.d.ts +23 -1
- package/dist/src/dom/locale/te.js +27 -5
- package/dist/src/dom/locale/th.d.ts +11 -1
- package/dist/src/dom/locale/th.js +11 -1
- package/dist/src/dom/locale/tl.d.ts +8 -1
- package/dist/src/dom/locale/tl.js +8 -1
- package/dist/src/dom/locale/tr.d.ts +21 -1
- package/dist/src/dom/locale/tr.js +23 -3
- package/dist/src/dom/locale/types.d.ts +1 -1
- package/dist/src/dom/locale/uk.d.ts +23 -1
- package/dist/src/dom/locale/uk.js +25 -3
- package/dist/src/dom/locale/ur.d.ts +22 -1
- package/dist/src/dom/locale/ur.js +25 -4
- package/dist/src/dom/locale/vi.d.ts +22 -1
- package/dist/src/dom/locale/vi.js +25 -4
- package/dist/src/dom/locale/zh-HK.d.ts +21 -1
- package/dist/src/dom/locale/zh-HK.js +23 -3
- package/dist/src/dom/locale/zh-Hans.d.ts +23 -1
- package/dist/src/dom/locale/zh-Hans.js +27 -5
- package/dist/src/dom/locale/zh-TW.d.ts +23 -1
- package/dist/src/dom/locale/zh-TW.js +25 -3
- package/dist/src/dom/menus.js +26 -2
- package/dist/src/dom/selectors.d.ts +1 -0
- package/dist/src/dom/selectors.js +9 -1
- package/dist/src/dom/wait-snapshot.d.ts +1 -0
- package/dist/src/dom/wait-snapshot.js +45 -22
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js +2 -2
- package/dist/src/platform/local-paths.d.ts +1 -0
- package/dist/src/platform/local-paths.js +7 -3
- package/dist/src/safety/risk.d.ts +1 -0
- package/dist/src/safety/risk.js +1 -0
- package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +10 -1
- package/dist/src/scripts/apply-intelligence-locale-captures.js +219 -7
- package/dist/src/scripts/capture-intelligence-locales.d.ts +119 -1
- package/dist/src/scripts/capture-intelligence-locales.js +653 -232
- package/dist/src/scripts/capture-surface-profile.d.ts +3 -1
- package/dist/src/scripts/capture-surface-profile.js +75 -25
- package/dist/src/scripts/live-smoke/scenarios.d.ts +3 -0
- package/dist/src/scripts/live-smoke/scenarios.js +321 -13
- package/dist/src/scripts/live-smoke.js +1 -0
- package/dist/src/scripts/locale-capture/surface-graph.d.ts +31 -0
- package/dist/src/scripts/locale-capture/surface-graph.js +50 -0
- package/dist/src/scripts/release-canary-module.d.ts +20 -0
- package/dist/src/scripts/release-canary-module.js +86 -0
- package/dist/src/types.d.ts +30 -6
- package/package.json +8 -2
- package/references/2026-07-16-chat-work-surfaces.md +13 -0
- package/references/2026-08-08-model-selection-locale-sweep.md +32 -0
- package/references/backend-protocol.md +22 -7
- package/references/language-coverage.md +24 -13
- package/references/localization.md +36 -14
- package/references/python-parity.md +9 -3
- package/references/troubleshooting.md +26 -4
|
@@ -5,9 +5,27 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Some non-Intelligence surfaces may still fall back to English + `selector_drift`.
|
|
7
7
|
*
|
|
8
|
-
* Intelligence picker labels updated 2026-06-10
|
|
8
|
+
* Intelligence picker labels updated 2026-06-10, stop-control labels updated 2026-06-15, Chat/Work surface labels updated 2026-07-17, and Power/Advanced selector labels updated 2026-08-08 from visible ChatGPT sessions.
|
|
9
9
|
*/
|
|
10
10
|
export const zhTW = {
|
|
11
|
+
configurationAxes: {
|
|
12
|
+
power: ["能力"],
|
|
13
|
+
model: ["模型"],
|
|
14
|
+
effort: ["推理強度"],
|
|
15
|
+
speed: ["速度"],
|
|
16
|
+
advanced: ["進階"],
|
|
17
|
+
},
|
|
18
|
+
configurationOptions: {
|
|
19
|
+
instant: ["即時"],
|
|
20
|
+
light: ["快思"],
|
|
21
|
+
medium: ["中"],
|
|
22
|
+
high: ["高"],
|
|
23
|
+
extraHigh: ["極高", "超高"],
|
|
24
|
+
max: ["最高"],
|
|
25
|
+
ultra: ["極致"],
|
|
26
|
+
standard: ["標準"],
|
|
27
|
+
fast: ["快速"],
|
|
28
|
+
},
|
|
11
29
|
composerTextbox: ["與 ChatGPT 對話"],
|
|
12
30
|
sendButton: ["傳送提示詞"],
|
|
13
31
|
searchChatsButton: ["搜尋對話"],
|
|
@@ -17,15 +35,19 @@ export const zhTW = {
|
|
|
17
35
|
addFilesOpenerCandidates: ["新增檔案等更多功能"],
|
|
18
36
|
addPhotosFilesMenuItem: ["新增照片和檔案"],
|
|
19
37
|
copyResponse: ["複製回應"],
|
|
20
|
-
modeLabels: ["即時", "中等", "高", "超高", "專業"],
|
|
38
|
+
modeLabels: ["即時", "中等", "高", "超高", "專業", "中"],
|
|
21
39
|
modeOptions: {
|
|
22
40
|
instant: ["即時"],
|
|
23
|
-
medium: ["中等"],
|
|
41
|
+
medium: ["中等", "中"],
|
|
24
42
|
high: ["高"],
|
|
25
43
|
extraHigh: ["超高"],
|
|
26
44
|
pro: ["專業"],
|
|
27
45
|
},
|
|
28
46
|
modeOpenerExtra: ["設定"],
|
|
47
|
+
experienceOptions: {
|
|
48
|
+
chat: ["對話"],
|
|
49
|
+
work: ["工作"],
|
|
50
|
+
},
|
|
29
51
|
tools: {
|
|
30
52
|
web_search: ["網頁搜尋"],
|
|
31
53
|
deep_research: ["深入研究"],
|
package/dist/src/dom/menus.js
CHANGED
|
@@ -10,6 +10,28 @@ export function extractMenuItemsFromText(text) {
|
|
|
10
10
|
export async function enumerateVisibleMenuItems(page) {
|
|
11
11
|
if (typeof page.evaluate === "function") {
|
|
12
12
|
const labels = await page.evaluate(() => {
|
|
13
|
+
const visible = (element) => {
|
|
14
|
+
const html = element;
|
|
15
|
+
const rect = html.getBoundingClientRect?.();
|
|
16
|
+
if (rect !== undefined && (rect.width <= 0 || rect.height <= 0))
|
|
17
|
+
return false;
|
|
18
|
+
let current = element;
|
|
19
|
+
while (current !== null) {
|
|
20
|
+
if (current.hasAttribute?.("inert") || current.getAttribute?.("aria-hidden") === "true") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const style = typeof window !== "undefined"
|
|
24
|
+
? window.getComputedStyle?.(current)
|
|
25
|
+
: undefined;
|
|
26
|
+
if (style?.display === "none"
|
|
27
|
+
|| style?.visibility === "hidden"
|
|
28
|
+
|| style?.opacity === "0") {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
current = current.parentElement ?? null;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
};
|
|
13
35
|
const toItem = (node) => {
|
|
14
36
|
const element = node;
|
|
15
37
|
const label = (element.innerText ?? element.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
@@ -37,13 +59,14 @@ export async function enumerateVisibleMenuItems(page) {
|
|
|
37
59
|
item.ariaLabel = ariaLabel;
|
|
38
60
|
return item;
|
|
39
61
|
};
|
|
40
|
-
const allRoleNodes = Array.from(document.querySelectorAll("[role='menuitem'], [role='menuitemradio'], [role='option']"))
|
|
62
|
+
const allRoleNodes = Array.from(document.querySelectorAll("[role='menuitem'], [role='menuitemradio'], [role='option']"))
|
|
63
|
+
.filter(visible);
|
|
41
64
|
// Scope to open menu containers when any exist, so stray role items elsewhere on
|
|
42
65
|
// the page (sidebar rows, decorative listboxes) cannot contaminate menu matching.
|
|
43
66
|
// An empty scoped set falls back to the unscoped list: real menus keep their items
|
|
44
67
|
// inside the container, so an empty intersection means the container heuristic failed.
|
|
45
68
|
const containers = Array.from(document.querySelectorAll("[role='menu'], [role='listbox'], [data-radix-popper-content-wrapper]"))
|
|
46
|
-
.filter(container => typeof container.contains === "function");
|
|
69
|
+
.filter(container => visible(container) && typeof container.contains === "function");
|
|
47
70
|
const scopedRoleNodes = containers.length > 0
|
|
48
71
|
? allRoleNodes.filter(node => containers.some(container => container.contains(node)))
|
|
49
72
|
: allRoleNodes;
|
|
@@ -54,6 +77,7 @@ export async function enumerateVisibleMenuItems(page) {
|
|
|
54
77
|
return { items: roleItems, labels: [], split: false };
|
|
55
78
|
}
|
|
56
79
|
const menus = Array.from(document.querySelectorAll("[role='menu'], [role='listbox'], [data-radix-popper-content-wrapper]"))
|
|
80
|
+
.filter(visible)
|
|
57
81
|
.map(node => node.innerText ?? node.textContent ?? "")
|
|
58
82
|
.filter(Boolean);
|
|
59
83
|
return { items: [], labels: menus, split: true };
|
|
@@ -10,6 +10,7 @@ export declare const cssSelectors: {
|
|
|
10
10
|
};
|
|
11
11
|
export declare function composerTextbox(page: PageLike): LocatorLike;
|
|
12
12
|
export declare function sendButton(page: PageLike): LocatorLike;
|
|
13
|
+
export declare function stopGenerationButton(page: PageLike): LocatorLike;
|
|
13
14
|
export declare function searchChatsButton(page: PageLike): LocatorLike;
|
|
14
15
|
export declare function searchChatsInput(page: PageLike): LocatorLike;
|
|
15
16
|
export declare function newChatButton(page: PageLike): LocatorLike;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { anyLabelPattern, localeLabels } from "./locale-labels.js";
|
|
1
|
+
import { anyLabelPattern, escapeRegExp, localeLabels } from "./locale-labels.js";
|
|
2
2
|
// Language-sensitive label tokens are sourced from the locale registry; the structural
|
|
3
3
|
// clauses (download attributes, file-backend hrefs, blob/data sources) are language-agnostic
|
|
4
4
|
// and stay literal. For a single English candidate the generated selectors are identical to
|
|
@@ -57,6 +57,14 @@ export function sendButton(page) {
|
|
|
57
57
|
}
|
|
58
58
|
return page.getByRole("button", { name: anyLabelPattern(localeLabels.sendButton) });
|
|
59
59
|
}
|
|
60
|
+
export function stopGenerationButton(page) {
|
|
61
|
+
if (typeof page.getByRole !== "function") {
|
|
62
|
+
return requiredLocator(page, "button[aria-label*='Stop'], button[title*='Stop']");
|
|
63
|
+
}
|
|
64
|
+
return page.getByRole("button", {
|
|
65
|
+
name: new RegExp(`^(?:${localeLabels.stopControl.map(escapeRegExp).join("|")})$`, "i")
|
|
66
|
+
});
|
|
67
|
+
}
|
|
60
68
|
export function searchChatsButton(page) {
|
|
61
69
|
if (typeof page.getByRole !== "function") {
|
|
62
70
|
return requiredLocator(page, "button");
|
|
@@ -53,35 +53,57 @@ export async function readWaitDomSnapshot(page) {
|
|
|
53
53
|
|| /^reading (?:the )?images?$/.test(trimmedForTransient);
|
|
54
54
|
// --- Generation state: mirrors dom/generation-state.ts readAssistantGenerationState ---
|
|
55
55
|
const isVisible = (element) => {
|
|
56
|
+
if (element.hidden || element.closest("[hidden], [inert], [aria-hidden='true']") !== null) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
56
59
|
const style = window.getComputedStyle(element);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
if (style.display === "none"
|
|
61
|
+
|| style.visibility === "hidden"
|
|
62
|
+
|| style.opacity === "0"
|
|
63
|
+
|| style.pointerEvents === "none") {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const rect = element.getBoundingClientRect();
|
|
67
|
+
return rect.width > 0 || rect.height > 0;
|
|
68
|
+
};
|
|
69
|
+
const elementValues = (element) => [
|
|
70
|
+
element.getAttribute("aria-label"),
|
|
71
|
+
element.getAttribute("title"),
|
|
72
|
+
element.innerText,
|
|
73
|
+
element.textContent
|
|
74
|
+
].map(normalizeLower).filter(Boolean);
|
|
75
|
+
const matchingLabels = (element, phrases) => {
|
|
76
|
+
const values = elementValues(element);
|
|
77
|
+
return phrases.filter(phrase => values.includes(normalizeLower(phrase)));
|
|
78
|
+
};
|
|
79
|
+
const isScopedStopControl = (button) => {
|
|
80
|
+
if (button.matches("[data-testid='stop-button'], [data-testid*='stop' i]"))
|
|
81
|
+
return true;
|
|
82
|
+
if (button.closest("[data-testid*='composer' i], [aria-label*='composer' i]") !== null)
|
|
83
|
+
return true;
|
|
84
|
+
const form = button.closest("form");
|
|
85
|
+
return form?.querySelector("textarea, [contenteditable='true'], [role='textbox']") !== null;
|
|
61
86
|
};
|
|
62
|
-
const
|
|
87
|
+
const visibleStopButtons = Array.from(document.querySelectorAll("button"))
|
|
63
88
|
.filter((button) => isVisible(button)
|
|
64
89
|
&& button.disabled !== true
|
|
65
|
-
&& button.getAttribute("aria-disabled") !== "true"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
button.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.map(phrase => phrase.toLowerCase())
|
|
78
|
-
.filter(phrase => text.includes(phrase)));
|
|
79
|
-
const activeSignals = matchingSignals(args.stop);
|
|
80
|
-
const stoppedSignals = matchingSignals(args.stopped);
|
|
90
|
+
&& button.getAttribute("aria-disabled") !== "true"
|
|
91
|
+
&& isScopedStopControl(button)
|
|
92
|
+
&& matchingLabels(button, args.send).length === 0
|
|
93
|
+
&& matchingLabels(button, args.stop).length > 0);
|
|
94
|
+
const activeSignals = [...new Set(visibleStopButtons.flatMap(button => matchingLabels(button, args.stop)))];
|
|
95
|
+
const latestAssistantTurn = latestAssistant?.closest("[data-testid^='conversation-turn']")
|
|
96
|
+
?? Array.from(document.querySelectorAll("[data-testid^='conversation-turn']")).at(-1);
|
|
97
|
+
const stoppedSignals = latestAssistantTurn === undefined
|
|
98
|
+
? []
|
|
99
|
+
: [...new Set(Array.from(latestAssistantTurn.querySelectorAll("button, [role='status'], [aria-label], [title], p, span, div"))
|
|
100
|
+
.filter(element => isVisible(element))
|
|
101
|
+
.flatMap(element => matchingLabels(element, args.stopped)))];
|
|
81
102
|
const generation = {
|
|
103
|
+
observed: true,
|
|
82
104
|
active: activeSignals.length > 0,
|
|
83
105
|
stopped: stoppedSignals.length > 0,
|
|
84
|
-
signals: [...new Set([...activeSignals, ...stoppedSignals
|
|
106
|
+
signals: [...new Set([...activeSignals, ...stoppedSignals])].slice(0, 5)
|
|
85
107
|
};
|
|
86
108
|
// --- Response actions: mirrors dom/generation-state.ts latestAssistantTurnHasResponseActions ---
|
|
87
109
|
const turns = Array.from(document.querySelectorAll("[data-testid^='conversation-turn']"));
|
|
@@ -122,6 +144,7 @@ export async function readWaitDomSnapshot(page) {
|
|
|
122
144
|
transient: [...localeLabels.transientAssistant],
|
|
123
145
|
stop: [...localeLabels.stopControl],
|
|
124
146
|
stopped: [...localeLabels.stoppedAssistant],
|
|
147
|
+
send: [...localeLabels.sendButton],
|
|
125
148
|
actions: [...localeLabels.responseActions]
|
|
126
149
|
});
|
|
127
150
|
}
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BlockerKind, CommandContext, CommandResult } from "./types.js";
|
|
2
2
|
type BlockerDetails = Partial<Omit<NonNullable<CommandResult["blocker"]>, "kind" | "message" | "visibleText">>;
|
|
3
|
-
export declare const BROWSER_BRIDGE_UNAVAILABLE_MESSAGE = "Codex cannot access the ChatGPT browser bridge from this backend process. In an ordinary shell this is expected; for a live Codex Chrome run,
|
|
3
|
+
export declare const BROWSER_BRIDGE_UNAVAILABLE_MESSAGE = "Codex cannot access the ChatGPT browser bridge from this backend process. In an ordinary shell this is expected; for a live Codex Chrome run, assign the Chrome plugin runtime returned by setupBrowserRuntime() to globalThis.agent before using it.";
|
|
4
4
|
export declare const BROWSER_BRIDGE_REMEDIATION: NonNullable<NonNullable<CommandResult["blocker"]>["remediation"]>;
|
|
5
5
|
export declare class ChatGPTControlError extends Error {
|
|
6
6
|
readonly kind: BlockerKind;
|
package/dist/src/errors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const BROWSER_BRIDGE_UNAVAILABLE_MESSAGE = "Codex cannot access the ChatGPT browser bridge from this backend process. In an ordinary shell this is expected; for a live Codex Chrome run,
|
|
1
|
+
export const BROWSER_BRIDGE_UNAVAILABLE_MESSAGE = "Codex cannot access the ChatGPT browser bridge from this backend process. In an ordinary shell this is expected; for a live Codex Chrome run, assign the Chrome plugin runtime returned by setupBrowserRuntime() to globalThis.agent before using it.";
|
|
2
2
|
export const BROWSER_BRIDGE_REMEDIATION = [
|
|
3
3
|
{
|
|
4
4
|
label: "Ordinary shell",
|
|
@@ -7,7 +7,7 @@ export const BROWSER_BRIDGE_REMEDIATION = [
|
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
label: "Codex Chrome bootstrap",
|
|
10
|
-
instruction: "For a live run, initialize the Chrome plugin runtime in node_repl with
|
|
10
|
+
instruction: "For a live run, initialize the Chrome plugin runtime in node_repl with globalThis.agent = await setupBrowserRuntime(), then set globalThis.browser = await agent.browsers.get(\"extension\") before calling createChatGPT({ agent: globalThis.agent }).",
|
|
11
11
|
userActionRequired: false
|
|
12
12
|
},
|
|
13
13
|
{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type HostPathPlatform = NodeJS.Platform;
|
|
2
|
+
export declare function currentHostPathPlatform(): HostPathPlatform;
|
|
2
3
|
export declare function isHostAbsolutePath(value: string, platform?: HostPathPlatform): boolean;
|
|
3
4
|
export declare function resolveForHostPath(value: string, platform?: HostPathPlatform): string;
|
|
4
5
|
export declare function basenameForHostPath(value: string, platform?: HostPathPlatform): string;
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
|
|
2
|
+
import { platform as readHostPlatform } from "node:os";
|
|
3
|
+
export function currentHostPathPlatform() {
|
|
4
|
+
return readHostPlatform();
|
|
5
|
+
}
|
|
6
|
+
export function isHostAbsolutePath(value, platform = currentHostPathPlatform()) {
|
|
3
7
|
if (value.length === 0)
|
|
4
8
|
return false;
|
|
5
9
|
if (platform === "win32")
|
|
6
10
|
return isFullyQualifiedWindowsPath(value);
|
|
7
11
|
return path.posix.isAbsolute(value);
|
|
8
12
|
}
|
|
9
|
-
export function resolveForHostPath(value, platform =
|
|
13
|
+
export function resolveForHostPath(value, platform = currentHostPathPlatform()) {
|
|
10
14
|
if (!isHostAbsolutePath(value, platform)) {
|
|
11
15
|
throw new Error(`File attachment path must be absolute for the backend host: ${value}`);
|
|
12
16
|
}
|
|
13
17
|
return platform === "win32" ? path.win32.resolve(value) : path.posix.resolve(value);
|
|
14
18
|
}
|
|
15
|
-
export function basenameForHostPath(value, platform =
|
|
19
|
+
export function basenameForHostPath(value, platform = currentHostPathPlatform()) {
|
|
16
20
|
return platform === "win32" ? path.win32.basename(value) : path.posix.basename(value);
|
|
17
21
|
}
|
|
18
22
|
function isFullyQualifiedWindowsPath(value) {
|
|
@@ -19,6 +19,7 @@ export declare const commandRisk: {
|
|
|
19
19
|
readonly "messages.wait": "low";
|
|
20
20
|
readonly "messages.readLatest": "medium";
|
|
21
21
|
readonly "messages.status": "medium";
|
|
22
|
+
readonly "messages.stop": "medium";
|
|
22
23
|
readonly "messages.waitAndRead": "medium";
|
|
23
24
|
readonly "artifacts.listLatest": "medium";
|
|
24
25
|
readonly "artifacts.wait": "low";
|
package/dist/src/safety/risk.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
type IntelligenceModeOptionId = "instant" | "medium" | "high" | "extraHigh" | "pro";
|
|
2
|
+
type ExperienceOptionId = "chat" | "work";
|
|
3
|
+
type ConfigurationAxisId = "model" | "intelligence" | "effort" | "speed" | "power" | "advanced";
|
|
4
|
+
type ConfigurationOptionId = "instant" | "light" | "medium" | "high" | "extraHigh" | "max" | "ultra" | "pro" | "standard" | "fast";
|
|
5
|
+
type SurfaceContribution = {
|
|
6
|
+
workComposerTextbox: string[];
|
|
7
|
+
experienceOptions: Partial<Record<ExperienceOptionId, string[]>>;
|
|
8
|
+
configurationAxes: Partial<Record<ConfigurationAxisId, string[]>>;
|
|
9
|
+
configurationOptions: Partial<Record<ConfigurationOptionId, string[]>>;
|
|
10
|
+
};
|
|
2
11
|
export declare function main(argv?: string[]): Promise<number>;
|
|
3
12
|
export declare function mergeCapture(source: string, labels: readonly string[], modeOptions: Partial<Record<IntelligenceModeOptionId, string[]>>, generationLabels?: {
|
|
4
13
|
stopControl?: readonly string[];
|
|
5
14
|
stoppedAssistant?: readonly string[];
|
|
6
|
-
}): string;
|
|
15
|
+
}, surface?: SurfaceContribution): string;
|
|
7
16
|
export {};
|
|
@@ -4,6 +4,11 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { readLanguageCoverage } from "./locale-capture/language-coverage.js";
|
|
5
5
|
const ENGLISH_MODE_LABELS = new Set(["Latest", "Instant", "Thinking", "Extended", "Medium", "High", "Extra High", "Pro"]);
|
|
6
6
|
const ENGLISH_STOP_CONTROL = new Set(["stop generating", "stop streaming", "stop answering", "cancel"]);
|
|
7
|
+
// These labels are unsafe regardless of language provenance: they are generic
|
|
8
|
+
// controls that can describe Send, dialog dismissal, or unrelated cancellation.
|
|
9
|
+
// Keep this separate from the English-exclusion set so changing the canonical
|
|
10
|
+
// English registry can never make them eligible for a locale stop selector.
|
|
11
|
+
const UNSAFE_GENERIC_STOP_CONTROLS = new Set(["cancel"]);
|
|
7
12
|
const ENGLISH_STOPPED_ASSISTANT = new Set(["stopped thinking", "stopped answering", "generation stopped"]);
|
|
8
13
|
const INTELLIGENCE_MODE_OPTION_IDS = ["instant", "medium", "high", "extraHigh", "pro"];
|
|
9
14
|
const ENGLISH_INTELLIGENCE_MODE_OPTIONS = {
|
|
@@ -13,7 +18,37 @@ const ENGLISH_INTELLIGENCE_MODE_OPTIONS = {
|
|
|
13
18
|
extraHigh: "Extra High",
|
|
14
19
|
pro: "Pro",
|
|
15
20
|
};
|
|
16
|
-
const
|
|
21
|
+
const EXPERIENCE_OPTION_IDS = ["chat", "work"];
|
|
22
|
+
const WORK_CONFIGURATION_AXIS_IDS = ["model", "effort", "speed"];
|
|
23
|
+
const CHAT_CONFIGURATION_AXIS_IDS = ["model", "effort"];
|
|
24
|
+
const CONFIGURATION_AXIS_IDS = ["power", "model", "intelligence", "effort", "speed", "advanced"];
|
|
25
|
+
const CONFIGURATION_OPTION_IDS = ["instant", "light", "medium", "high", "extraHigh", "max", "ultra", "pro", "standard", "fast"];
|
|
26
|
+
const CHAT_EFFORT_OPTION_IDS = ["instant", "medium", "high", "extraHigh", "pro"];
|
|
27
|
+
const EFFORT_OPTION_IDS = ["light", "medium", "high", "extraHigh", "max", "ultra"];
|
|
28
|
+
const SPEED_OPTION_IDS = ["standard", "fast"];
|
|
29
|
+
const ENGLISH_EXPERIENCE_OPTIONS = new Set(["chat", "quick chat", "work"]);
|
|
30
|
+
const ENGLISH_CONFIGURATION_AXES = {
|
|
31
|
+
model: "Model",
|
|
32
|
+
intelligence: "Intelligence",
|
|
33
|
+
effort: "Effort",
|
|
34
|
+
speed: "Speed",
|
|
35
|
+
power: "Power",
|
|
36
|
+
advanced: "Advanced"
|
|
37
|
+
};
|
|
38
|
+
const ENGLISH_CONFIGURATION_OPTIONS = {
|
|
39
|
+
instant: "Instant",
|
|
40
|
+
light: "Light",
|
|
41
|
+
medium: "Medium",
|
|
42
|
+
high: "High",
|
|
43
|
+
extraHigh: "Extra High",
|
|
44
|
+
max: "Max",
|
|
45
|
+
ultra: "Ultra",
|
|
46
|
+
pro: "Pro",
|
|
47
|
+
standard: "Standard",
|
|
48
|
+
fast: "Fast",
|
|
49
|
+
};
|
|
50
|
+
const ENGLISH_COMPOSER_LABELS = new Set(["chat with chatgpt", "ask chatgpt", "work on anything", "work on something"]);
|
|
51
|
+
const UPDATE_NOTE = " * Intelligence picker labels updated 2026-06-10, stop-control labels updated 2026-06-15, Chat/Work surface labels updated 2026-07-17, and Power/Advanced selector labels updated 2026-08-08 from visible ChatGPT sessions.";
|
|
17
52
|
class ApplyUsageError extends Error {
|
|
18
53
|
exitCode;
|
|
19
54
|
constructor(message, exitCode = 2) {
|
|
@@ -58,7 +93,15 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
58
93
|
const modeOptions = observedNonEnglishModeOptions(record);
|
|
59
94
|
const stopControl = observedNonEnglishGenerationLabels(record.generationStopLabels, ENGLISH_STOP_CONTROL);
|
|
60
95
|
const stoppedAssistant = observedNonEnglishGenerationLabels(record.generationStoppedLabels, ENGLISH_STOPPED_ASSISTANT);
|
|
61
|
-
|
|
96
|
+
const surface = observedNonEnglishSurface(record);
|
|
97
|
+
if (labels.length === 0
|
|
98
|
+
&& Object.keys(modeOptions).length === 0
|
|
99
|
+
&& stopControl.length === 0
|
|
100
|
+
&& stoppedAssistant.length === 0
|
|
101
|
+
&& surface.workComposerTextbox.length === 0
|
|
102
|
+
&& Object.keys(surface.experienceOptions).length === 0
|
|
103
|
+
&& Object.keys(surface.configurationAxes).length === 0
|
|
104
|
+
&& Object.keys(surface.configurationOptions).length === 0)
|
|
62
105
|
continue;
|
|
63
106
|
planned.push({
|
|
64
107
|
locale: language.bcp47,
|
|
@@ -67,6 +110,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
67
110
|
modeOptions,
|
|
68
111
|
stopControl,
|
|
69
112
|
stoppedAssistant,
|
|
113
|
+
surface,
|
|
70
114
|
});
|
|
71
115
|
}
|
|
72
116
|
if (!options.reviewed) {
|
|
@@ -81,10 +125,10 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
81
125
|
const after = mergeCapture(before, change.labels, change.modeOptions, {
|
|
82
126
|
stopControl: change.stopControl,
|
|
83
127
|
stoppedAssistant: change.stoppedAssistant,
|
|
84
|
-
});
|
|
128
|
+
}, change.surface);
|
|
85
129
|
if (after !== before) {
|
|
86
130
|
await writeFile(change.file, after, "utf8");
|
|
87
|
-
console.log(`updated ${change.locale} labels=${change.labels.length} modeOptions=${Object.keys(change.modeOptions).length} stopControl=${change.stopControl.length} stoppedAssistant=${change.stoppedAssistant.length}`);
|
|
131
|
+
console.log(`updated ${change.locale} labels=${change.labels.length} modeOptions=${Object.keys(change.modeOptions).length} surfaces=${surfaceContributionCount(change.surface)} stopControl=${change.stopControl.length} stoppedAssistant=${change.stoppedAssistant.length}`);
|
|
88
132
|
}
|
|
89
133
|
}
|
|
90
134
|
return 0;
|
|
@@ -130,9 +174,95 @@ function observedNonEnglishModeOptions(record) {
|
|
|
130
174
|
function observedNonEnglishGenerationLabels(values, englishValues) {
|
|
131
175
|
return dedupe((values ?? [])
|
|
132
176
|
.map(value => value.replace(/\s+/g, " ").trim())
|
|
133
|
-
.filter(value => value.length > 0
|
|
177
|
+
.filter(value => value.length > 0
|
|
178
|
+
&& !englishValues.has(value.toLowerCase())
|
|
179
|
+
&& !UNSAFE_GENERIC_STOP_CONTROLS.has(value.toLowerCase())));
|
|
134
180
|
}
|
|
135
|
-
|
|
181
|
+
function observedNonEnglishSurface(record) {
|
|
182
|
+
const empty = {
|
|
183
|
+
workComposerTextbox: [],
|
|
184
|
+
experienceOptions: {},
|
|
185
|
+
configurationAxes: {},
|
|
186
|
+
configurationOptions: {},
|
|
187
|
+
};
|
|
188
|
+
const capture = record.surfaceCapture;
|
|
189
|
+
if (capture === undefined)
|
|
190
|
+
return empty;
|
|
191
|
+
if (capture.status !== "ok" || capture.chat === undefined || capture.work === undefined || !capture.restoredChat) {
|
|
192
|
+
throw new ApplyUsageError(`${record.requestedLocale} surface capture was not successful and restored: ${capture.blocker?.message ?? "unknown blocker"}.`, 1);
|
|
193
|
+
}
|
|
194
|
+
const contribution = { ...empty };
|
|
195
|
+
contribution.experienceOptions = {
|
|
196
|
+
...nonEnglishSlot("chat", capture.chat.optionLabel, ENGLISH_EXPERIENCE_OPTIONS),
|
|
197
|
+
...nonEnglishSlot("work", capture.work.optionLabel, ENGLISH_EXPERIENCE_OPTIONS),
|
|
198
|
+
};
|
|
199
|
+
const sharedComposerLabels = new Set(capture.chat.composerLabels.map(normalizedLower));
|
|
200
|
+
contribution.workComposerTextbox = dedupe(capture.work.composerLabels
|
|
201
|
+
.map(normalized)
|
|
202
|
+
.filter(label => label.length > 0
|
|
203
|
+
&& !sharedComposerLabels.has(normalizedLower(label))
|
|
204
|
+
&& !ENGLISH_COMPOSER_LABELS.has(normalizedLower(label))));
|
|
205
|
+
addLocalizedConfigurationAxis(contribution.configurationAxes, "power", capture.chat.power?.axisLabel);
|
|
206
|
+
addLocalizedConfigurationAxis(contribution.configurationAxes, "power", capture.work.power?.axisLabel);
|
|
207
|
+
addLocalizedConfigurationAxis(contribution.configurationAxes, "advanced", capture.chat.advanced?.label);
|
|
208
|
+
addLocalizedConfigurationAxis(contribution.configurationAxes, "advanced", capture.work.advanced?.label);
|
|
209
|
+
const chatRows = capture.chat.configurationRows;
|
|
210
|
+
if (chatRows !== undefined) {
|
|
211
|
+
if (chatRows.length !== CHAT_CONFIGURATION_AXIS_IDS.length
|
|
212
|
+
|| chatRows.some((row, index) => row.axis !== CHAT_CONFIGURATION_AXIS_IDS[index])) {
|
|
213
|
+
throw new ApplyUsageError(`${record.requestedLocale} did not capture the ordered Chat Model/Effort rows.`, 1);
|
|
214
|
+
}
|
|
215
|
+
for (const row of chatRows) {
|
|
216
|
+
addLocalizedConfigurationAxis(contribution.configurationAxes, row.axis, row.axisLabel);
|
|
217
|
+
if (row.axis === "effort") {
|
|
218
|
+
assignOrderedLocalizedOptions(record.requestedLocale, row.options, CHAT_EFFORT_OPTION_IDS, contribution.configurationOptions, "Chat Effort");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const rows = capture.work.configurationRows;
|
|
223
|
+
if (rows.length !== WORK_CONFIGURATION_AXIS_IDS.length
|
|
224
|
+
|| rows.some((row, index) => row.axis !== WORK_CONFIGURATION_AXIS_IDS[index])) {
|
|
225
|
+
throw new ApplyUsageError(`${record.requestedLocale} did not capture the ordered Model/Effort/Speed rows.`, 1);
|
|
226
|
+
}
|
|
227
|
+
for (const row of rows) {
|
|
228
|
+
addLocalizedConfigurationAxis(contribution.configurationAxes, row.axis, row.axisLabel);
|
|
229
|
+
if (row.axis === "effort") {
|
|
230
|
+
assignOrderedLocalizedOptions(record.requestedLocale, row.options, EFFORT_OPTION_IDS, contribution.configurationOptions, "Effort");
|
|
231
|
+
}
|
|
232
|
+
else if (row.axis === "speed") {
|
|
233
|
+
assignOrderedLocalizedOptions(record.requestedLocale, row.options, SPEED_OPTION_IDS, contribution.configurationOptions, "Speed");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return contribution;
|
|
237
|
+
}
|
|
238
|
+
function addLocalizedConfigurationAxis(target, axis, value) {
|
|
239
|
+
const label = normalized(value);
|
|
240
|
+
if (label.length === 0 || label === ENGLISH_CONFIGURATION_AXES[axis])
|
|
241
|
+
return;
|
|
242
|
+
target[axis] = dedupe([...(target[axis] ?? []), label]);
|
|
243
|
+
}
|
|
244
|
+
function assignOrderedLocalizedOptions(locale, options, ids, target, axis) {
|
|
245
|
+
if (options.length === 0 || options.length > ids.length) {
|
|
246
|
+
throw new ApplyUsageError(`${locale} captured ${options.length} ${axis} options; expected 1-${ids.length}.`, 1);
|
|
247
|
+
}
|
|
248
|
+
options.forEach((option, index) => {
|
|
249
|
+
const id = ids[index];
|
|
250
|
+
const label = normalized(option.label);
|
|
251
|
+
if (label.length > 0 && label !== ENGLISH_CONFIGURATION_OPTIONS[id]) {
|
|
252
|
+
target[id] = dedupe([...(target[id] ?? []), label]);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function nonEnglishSlot(id, value, englishValues) {
|
|
257
|
+
const label = normalized(value);
|
|
258
|
+
return label.length > 0 && !englishValues.has(normalizedLower(label)) ? { [id]: [label] } : {};
|
|
259
|
+
}
|
|
260
|
+
export function mergeCapture(source, labels, modeOptions, generationLabels = {}, surface = {
|
|
261
|
+
workComposerTextbox: [],
|
|
262
|
+
experienceOptions: {},
|
|
263
|
+
configurationAxes: {},
|
|
264
|
+
configurationOptions: {},
|
|
265
|
+
}) {
|
|
136
266
|
let text = updateComment(source);
|
|
137
267
|
if (labels.length > 0) {
|
|
138
268
|
const existing = parseExistingModeLabels(text);
|
|
@@ -149,6 +279,10 @@ export function mergeCapture(source, labels, modeOptions, generationLabels = {})
|
|
|
149
279
|
}
|
|
150
280
|
}
|
|
151
281
|
text = mergeModeOptions(text, modeOptions);
|
|
282
|
+
text = mergeTopLevelStringArrayProperty(text, "workComposerTextbox", surface.workComposerTextbox, "composerTextbox");
|
|
283
|
+
text = mergeNestedOptions(text, "experienceOptions", EXPERIENCE_OPTION_IDS, surface.experienceOptions, "modeOpenerExtra");
|
|
284
|
+
text = mergeNestedOptions(text, "configurationAxes", CONFIGURATION_AXIS_IDS, surface.configurationAxes, "experienceOptions");
|
|
285
|
+
text = mergeNestedOptions(text, "configurationOptions", CONFIGURATION_OPTION_IDS, surface.configurationOptions, "configurationAxes");
|
|
152
286
|
text = mergeStringArrayProperty(text, "stopControl", generationLabels.stopControl ?? []);
|
|
153
287
|
text = mergeStringArrayProperty(text, "stoppedAssistant", generationLabels.stoppedAssistant ?? []);
|
|
154
288
|
return text;
|
|
@@ -220,13 +354,71 @@ function formatModeOptions(modeOptions) {
|
|
|
220
354
|
}
|
|
221
355
|
return [" modeOptions: {", ...lines, " },"].join("\n");
|
|
222
356
|
}
|
|
223
|
-
function
|
|
357
|
+
function mergeTopLevelStringArrayProperty(source, property, values, insertAfterProperty) {
|
|
224
358
|
if (values.length === 0)
|
|
225
359
|
return source;
|
|
226
360
|
const existing = parseExistingStringArrayProperty(source, property);
|
|
227
361
|
const merged = dedupe([...existing, ...values]);
|
|
228
362
|
const line = ` ${property}: [${merged.map(value => JSON.stringify(value)).join(", ")}],`;
|
|
229
363
|
const propertyPattern = new RegExp(`^\\s*${property}:\\s*\\[[^\\]]*\\],`, "m");
|
|
364
|
+
if (propertyPattern.test(source))
|
|
365
|
+
return source.replace(propertyPattern, line);
|
|
366
|
+
const anchor = new RegExp(`^(\\s*${insertAfterProperty}:\\s*[^\\n]+,\\n)`, "m");
|
|
367
|
+
if (anchor.test(source))
|
|
368
|
+
return source.replace(anchor, `$1${line}\n`);
|
|
369
|
+
return source.replace(/^export const \w+ = \{\n/m, match => `${match}${line}\n`);
|
|
370
|
+
}
|
|
371
|
+
function mergeNestedOptions(source, property, ids, additions, insertAfterProperty) {
|
|
372
|
+
const existing = parseNestedOptions(source, property, ids);
|
|
373
|
+
const merged = {};
|
|
374
|
+
for (const id of ids) {
|
|
375
|
+
const values = dedupe([...(existing[id] ?? []), ...(additions[id] ?? [])]);
|
|
376
|
+
if (values.length > 0)
|
|
377
|
+
merged[id] = values;
|
|
378
|
+
}
|
|
379
|
+
const lines = ids.flatMap(id => {
|
|
380
|
+
const values = merged[id];
|
|
381
|
+
return values === undefined || values.length === 0
|
|
382
|
+
? []
|
|
383
|
+
: [` ${id}: [${values.map(value => JSON.stringify(value)).join(", ")}],`];
|
|
384
|
+
});
|
|
385
|
+
if (lines.length === 0)
|
|
386
|
+
return source;
|
|
387
|
+
const block = [` ${property}: {`, ...lines, " },"].join("\n");
|
|
388
|
+
const blockPattern = new RegExp(`^\\s*${property}:\\s*\\{[\\s\\S]*?^\\s*\\},\\n`, "m");
|
|
389
|
+
if (blockPattern.test(source))
|
|
390
|
+
return source.replace(blockPattern, `${block}\n`);
|
|
391
|
+
const nestedAnchor = new RegExp(`^(\\s*${insertAfterProperty}:\\s*\\{[\\s\\S]*?^\\s*\\},\\n)`, "m");
|
|
392
|
+
if (nestedAnchor.test(source))
|
|
393
|
+
return source.replace(nestedAnchor, `$1${block}\n`);
|
|
394
|
+
const lineAnchor = new RegExp(`^(\\s*${insertAfterProperty}:\\s*[^\\n]+,\\n)`, "m");
|
|
395
|
+
if (lineAnchor.test(source))
|
|
396
|
+
return source.replace(lineAnchor, `$1${block}\n`);
|
|
397
|
+
return source.replace(/^export const \w+ = \{\n/m, match => `${match}${block}\n`);
|
|
398
|
+
}
|
|
399
|
+
function parseNestedOptions(source, property, ids) {
|
|
400
|
+
const result = {};
|
|
401
|
+
const block = new RegExp(`^\\s*${property}:\\s*\\{(?<body>[\\s\\S]*?)^\\s*\\},`, "m").exec(source)?.groups?.body;
|
|
402
|
+
if (block === undefined)
|
|
403
|
+
return result;
|
|
404
|
+
for (const id of ids) {
|
|
405
|
+
const body = new RegExp(`^\\s*${id}:\\s*\\[(?<body>[^\\]]*)\\],`, "m").exec(block)?.groups?.body;
|
|
406
|
+
if (body === undefined)
|
|
407
|
+
continue;
|
|
408
|
+
const values = parseJsonStringList(body);
|
|
409
|
+
if (values.length > 0)
|
|
410
|
+
result[id] = values;
|
|
411
|
+
}
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
414
|
+
function mergeStringArrayProperty(source, property, values) {
|
|
415
|
+
if (values.length === 0)
|
|
416
|
+
return source;
|
|
417
|
+
const existing = parseExistingStringArrayProperty(source, property);
|
|
418
|
+
const merged = dedupe([...existing, ...values])
|
|
419
|
+
.filter(value => property !== "stopControl" || !UNSAFE_GENERIC_STOP_CONTROLS.has(value.toLowerCase()));
|
|
420
|
+
const line = ` ${property}: [${merged.map(value => JSON.stringify(value)).join(", ")}],`;
|
|
421
|
+
const propertyPattern = new RegExp(`^\\s*${property}:\\s*\\[[^\\]]*\\],`, "m");
|
|
230
422
|
if (propertyPattern.test(source)) {
|
|
231
423
|
return source.replace(propertyPattern, line);
|
|
232
424
|
}
|
|
@@ -251,11 +443,31 @@ function parseExistingStringArrayProperty(source, property) {
|
|
|
251
443
|
}
|
|
252
444
|
function updateComment(source) {
|
|
253
445
|
let text = source.replace(/\n \* Omitted because they match English case-insensitively: `modeLabels`[\s\S]*?blocker copy\.\n/g, "\n * Some non-Intelligence surfaces may still fall back to English + `selector_drift`.\n");
|
|
446
|
+
text = text.replace(/^ \* Intelligence picker labels updated 2026-06-10[^\n]*$/m, UPDATE_NOTE);
|
|
254
447
|
if (!text.includes(UPDATE_NOTE)) {
|
|
255
448
|
text = text.replace(/\n \*\//, `\n *\n${UPDATE_NOTE}\n */`);
|
|
256
449
|
}
|
|
257
450
|
return text;
|
|
258
451
|
}
|
|
452
|
+
function parseJsonStringList(body) {
|
|
453
|
+
const values = [];
|
|
454
|
+
for (const stringMatch of body.matchAll(/"((?:\\"|[^"])*)"/g)) {
|
|
455
|
+
values.push(JSON.parse(`"${stringMatch[1]}"`));
|
|
456
|
+
}
|
|
457
|
+
return values;
|
|
458
|
+
}
|
|
459
|
+
function normalized(value) {
|
|
460
|
+
return (value ?? "").replace(/\s+/g, " ").trim();
|
|
461
|
+
}
|
|
462
|
+
function normalizedLower(value) {
|
|
463
|
+
return normalized(value).toLocaleLowerCase();
|
|
464
|
+
}
|
|
465
|
+
function surfaceContributionCount(surface) {
|
|
466
|
+
return surface.workComposerTextbox.length
|
|
467
|
+
+ Object.keys(surface.experienceOptions).length
|
|
468
|
+
+ Object.keys(surface.configurationAxes).length
|
|
469
|
+
+ Object.keys(surface.configurationOptions).length;
|
|
470
|
+
}
|
|
259
471
|
function dedupe(values) {
|
|
260
472
|
const seen = new Set();
|
|
261
473
|
const result = [];
|