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
|
@@ -6,7 +6,7 @@ import type { ChatGPTResponsesCreateArgs } from "../runner/responses.js";
|
|
|
6
6
|
import type { ChatGPTRunStream } from "../runner/stream.js";
|
|
7
7
|
import type { ChatGPTAgent, ChatGPTAgentConfig, ChatGPTResponse, ChatGPTRunInput, ChatGPTRunResult } from "../runner/types.js";
|
|
8
8
|
import type { ReportRedactionOptions } from "../safety/report-redaction.js";
|
|
9
|
-
import type { ArtifactDownloadArgs, ArtifactWaitArgs, ApplyConfigurationData, ApplyConfigurationArgs, BootstrapArgs, ChatGPTExperience, CommandResult, ConfigurationInspectionData, CopyResponseArgs, DetectExperienceArgs, DetectExperienceData, DownloadLatestArgs, GetModeArgs, GetModeData, InspectConfigurationArgs, ListArtifactsArgs, MessageStatusArgs, NewThreadArgs, OpenThreadArgs, OpenExperienceData, ReadLatestArgs, ReadWorkLatestArgs, ReadWorkLatestData, SearchThreadsArgs, SelectToolArgs, SequencePlan, SetModeArgs, StartWorkArgs, StartWorkData, SteerWorkArgs, SteerWorkData, WaitArgs, WorkStatusArgs, WorkStatusData, WorkWaitArgs, WorkWaitData } from "../types.js";
|
|
9
|
+
import type { ArtifactDownloadArgs, ArtifactWaitArgs, ApplyConfigurationData, ApplyConfigurationArgs, BootstrapArgs, ChatGPTExperience, CommandResult, ConfigurationInspectionData, CopyResponseArgs, DetectExperienceArgs, DetectExperienceData, DownloadLatestArgs, GetModeArgs, GetModeData, InspectConfigurationArgs, ListArtifactsArgs, MessageStatusArgs, StopGenerationArgs, StopGenerationData, NewThreadArgs, OpenThreadArgs, OpenExperienceData, ReadLatestArgs, ReadWorkLatestArgs, ReadWorkLatestData, SearchThreadsArgs, SelectToolArgs, SequencePlan, SetModeArgs, StartWorkArgs, StartWorkData, SteerWorkArgs, SteerWorkData, WaitArgs, WorkStatusArgs, WorkStatusData, WorkWaitArgs, WorkWaitData } from "../types.js";
|
|
10
10
|
import { type BackendEvent, type BackendRequest, type BackendResponse } from "./protocol.js";
|
|
11
11
|
export type BackendTransport = {
|
|
12
12
|
request(request: BackendRequest): Promise<BackendResponse>;
|
|
@@ -108,6 +108,7 @@ export type ChatGPTBackendClient = {
|
|
|
108
108
|
wait(args?: WaitArgs): Promise<CommandResult<unknown>>;
|
|
109
109
|
readLatest(args?: ReadLatestArgs): Promise<CommandResult<unknown>>;
|
|
110
110
|
status(args?: MessageStatusArgs): Promise<CommandResult<unknown>>;
|
|
111
|
+
stop(args: StopGenerationArgs): Promise<CommandResult<StopGenerationData>>;
|
|
111
112
|
waitAndRead(args?: WaitArgs & ReadLatestArgs): Promise<CommandResult<unknown>>;
|
|
112
113
|
};
|
|
113
114
|
files: {
|
|
@@ -95,6 +95,7 @@ export function createChatGPTBackendClient(transport) {
|
|
|
95
95
|
wait: args => request("messages.wait", args ?? {}),
|
|
96
96
|
readLatest: args => request("messages.readLatest", args ?? {}),
|
|
97
97
|
status: args => request("messages.status", args ?? {}),
|
|
98
|
+
stop: args => request("messages.stop", args),
|
|
98
99
|
waitAndRead: args => request("messages.waitAndRead", args)
|
|
99
100
|
},
|
|
100
101
|
artifacts: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const BACKEND_REQUEST_SCHEMA_VERSION: "chatgpt.browser_control.backend_request.v1";
|
|
2
2
|
export declare const BACKEND_RESPONSE_SCHEMA_VERSION: "chatgpt.browser_control.backend_response.v1";
|
|
3
3
|
export declare const BACKEND_EVENT_SCHEMA_VERSION: "chatgpt.browser_control.backend_event.v1";
|
|
4
|
-
export declare const backendCommands: readonly ["backend.version", "backend.health", "backend.capabilities", "runner.run", "runner.plan", "runner.stream", "responses.create", "ask", "askInThread", "askWithFiles", "askAndDownload", "runMessages", "openThread", "readLatest", "copyLatest", "downloadLatest", "runPlan", "doctor", "createReport", "reports.create", "reports.redact", "reports.summarize", "commands", "describe", "help", "session.bootstrap", "experience.detect", "experience.open", "configuration.inspect", "configuration.apply", "work.start", "work.status", "work.wait", "work.steer", "work.readLatest", "threads.new", "threads.search", "threads.open", "messages.compose", "messages.submit", "messages.ask", "messages.wait", "messages.readLatest", "messages.status", "messages.waitAndRead", "artifacts.listLatest", "artifacts.wait", "artifacts.downloadLatest", "files.preflight", "files.attach", "files.downloadLatest", "projects.sources.list", "projects.sources.planAdd", "projects.sources.add", "modes.set", "modes.get", "tools.select", "response.copy"];
|
|
4
|
+
export declare const backendCommands: readonly ["backend.version", "backend.health", "backend.capabilities", "runner.run", "runner.plan", "runner.stream", "responses.create", "ask", "askInThread", "askWithFiles", "askAndDownload", "runMessages", "openThread", "readLatest", "copyLatest", "downloadLatest", "runPlan", "doctor", "createReport", "reports.create", "reports.redact", "reports.summarize", "commands", "describe", "help", "session.bootstrap", "experience.detect", "experience.open", "configuration.inspect", "configuration.apply", "work.start", "work.status", "work.wait", "work.steer", "work.readLatest", "threads.new", "threads.search", "threads.open", "messages.compose", "messages.submit", "messages.ask", "messages.wait", "messages.readLatest", "messages.status", "messages.stop", "messages.waitAndRead", "artifacts.listLatest", "artifacts.wait", "artifacts.downloadLatest", "files.preflight", "files.attach", "files.downloadLatest", "projects.sources.list", "projects.sources.planAdd", "projects.sources.add", "modes.set", "modes.get", "tools.select", "response.copy"];
|
|
5
5
|
export type BackendCommand = typeof backendCommands[number];
|
|
6
6
|
export type BackendRequest = {
|
|
7
7
|
schemaVersion: typeof BACKEND_REQUEST_SCHEMA_VERSION;
|
|
@@ -159,6 +159,8 @@ async function dispatchBackendCommand(client, request) {
|
|
|
159
159
|
return client.messages.readLatest(emptyToUndefined(payload));
|
|
160
160
|
case "messages.status":
|
|
161
161
|
return client.messages.status(emptyToUndefined(payload));
|
|
162
|
+
case "messages.stop":
|
|
163
|
+
return client.messages.stop(payload);
|
|
162
164
|
case "messages.waitAndRead":
|
|
163
165
|
return client.messages.waitAndRead(payload);
|
|
164
166
|
case "artifacts.listLatest":
|
|
@@ -6,4 +6,5 @@ export type AttachedBrowser = {
|
|
|
6
6
|
tabId?: string;
|
|
7
7
|
};
|
|
8
8
|
export declare function attachChatGPTBrowser(env: RuntimeEnv, args?: BootstrapArgs): Promise<AttachedBrowser>;
|
|
9
|
+
export { isChatGPTUrl } from "./chatgpt-url.js";
|
|
9
10
|
export declare function tabIdFromPage(page: PageLike): string | undefined;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { BrowserBridgeUnavailableError, ChatGPTControlError, LoginRequiredError } from "../errors.js";
|
|
2
|
+
import { CHATGPT_HOME, isChatGPTUrl } from "./chatgpt-url.js";
|
|
2
3
|
import { parseConversationId, readPageState } from "./page-state.js";
|
|
3
|
-
const CHATGPT_HOME = "https://chatgpt.com/";
|
|
4
|
-
const CHATGPT_HOSTS = new Set(["chatgpt.com", "www.chatgpt.com", "chat.openai.com"]);
|
|
5
4
|
const MAX_EXISTING_TAB_DIAGNOSTIC_CANDIDATES = 10;
|
|
6
5
|
const MAX_EXISTING_TAB_DIAGNOSTIC_FIELD_LENGTH = 240;
|
|
7
6
|
export async function attachChatGPTBrowser(env, args = {}) {
|
|
8
7
|
const browser = await getBrowser(env);
|
|
9
8
|
const page = await getOrCreateChatGPTPage(browser, env, args);
|
|
9
|
+
await assertPageOnChatGPTOrigin(page);
|
|
10
10
|
const state = await readPageState(page);
|
|
11
|
+
if (!isChatGPTUrl(state.url))
|
|
12
|
+
throw unsafeChatGPTOriginError();
|
|
11
13
|
if (state.blocker?.kind === "login_required") {
|
|
12
14
|
throw new LoginRequiredError(state.blocker.visibleText);
|
|
13
15
|
}
|
|
@@ -95,6 +97,7 @@ async function tryBrowserGetPreferredListed(browsers) {
|
|
|
95
97
|
}
|
|
96
98
|
async function getOrCreateChatGPTPage(browser, env, args) {
|
|
97
99
|
const targetUrl = args.url ?? CHATGPT_HOME;
|
|
100
|
+
assertSafeChatGPTNavigation(targetUrl);
|
|
98
101
|
const explicitExistingPolicy = normalizeExplicitExistingTabPolicy(args);
|
|
99
102
|
if (env.page !== undefined) {
|
|
100
103
|
const cached = normalizePage(env.page);
|
|
@@ -149,7 +152,7 @@ function normalizeExplicitExistingTabPolicy(args) {
|
|
|
149
152
|
if (args.existingTab === true) {
|
|
150
153
|
return {
|
|
151
154
|
target: { type: "selected", host: "chatgpt" },
|
|
152
|
-
ifMissing: "
|
|
155
|
+
ifMissing: "block",
|
|
153
156
|
ifMultiple: "first",
|
|
154
157
|
requireChatGPT: true
|
|
155
158
|
};
|
|
@@ -187,6 +190,21 @@ async function selectExistingTab(browser, policy) {
|
|
|
187
190
|
}
|
|
188
191
|
}
|
|
189
192
|
}
|
|
193
|
+
if (typeof browser.tabs?.list === "function") {
|
|
194
|
+
const controlled = await Promise.resolve(browser.tabs.list.call(browser.tabs)).catch(() => []);
|
|
195
|
+
const matches = [];
|
|
196
|
+
for (const candidate of controlled) {
|
|
197
|
+
const page = await hydrateTab(browser, candidate);
|
|
198
|
+
if (await pageMatchesExistingTarget(page, policy))
|
|
199
|
+
matches.push(page);
|
|
200
|
+
}
|
|
201
|
+
if (matches.length === 1 || (matches.length > 1 && (policy.ifMultiple ?? "block") === "first")) {
|
|
202
|
+
return { page: matches[0] };
|
|
203
|
+
}
|
|
204
|
+
if (matches.length > 1) {
|
|
205
|
+
throw new ExistingTabSelectionError("Multiple already-controlled ChatGPT tabs matched the requested existing-tab target.", "existing_tab_ambiguous");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
190
208
|
return userMatch.diagnostics === undefined
|
|
191
209
|
? { diagnostics: diagnosticsForUnavailableUserTabs(policy) }
|
|
192
210
|
: userMatch;
|
|
@@ -213,6 +231,7 @@ async function selectExistingUserTab(browser, policy, collectDiagnostics) {
|
|
|
213
231
|
}
|
|
214
232
|
const selected = matches[0];
|
|
215
233
|
const page = normalizePage(await claimTab.call(browser.user, selected));
|
|
234
|
+
await assertPageOnChatGPTOrigin(page);
|
|
216
235
|
return diagnostics === undefined ? { page } : { page, diagnostics };
|
|
217
236
|
}
|
|
218
237
|
function userTabMatchesTarget(tab, policy) {
|
|
@@ -340,14 +359,10 @@ async function pageMatchesExistingTarget(page, policy) {
|
|
|
340
359
|
return userTabMatchesTarget(tab, policy);
|
|
341
360
|
}
|
|
342
361
|
async function findExistingChatGPTTab(browser) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}, false).catch(() => ({ page: undefined }));
|
|
348
|
-
if (userTab.page !== undefined) {
|
|
349
|
-
return userTab.page;
|
|
350
|
-
}
|
|
362
|
+
// Reuse a tab already controlled by this browser session before attempting
|
|
363
|
+
// to claim an external user tab. Claiming a tab that is still associated
|
|
364
|
+
// with an interrupted host call can otherwise wait on a stale control lock
|
|
365
|
+
// until the next bounded browser call is killed.
|
|
351
366
|
const selected = browser.tabs?.selected;
|
|
352
367
|
if (typeof selected === "function") {
|
|
353
368
|
try {
|
|
@@ -355,7 +370,7 @@ async function findExistingChatGPTTab(browser) {
|
|
|
355
370
|
if (current !== undefined) {
|
|
356
371
|
const normalized = normalizePage(current);
|
|
357
372
|
try {
|
|
358
|
-
if ((await normalized.url?.())
|
|
373
|
+
if (isChatGPTUrl(await normalized.url?.())) {
|
|
359
374
|
return normalized;
|
|
360
375
|
}
|
|
361
376
|
}
|
|
@@ -369,20 +384,33 @@ async function findExistingChatGPTTab(browser) {
|
|
|
369
384
|
}
|
|
370
385
|
}
|
|
371
386
|
const list = browser.tabs?.list;
|
|
372
|
-
if (typeof list
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
387
|
+
if (typeof list === "function") {
|
|
388
|
+
const tabs = await list.call(browser.tabs);
|
|
389
|
+
const normalized = await Promise.all(tabs.map(tab => hydrateTab(browser, tab)));
|
|
390
|
+
for (const tab of normalized) {
|
|
391
|
+
try {
|
|
392
|
+
if (isChatGPTUrl(await tab.url?.())) {
|
|
393
|
+
return tab;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// Keep looking.
|
|
381
398
|
}
|
|
382
399
|
}
|
|
383
|
-
|
|
384
|
-
|
|
400
|
+
}
|
|
401
|
+
const userTab = await selectExistingUserTab(browser, {
|
|
402
|
+
target: { type: "selected", host: "chatgpt" },
|
|
403
|
+
ifMultiple: "first",
|
|
404
|
+
requireChatGPT: true
|
|
405
|
+
}, false).catch(error => {
|
|
406
|
+
if (error instanceof ChatGPTControlError
|
|
407
|
+
&& error.blockerDetails.code === "unsafe_chatgpt_origin") {
|
|
408
|
+
throw error;
|
|
385
409
|
}
|
|
410
|
+
return { page: undefined };
|
|
411
|
+
});
|
|
412
|
+
if (userTab.page !== undefined) {
|
|
413
|
+
return userTab.page;
|
|
386
414
|
}
|
|
387
415
|
return undefined;
|
|
388
416
|
}
|
|
@@ -422,17 +450,7 @@ function targetRequiresChatGPT(target) {
|
|
|
422
450
|
return true;
|
|
423
451
|
}
|
|
424
452
|
}
|
|
425
|
-
|
|
426
|
-
if (url === undefined) {
|
|
427
|
-
return false;
|
|
428
|
-
}
|
|
429
|
-
try {
|
|
430
|
-
return CHATGPT_HOSTS.has(new URL(url).hostname);
|
|
431
|
-
}
|
|
432
|
-
catch {
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
453
|
+
export { isChatGPTUrl } from "./chatgpt-url.js";
|
|
436
454
|
function urlMatches(actual, expected) {
|
|
437
455
|
if (actual === undefined) {
|
|
438
456
|
return false;
|
|
@@ -485,6 +503,7 @@ function userTabCandidateLabel(tab) {
|
|
|
485
503
|
return `tab ${tab.id} - ${tab.title ?? "Untitled"} - ${tab.url ?? "unknown URL"}`;
|
|
486
504
|
}
|
|
487
505
|
async function createTab(browser, url) {
|
|
506
|
+
assertSafeChatGPTNavigation(url);
|
|
488
507
|
if (typeof browser.tabs?.create === "function") {
|
|
489
508
|
const tab = await browser.tabs.create(url);
|
|
490
509
|
const page = await hydrateTab(browser, tab);
|
|
@@ -502,18 +521,33 @@ async function createTab(browser, url) {
|
|
|
502
521
|
if (typeof page.goto === "function") {
|
|
503
522
|
await page.goto(url);
|
|
504
523
|
}
|
|
524
|
+
await assertPageOnChatGPTOrigin(page);
|
|
505
525
|
return page;
|
|
506
526
|
}
|
|
507
527
|
return undefined;
|
|
508
528
|
}
|
|
529
|
+
function assertSafeChatGPTNavigation(url) {
|
|
530
|
+
if (isChatGPTUrl(url))
|
|
531
|
+
return;
|
|
532
|
+
throw unsafeChatGPTOriginError("ChatGPT navigation requires HTTPS on an allowlisted ChatGPT origin with the default port.");
|
|
533
|
+
}
|
|
509
534
|
async function ensurePageAt(page, url) {
|
|
510
535
|
const currentUrl = await Promise.resolve(page.url?.()).catch(() => "");
|
|
511
|
-
if (currentUrl
|
|
536
|
+
if (isChatGPTUrl(currentUrl)) {
|
|
512
537
|
return;
|
|
513
538
|
}
|
|
514
539
|
if (typeof page.goto === "function") {
|
|
515
540
|
await page.goto(url);
|
|
516
541
|
}
|
|
542
|
+
await assertPageOnChatGPTOrigin(page);
|
|
543
|
+
}
|
|
544
|
+
async function assertPageOnChatGPTOrigin(page) {
|
|
545
|
+
const actualUrl = await Promise.resolve(page.url?.()).catch(() => undefined);
|
|
546
|
+
if (!isChatGPTUrl(actualUrl))
|
|
547
|
+
throw unsafeChatGPTOriginError();
|
|
548
|
+
}
|
|
549
|
+
function unsafeChatGPTOriginError(message = "The browser did not remain on a supported ChatGPT origin after navigation or attachment.") {
|
|
550
|
+
return new ChatGPTControlError(message, "selector_drift", false, undefined, { code: "unsafe_chatgpt_origin" });
|
|
517
551
|
}
|
|
518
552
|
function normalizeBrowser(browser) {
|
|
519
553
|
if (browser === undefined || browser === null || typeof browser !== "object") {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const CHATGPT_HOME = "https://chatgpt.com/";
|
|
2
|
+
/**
|
|
3
|
+
* Accept only the visible ChatGPT HTTPS origins supported by this SDK.
|
|
4
|
+
* URL credentials and non-default ports are rejected even when the hostname
|
|
5
|
+
* itself is allowlisted.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isChatGPTUrl(value: string | undefined): boolean;
|
|
8
|
+
export declare function requireChatGPTUrl(value: string, label: string): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const CHATGPT_HOME = "https://chatgpt.com/";
|
|
2
|
+
const CHATGPT_HOSTS = new Set(["chatgpt.com", "www.chatgpt.com", "chat.openai.com"]);
|
|
3
|
+
/**
|
|
4
|
+
* Accept only the visible ChatGPT HTTPS origins supported by this SDK.
|
|
5
|
+
* URL credentials and non-default ports are rejected even when the hostname
|
|
6
|
+
* itself is allowlisted.
|
|
7
|
+
*/
|
|
8
|
+
export function isChatGPTUrl(value) {
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
return false;
|
|
11
|
+
try {
|
|
12
|
+
const url = new URL(value);
|
|
13
|
+
return url.protocol === "https:"
|
|
14
|
+
&& url.port === ""
|
|
15
|
+
&& url.username === ""
|
|
16
|
+
&& url.password === ""
|
|
17
|
+
&& CHATGPT_HOSTS.has(url.hostname);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function requireChatGPTUrl(value, label) {
|
|
24
|
+
if (!isChatGPTUrl(value)) {
|
|
25
|
+
throw new Error(`${label} must use HTTPS on an allowlisted ChatGPT origin with the default port.`);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
@@ -4,4 +4,4 @@ export type DownloadLike = {
|
|
|
4
4
|
saveAs?: (path: string) => Promise<void>;
|
|
5
5
|
path?: () => Promise<string | null>;
|
|
6
6
|
};
|
|
7
|
-
export declare function waitForDownloadFromClick(page: PageLike, click: () => Promise<void>, destDir: string, timeoutMs: number): Promise<DownloadedFile>;
|
|
7
|
+
export declare function waitForDownloadFromClick(page: PageLike, click: () => Promise<void>, destDir: string, timeoutMs: number, filenameHint?: string): Promise<DownloadedFile>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { mkdir, stat } from "node:fs/promises";
|
|
1
|
+
import { copyFile, mkdir, stat } from "node:fs/promises";
|
|
2
2
|
import { basename, join, resolve } from "node:path";
|
|
3
3
|
import { localGuardTimeout, withTimeout } from "../commands/timeouts.js";
|
|
4
|
-
export async function waitForDownloadFromClick(page, click, destDir, timeoutMs) {
|
|
4
|
+
export async function waitForDownloadFromClick(page, click, destDir, timeoutMs, filenameHint) {
|
|
5
5
|
const absoluteDest = resolve(destDir);
|
|
6
6
|
await mkdir(absoluteDest, { recursive: true });
|
|
7
7
|
const downloadPromise = page.waitForEvent?.("download", { timeout: timeoutMs, timeoutMs });
|
|
@@ -10,13 +10,22 @@ export async function waitForDownloadFromClick(page, click, destDir, timeoutMs)
|
|
|
10
10
|
}
|
|
11
11
|
await withTimeout(click(), localGuardTimeout(timeoutMs, 10000), "Download control click did not complete before the local guard timeout.");
|
|
12
12
|
const download = await downloadPromise;
|
|
13
|
-
const
|
|
13
|
+
const sourcePath = typeof download.path === "function" ? await download.path() : null;
|
|
14
|
+
const suggestedFilename = filenameHint
|
|
15
|
+
?? download.suggestedFilename?.()
|
|
16
|
+
?? (sourcePath === null ? undefined : basename(sourcePath))
|
|
17
|
+
?? `chatgpt-download-${Date.now()}`;
|
|
14
18
|
const targetPath = join(absoluteDest, basename(suggestedFilename));
|
|
15
19
|
if (typeof download.saveAs === "function") {
|
|
16
20
|
await download.saveAs(targetPath);
|
|
17
21
|
}
|
|
22
|
+
else if (sourcePath !== null) {
|
|
23
|
+
if (resolve(sourcePath) !== resolve(targetPath)) {
|
|
24
|
+
await copyFile(sourcePath, targetPath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
18
27
|
else {
|
|
19
|
-
throw new Error("The browser download object
|
|
28
|
+
throw new Error("The browser download object exposes neither saveAs() nor a completed local path().");
|
|
20
29
|
}
|
|
21
30
|
const saved = await stat(targetPath);
|
|
22
31
|
if (saved.size <= 0) {
|
|
@@ -21,9 +21,15 @@ export async function readPageState(page) {
|
|
|
21
21
|
const url = typeof rawUrl === "string" ? rawUrl : "";
|
|
22
22
|
const rawTitle = typeof page.title === "function" ? await page.title().catch(() => undefined) : undefined;
|
|
23
23
|
const title = typeof rawTitle === "string" ? rawTitle : undefined;
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
24
|
+
const surface = await readPageSurfaceSnapshot(page);
|
|
25
|
+
const visibleText = surface.visibleText;
|
|
26
|
+
const blockerSurface = surface.blockerSurface;
|
|
27
|
+
const fullPageBlocker = classifyVisibleText(visibleText);
|
|
28
|
+
const classifiedBlocker = blockerSurface.hasConversationMessages
|
|
29
|
+
? classifyVisibleText(blockerSurface.text)
|
|
30
|
+
: (classifyVisibleText(blockerSurface.text) ?? fullPageBlocker);
|
|
31
|
+
const loginWall = classifiedBlocker?.kind === "login_required" && isLikelyLoginWall(visibleText);
|
|
32
|
+
const signedIn = isLikelySignedIn(visibleText) && !loginWall;
|
|
27
33
|
const blocker = classifiedBlocker?.kind === "login_required" && signedIn
|
|
28
34
|
? undefined
|
|
29
35
|
: classifiedBlocker;
|
|
@@ -64,8 +70,193 @@ export async function readVisibleText(page) {
|
|
|
64
70
|
}
|
|
65
71
|
return "";
|
|
66
72
|
}
|
|
73
|
+
async function readPageSurfaceSnapshot(page) {
|
|
74
|
+
if (typeof page.evaluate === "function") {
|
|
75
|
+
try {
|
|
76
|
+
const snapshot = await withTimeout(page.evaluate(() => {
|
|
77
|
+
const messageSelector = "[data-message-author-role], [data-testid^='conversation-turn']";
|
|
78
|
+
const systemSelector = [
|
|
79
|
+
"[role='alert']",
|
|
80
|
+
"[role='status']",
|
|
81
|
+
"[role='dialog']",
|
|
82
|
+
"[aria-live='assertive']",
|
|
83
|
+
"[data-testid*='toast' i]",
|
|
84
|
+
"[data-testid*='banner' i]",
|
|
85
|
+
"[class*='toast' i]",
|
|
86
|
+
"[class*='banner' i]"
|
|
87
|
+
].join(", ");
|
|
88
|
+
const visible = (element) => {
|
|
89
|
+
if (element.hidden || element.closest("[hidden], [inert], [aria-hidden='true']") !== null)
|
|
90
|
+
return false;
|
|
91
|
+
const style = window.getComputedStyle(element);
|
|
92
|
+
if (style.display === "none"
|
|
93
|
+
|| style.visibility === "hidden"
|
|
94
|
+
|| style.opacity === "0"
|
|
95
|
+
|| style.pointerEvents === "none")
|
|
96
|
+
return false;
|
|
97
|
+
const rect = element.getBoundingClientRect();
|
|
98
|
+
return rect.width > 0 || rect.height > 0;
|
|
99
|
+
};
|
|
100
|
+
const blockerText = Array.from(document.querySelectorAll(systemSelector))
|
|
101
|
+
.filter((element) => visible(element))
|
|
102
|
+
.filter(element => element.closest(messageSelector) === null)
|
|
103
|
+
.map(element => `${element.textContent ?? ""} ${element.getAttribute("aria-label") ?? ""}`)
|
|
104
|
+
.join(" ");
|
|
105
|
+
return {
|
|
106
|
+
visibleText: document.body?.innerText ?? "",
|
|
107
|
+
blockerText,
|
|
108
|
+
hasConversationMessages: Array.from(document.querySelectorAll(messageSelector)).some(visible)
|
|
109
|
+
};
|
|
110
|
+
}), 1000, "Timed out while reading the visible ChatGPT page surface.");
|
|
111
|
+
if (typeof snapshot === "string") {
|
|
112
|
+
return {
|
|
113
|
+
visibleText: snapshot,
|
|
114
|
+
blockerSurface: { text: snapshot, hasConversationMessages: false }
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (typeof snapshot === "object" && snapshot !== null
|
|
118
|
+
&& typeof snapshot.visibleText === "string"
|
|
119
|
+
&& typeof snapshot.blockerText === "string"
|
|
120
|
+
&& typeof snapshot.hasConversationMessages === "boolean") {
|
|
121
|
+
const typed = snapshot;
|
|
122
|
+
return {
|
|
123
|
+
visibleText: typed.visibleText,
|
|
124
|
+
blockerSurface: {
|
|
125
|
+
text: typed.blockerText,
|
|
126
|
+
hasConversationMessages: typed.hasConversationMessages
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Fall through to one serialized snapshot below.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (typeof page.content === "function") {
|
|
136
|
+
try {
|
|
137
|
+
const html = await withTimeout(page.content(), 1000, "Timed out while reading the serialized ChatGPT page surface.");
|
|
138
|
+
return serializedPageSurface(html);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Return an empty fail-closed snapshot below.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
visibleText: "",
|
|
146
|
+
blockerSurface: { text: "", hasConversationMessages: false }
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function serializedPageSurface(html) {
|
|
150
|
+
const stack = [];
|
|
151
|
+
const blockerNodes = [];
|
|
152
|
+
const voidElements = new Set([
|
|
153
|
+
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"
|
|
154
|
+
]);
|
|
155
|
+
let hasConversationMessages = false;
|
|
156
|
+
let visibleText = "";
|
|
157
|
+
const hidden = (attributes) => /(?:^|\s)(?:hidden|inert)(?:\s|=|$)/i.test(attributes)
|
|
158
|
+
|| /\baria-hidden\s*=\s*["']?true/i.test(attributes)
|
|
159
|
+
|| /\bstyle\s*=\s*["'][^"']*(?:display\s*:\s*none|visibility\s*:\s*hidden|opacity\s*:\s*0|pointer-events\s*:\s*none)/i.test(attributes);
|
|
160
|
+
const hiddenInChain = (node) => {
|
|
161
|
+
for (let current = node; current !== undefined; current = current.parent) {
|
|
162
|
+
if (hidden(current.attributes))
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
};
|
|
167
|
+
const messageNode = (node) => {
|
|
168
|
+
const authorRole = serializedAttribute(node.attributes, "data-message-author-role");
|
|
169
|
+
const testId = serializedAttribute(node.attributes, "data-testid");
|
|
170
|
+
return (authorRole !== undefined && authorRole.length > 0)
|
|
171
|
+
|| testId?.toLowerCase().startsWith("conversation-turn") === true;
|
|
172
|
+
};
|
|
173
|
+
const insideMessage = (node) => {
|
|
174
|
+
for (let current = node; current !== undefined; current = current.parent) {
|
|
175
|
+
if (messageNode(current))
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
};
|
|
180
|
+
const systemSurface = (node) => {
|
|
181
|
+
const role = serializedAttribute(node.attributes, "role")?.toLowerCase();
|
|
182
|
+
const ariaLive = serializedAttribute(node.attributes, "aria-live")?.toLowerCase();
|
|
183
|
+
const testId = serializedAttribute(node.attributes, "data-testid")?.toLowerCase() ?? "";
|
|
184
|
+
const className = serializedAttribute(node.attributes, "class")?.toLowerCase() ?? "";
|
|
185
|
+
return role === "alert"
|
|
186
|
+
|| role === "status"
|
|
187
|
+
|| role === "dialog"
|
|
188
|
+
|| ariaLive === "assertive"
|
|
189
|
+
|| testId.includes("toast")
|
|
190
|
+
|| testId.includes("banner")
|
|
191
|
+
|| className.includes("toast")
|
|
192
|
+
|| className.includes("banner");
|
|
193
|
+
};
|
|
194
|
+
const ignoredInChain = (node) => {
|
|
195
|
+
for (let current = node; current !== undefined; current = current.parent) {
|
|
196
|
+
if (current.tag === "script" || current.tag === "style" || current.tag === "template")
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
};
|
|
201
|
+
for (const match of html.matchAll(/<!--[\s\S]*?-->|<![^>]*>|<\/?[a-z0-9-]+\b[^>]*>|[^<]+/gi)) {
|
|
202
|
+
const token = match[0];
|
|
203
|
+
const closing = /^<\/([a-z0-9-]+)/i.exec(token);
|
|
204
|
+
if (closing?.[1] !== undefined) {
|
|
205
|
+
const tag = closing[1].toLowerCase();
|
|
206
|
+
const index = stack.map(node => node.tag).lastIndexOf(tag);
|
|
207
|
+
if (index >= 0)
|
|
208
|
+
stack.splice(index);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const opening = /^<([a-z0-9-]+)\b([^>]*)>/i.exec(token);
|
|
212
|
+
if (opening?.[1] !== undefined) {
|
|
213
|
+
const node = {
|
|
214
|
+
tag: opening[1].toLowerCase(),
|
|
215
|
+
attributes: opening[2] ?? "",
|
|
216
|
+
parent: stack.at(-1),
|
|
217
|
+
blockerText: ""
|
|
218
|
+
};
|
|
219
|
+
const ignored = ignoredInChain(node);
|
|
220
|
+
if (messageNode(node) && !hiddenInChain(node) && !ignored)
|
|
221
|
+
hasConversationMessages = true;
|
|
222
|
+
if (systemSurface(node) && !hiddenInChain(node) && !insideMessage(node) && !ignored) {
|
|
223
|
+
const ariaLabel = serializedAttribute(node.attributes, "aria-label");
|
|
224
|
+
if (ariaLabel !== undefined)
|
|
225
|
+
node.blockerText = ` ${ariaLabel}`;
|
|
226
|
+
blockerNodes.push(node);
|
|
227
|
+
}
|
|
228
|
+
if (!voidElements.has(node.tag) && !/\/\s*>$/.test(token))
|
|
229
|
+
stack.push(node);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const currentNode = stack.at(-1);
|
|
233
|
+
if (!hiddenInChain(currentNode) && !ignoredInChain(currentNode)) {
|
|
234
|
+
visibleText += ` ${token}`;
|
|
235
|
+
for (const node of blockerNodes) {
|
|
236
|
+
for (let current = currentNode; current !== undefined; current = current.parent) {
|
|
237
|
+
if (current === node) {
|
|
238
|
+
node.blockerText += ` ${token}`;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
visibleText: htmlToText(visibleText),
|
|
247
|
+
blockerSurface: {
|
|
248
|
+
text: blockerNodes.map(node => htmlToText(node.blockerText)).filter(Boolean).join(" "),
|
|
249
|
+
hasConversationMessages
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function serializedAttribute(attributes, name) {
|
|
254
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
255
|
+
const match = new RegExp(`\\b${escapedName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>` + "`" + `]+))`, "i").exec(attributes);
|
|
256
|
+
return match?.[1] ?? match?.[2] ?? match?.[3];
|
|
257
|
+
}
|
|
67
258
|
export function htmlToText(html) {
|
|
68
|
-
return html
|
|
259
|
+
return stripHiddenMarkup(html)
|
|
69
260
|
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
70
261
|
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
71
262
|
.replace(/<[^>]+>/g, " ")
|
|
@@ -76,7 +267,23 @@ export function htmlToText(html) {
|
|
|
76
267
|
.replace(/\s+/g, " ")
|
|
77
268
|
.trim();
|
|
78
269
|
}
|
|
270
|
+
function stripHiddenMarkup(html) {
|
|
271
|
+
let visible = html;
|
|
272
|
+
const hiddenElement = /<([a-z0-9-]+)\b[^>]*(?:\bhidden\b|\binert\b|\baria-hidden\s*=\s*["']?true|\bstyle\s*=\s*["'][^"']*(?:display\s*:\s*none|visibility\s*:\s*hidden|opacity\s*:\s*0))[^>]*>[\s\S]*?<\/\1>/gi;
|
|
273
|
+
for (let pass = 0; pass < 4; pass += 1) {
|
|
274
|
+
const next = visible.replace(hiddenElement, " ");
|
|
275
|
+
if (next === visible)
|
|
276
|
+
break;
|
|
277
|
+
visible = next;
|
|
278
|
+
}
|
|
279
|
+
return visible;
|
|
280
|
+
}
|
|
79
281
|
function isLikelySignedIn(visibleText) {
|
|
80
282
|
const markers = localeLabels.signedInMarkers.map(escapeRegExp).join("|");
|
|
81
283
|
return new RegExp(`\\b(${markers})\\b`, "i").test(visibleText);
|
|
82
284
|
}
|
|
285
|
+
function isLikelyLoginWall(visibleText) {
|
|
286
|
+
const labels = localeLabels.loginBlocker.map(escapeRegExp).join("|");
|
|
287
|
+
const matches = visibleText.match(new RegExp(`(?:${labels})`, "gi")) ?? [];
|
|
288
|
+
return matches.length >= 2 || /\bsign\s?up\b|\bcreate (?:an )?account\b/i.test(visibleText);
|
|
289
|
+
}
|
package/dist/src/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AskArgs, ArtifactDownloadArgs, ArtifactWaitArgs, ApplyConfigurationData, ApplyConfigurationArgs, AttachFilesArgs, BootstrapArgs, ChatGPTExperience, CommandResult, ConfigurationInspectionData, ConfigurationSelection, CopyResponseArgs, DetectExperienceArgs, DetectExperienceData, DownloadLatestArgs, FilePreflightArgs, FilePreflightData, GetModeArgs, GetModeData, InspectConfigurationArgs, ListArtifactsArgs, MessageStatusArgs, NewThreadArgs, OpenThreadArgs, OpenExperienceData, ProjectSourcesAddArgs, ProjectSourcesAddData, ProjectSourcesAddPlanData, ProjectSourcesListArgs, ProjectSourcesListData, ProjectSourcesPlanAddArgs, ReadLatestArgs, RuntimeEnv, ReadWorkLatestArgs, ReadWorkLatestData, SearchThreadsArgs, SelectToolArgs, SequencePlan, SetModeArgs, StartWorkArgs, StartWorkData, SteerWorkArgs, SteerWorkData, ThreadTarget, WaitArgs, WorkStatusArgs, WorkStatusData, WorkWaitArgs, WorkWaitData } from "./types.js";
|
|
1
|
+
import type { AskArgs, ArtifactDownloadArgs, ArtifactWaitArgs, ApplyConfigurationData, ApplyConfigurationArgs, AttachFilesArgs, BootstrapArgs, ChatGPTExperience, CommandResult, ConfigurationInspectionData, ConfigurationSelection, CopyResponseArgs, DetectExperienceArgs, DetectExperienceData, DownloadLatestArgs, FilePreflightArgs, FilePreflightData, GetModeArgs, GetModeData, InspectConfigurationArgs, ListArtifactsArgs, MessageStatusArgs, StopGenerationArgs, StopGenerationData, NewThreadArgs, OpenThreadArgs, OpenExperienceData, ProjectSourcesAddArgs, ProjectSourcesAddData, ProjectSourcesAddPlanData, ProjectSourcesListArgs, ProjectSourcesListData, ProjectSourcesPlanAddArgs, ReadLatestArgs, RuntimeEnv, ReadWorkLatestArgs, ReadWorkLatestData, SearchThreadsArgs, SelectToolArgs, SequencePlan, SetModeArgs, StartWorkArgs, StartWorkData, SteerWorkArgs, SteerWorkData, ThreadTarget, WaitArgs, WorkStatusArgs, WorkStatusData, WorkWaitArgs, WorkWaitData } from "./types.js";
|
|
2
2
|
import { type DoctorArgs, type DoctorReport } from "./commands/doctor.js";
|
|
3
3
|
import { type RunReportData, type RunReportOptions } from "./commands/reports.js";
|
|
4
4
|
import { type CommandDescriptor } from "./commands/registry.js";
|
|
@@ -185,6 +185,7 @@ export type ChatGPTClient = {
|
|
|
185
185
|
wait(args?: WaitArgs): Promise<CommandResult<unknown>>;
|
|
186
186
|
readLatest(args?: ReadLatestArgs): Promise<CommandResult<unknown>>;
|
|
187
187
|
status(args?: MessageStatusArgs): Promise<CommandResult<unknown>>;
|
|
188
|
+
stop(args: StopGenerationArgs): Promise<CommandResult<StopGenerationData>>;
|
|
188
189
|
waitAndRead(args?: WaitArgs & ReadLatestArgs): Promise<CommandResult<unknown>>;
|
|
189
190
|
};
|
|
190
191
|
files: {
|
package/dist/src/client.js
CHANGED
|
@@ -2,7 +2,7 @@ import { downloadLatestArtifact, listLatestArtifacts, waitForArtifact } from "./
|
|
|
2
2
|
import { attachFiles, downloadLatestFile, preflightFiles } from "./commands/files.js";
|
|
3
3
|
import { addProjectSources, buildProjectSourceAddPlan, listProjectSources } from "./commands/project-sources.js";
|
|
4
4
|
import { doctor } from "./commands/doctor.js";
|
|
5
|
-
import { askMessage, composeMessage, messageStatus, readLatest, submitMessage, waitAndRead, waitForMessage } from "./commands/messages.js";
|
|
5
|
+
import { askMessage, composeMessage, messageStatus, readLatest, stopGeneration, submitMessage, waitAndRead, waitForMessage } from "./commands/messages.js";
|
|
6
6
|
import { getMode, selectTool, setMode } from "./commands/modes.js";
|
|
7
7
|
import { applyConfiguration, inspectConfiguration } from "./commands/configuration.js";
|
|
8
8
|
import { detectExperience, openExperience } from "./commands/experience.js";
|
|
@@ -95,6 +95,7 @@ export function createChatGPT(options = {}) {
|
|
|
95
95
|
wait: args => waitForMessage(env, args),
|
|
96
96
|
readLatest: args => readLatest(env, args),
|
|
97
97
|
status: args => messageStatus(env, args),
|
|
98
|
+
stop: args => stopGeneration(env, args),
|
|
98
99
|
waitAndRead: args => waitAndRead(env, args)
|
|
99
100
|
},
|
|
100
101
|
files: {
|