@wlv-zedd/dsh-chatgpt-web 1.0.4 → 1.0.5

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/lib/cli.js CHANGED
@@ -25,6 +25,7 @@ import { tmpdir } from "node:os";
25
25
  var CHATGPT_WEB_MODEL_PREFIX = "chatgpt-web/";
26
26
  var CHATGPT_WEB_BACKEND_MODEL = "gpt-5.6-sol";
27
27
  var CHATGPT_WEB_LUNA_BACKEND_MODEL = "gpt-5.6-luna";
28
+ var CHATGPT_WEB_GPT6_BACKEND_MODEL = "gpt-6";
28
29
  var CHATGPT_WEB_ZERO_RISK_BACKEND_MODEL = "chatgpt-web-zero-risk";
29
30
  var CHATGPT_WEB_ZERO_RISK_PRO_BACKEND_MODEL = "chatgpt-web-zero-risk-pro";
30
31
  var CHATGPT_WEB_INSTANT_CONTEXT_WINDOW = 41000;
@@ -166,8 +167,8 @@ var CHATGPT_WEB_LUNA_MODEL_ROUTES = [
166
167
  var CHATGPT_WEB_MODEL_ROUTES = [
167
168
  {
168
169
  slug: "chatgpt-web/light",
169
- displayName: "ChatGPT Web — Instant",
170
- description: "ChatGPT Web Instant through the native Codex harness.",
170
+ displayName: "ChatGPT Web — GPT-5.6 Sol Instant (即时)",
171
+ description: "通过原生 Codex 通道的 ChatGPT Web 即时档 (Instant)。",
171
172
  interactionMode: "automatic",
172
173
  backendModel: CHATGPT_WEB_BACKEND_MODEL,
173
174
  codexEffort: "low",
@@ -176,8 +177,8 @@ var CHATGPT_WEB_MODEL_ROUTES = [
176
177
  },
177
178
  {
178
179
  slug: "chatgpt-web/medium",
179
- displayName: "ChatGPT Web — Medium",
180
- description: "ChatGPT Web Medium through the native Codex harness.",
180
+ displayName: "ChatGPT Web — GPT-5.6 Sol Medium (中等)",
181
+ description: "通过原生 Codex 通道的 ChatGPT Web 中等档 (Medium)。",
181
182
  interactionMode: "automatic",
182
183
  backendModel: CHATGPT_WEB_BACKEND_MODEL,
183
184
  codexEffort: "medium",
@@ -186,18 +187,48 @@ var CHATGPT_WEB_MODEL_ROUTES = [
186
187
  },
187
188
  {
188
189
  slug: "chatgpt-web/high",
189
- displayName: "ChatGPT Web — High",
190
- description: "ChatGPT Web High through the native Codex harness.",
190
+ displayName: "ChatGPT Web — GPT-5.6 Sol High (高)",
191
+ description: "通过原生 Codex 通道的 ChatGPT Web 高档 (High)。",
191
192
  interactionMode: "automatic",
192
193
  backendModel: CHATGPT_WEB_BACKEND_MODEL,
193
194
  codexEffort: "high",
194
195
  adapterEffort: "high",
195
196
  requiresPro: false
196
197
  },
198
+ {
199
+ slug: "chatgpt-web/gpt-6-light",
200
+ displayName: "ChatGPT Web — GPT-6 Instant (即时)",
201
+ description: "通过原生 Codex 通道的 ChatGPT GPT-6 即时档(Plus 选择器,2026-10 / GPT-6 Instant)。",
202
+ interactionMode: "automatic",
203
+ backendModel: CHATGPT_WEB_GPT6_BACKEND_MODEL,
204
+ codexEffort: "low",
205
+ adapterEffort: "low",
206
+ requiresPro: false
207
+ },
208
+ {
209
+ slug: "chatgpt-web/gpt-6-medium",
210
+ displayName: "ChatGPT Web — GPT-6 Medium (中等)",
211
+ description: "通过原生 Codex 通道的 ChatGPT GPT-6 中等档(Plus 选择器,2026-10 / GPT-6 Medium)。",
212
+ interactionMode: "automatic",
213
+ backendModel: CHATGPT_WEB_GPT6_BACKEND_MODEL,
214
+ codexEffort: "medium",
215
+ adapterEffort: "medium",
216
+ requiresPro: false
217
+ },
218
+ {
219
+ slug: "chatgpt-web/gpt-6-high",
220
+ displayName: "ChatGPT Web — GPT-6 High (高)",
221
+ description: "通过原生 Codex 通道的 ChatGPT GPT-6 高档(Plus 选择器,2026-10 / GPT-6 High)。",
222
+ interactionMode: "automatic",
223
+ backendModel: CHATGPT_WEB_GPT6_BACKEND_MODEL,
224
+ codexEffort: "high",
225
+ adapterEffort: "high",
226
+ requiresPro: false
227
+ },
197
228
  {
198
229
  slug: "chatgpt-web/extra-high",
199
- displayName: "ChatGPT Web — Extra High",
200
- description: "Account-gated ChatGPT Web Extra High through the native Codex harness.",
230
+ displayName: "ChatGPT Web — Extra High (超高)",
231
+ description: "按账号开放的超高档 ChatGPT Web (Extra High,通过原生 Codex 通道)。",
201
232
  interactionMode: "automatic",
202
233
  backendModel: CHATGPT_WEB_BACKEND_MODEL,
203
234
  codexEffort: "xhigh",
@@ -207,7 +238,7 @@ var CHATGPT_WEB_MODEL_ROUTES = [
207
238
  {
208
239
  slug: "chatgpt-web/pro",
209
240
  displayName: "ChatGPT Web — Pro",
210
- description: "Account-gated ChatGPT Pro through the native Codex harness.",
241
+ description: "按账号开放的 ChatGPT Pro (通过原生 Codex 通道)。",
211
242
  interactionMode: "automatic",
212
243
  backendModel: CHATGPT_WEB_BACKEND_MODEL,
213
244
  codexEffort: "ultra",
@@ -270,7 +301,7 @@ function requireChatGptWebModelRoute(modelId, capabilities) {
270
301
  }
271
302
 
272
303
  // src/version.ts
273
- var VERSION = "1.0.4";
304
+ var VERSION = "1.0.5";
274
305
 
275
306
  // src/config.ts
276
307
  var CHATGPT_CONNECTOR_NAME = "Codex Native2";
@@ -844,15 +875,20 @@ var CHATGPT_COMPOSER_SELECTOR = [
844
875
  ].join(", ");
845
876
  var CHATGPT_EFFORT_CONTROL_SELECTOR = [
846
877
  'button[aria-haspopup="menu"][data-tone="neutral"]',
847
- 'button[data-testid="model-switcher-dropdown-button"][aria-haspopup="menu"]'
878
+ 'button[data-testid="model-switcher-dropdown-button"][aria-haspopup="menu"]',
879
+ 'button[aria-haspopup="menu"][aria-label="选择 ChatGPT 模型"]',
880
+ 'button[aria-haspopup="menu"][aria-label*="ChatGPT"][aria-label*="model" i]'
848
881
  ].join(", ");
849
882
  var CHATGPT_EFFORT_MENU_SELECTOR = [
850
- '[data-testid="composer-intelligence-picker-content"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider])',
851
- '[role="menu"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider])',
852
- '[role="group"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider])'
883
+ '[data-testid="composer-intelligence-picker-content"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider], [data-model-picker-power-slider])',
884
+ '[role="menu"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider], [data-model-picker-power-slider])',
885
+ '[role="group"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider], [data-model-picker-power-slider])'
853
886
  ].join(", ");
854
887
  var CHATGPT_EFFORT_ITEM_SELECTOR = '[role="menuitemradio"]';
855
- var CHATGPT_EFFORT_SLIDER_SELECTOR = '[data-model-reasoning-effort-slider] [role="slider"]';
888
+ var CHATGPT_EFFORT_SLIDER_SELECTOR = [
889
+ '[data-model-picker-power-slider] [role="slider"]',
890
+ '[data-model-reasoning-effort-slider] [role="slider"]'
891
+ ].join(", ");
856
892
  var CHATGPT_EFFORT_SLIDER_MAX_OPTIONS = 5;
857
893
  var CHATGPT_STOP_BUTTON_SELECTOR = [
858
894
  '[data-testid="stop-button"]',
@@ -7334,6 +7370,7 @@ class ChatGptMarkdownBuffer {
7334
7370
 
7335
7371
  // src/adapters/chatgpt-web/model.ts
7336
7372
  var CHATGPT_WEB_MODEL_ID = CHATGPT_WEB_BACKEND_MODEL;
7373
+ var CHATGPT_WEB_GPT6_MODEL_ID = CHATGPT_WEB_GPT6_BACKEND_MODEL;
7337
7374
  var CHATGPT_WEB_LUNA_MODEL_ID = CHATGPT_WEB_LUNA_BACKEND_MODEL;
7338
7375
  function resolveChatGptWebModelMode(modelId, reasoning, capabilities) {
7339
7376
  if (modelId === CHATGPT_WEB_LUNA_MODEL_ID) {
@@ -7354,7 +7391,7 @@ function resolveChatGptWebModelMode(modelId, reasoning, capabilities) {
7354
7391
  localTools: capabilities.localToolsEnabled
7355
7392
  };
7356
7393
  }
7357
- if (modelId !== CHATGPT_WEB_MODEL_ID) {
7394
+ if (modelId !== CHATGPT_WEB_MODEL_ID && modelId !== CHATGPT_WEB_GPT6_MODEL_ID) {
7358
7395
  throw new Error(`ChatGPT web model is not supported: ${modelId}`);
7359
7396
  }
7360
7397
  if (!capabilities.solAvailable) {
@@ -7363,15 +7400,15 @@ function resolveChatGptWebModelMode(modelId, reasoning, capabilities) {
7363
7400
  const effort = reasoning ?? "high";
7364
7401
  switch (effort) {
7365
7402
  case "low":
7366
- return { modelId, effort, displayLabel: "Instant", uiEffortIndex: 0, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
7403
+ return { modelId, effort, displayLabel: "Instant (即时)", uiEffortIndex: 0, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
7367
7404
  case "medium":
7368
- return { modelId, effort, displayLabel: "Medium", uiEffortIndex: 1, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
7405
+ return { modelId, effort, displayLabel: "Medium (中等)", uiEffortIndex: 1, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
7369
7406
  case "high":
7370
- return { modelId, effort, displayLabel: "High", uiEffortIndex: 2, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
7407
+ return { modelId, effort, displayLabel: "High (高)", uiEffortIndex: 2, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
7371
7408
  case "xhigh":
7372
7409
  if (!capabilities.proAvailable)
7373
7410
  throw new Error("ChatGPT Extra High effort is not available for this account");
7374
- return { modelId, effort, displayLabel: "Extra High", uiEffortIndex: 3, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
7411
+ return { modelId, effort, displayLabel: "Extra High (超高)", uiEffortIndex: 3, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
7375
7412
  case "max":
7376
7413
  if (!capabilities.proAvailable)
7377
7414
  throw new Error("ChatGPT Pro effort is not available for this account");
@@ -10856,7 +10893,7 @@ async function resolveChatGptToolConfirmation(page, appName, autoApprove, signal
10856
10893
  return true;
10857
10894
  }
10858
10895
  function assertChatGptWebInputWithinLimits(estimatedInputTokens, estimatedMessageTokens, modelId, effort, capabilities, promptChars, composerCharLimitOverride) {
10859
- if (modelId !== CHATGPT_WEB_MODEL_ID && modelId !== CHATGPT_WEB_LUNA_MODEL_ID) {
10896
+ if (modelId !== CHATGPT_WEB_MODEL_ID && modelId !== CHATGPT_WEB_LUNA_MODEL_ID && modelId !== CHATGPT_WEB_GPT6_MODEL_ID) {
10860
10897
  throw new Error(`ChatGPT web context limit is not defined for model: ${modelId}`);
10861
10898
  }
10862
10899
  if (modelId === CHATGPT_WEB_LUNA_MODEL_ID && estimatedInputTokens > CHATGPT_LUNA_BROWSER_INPUT_TOKEN_BUDGET) {
@@ -10879,7 +10916,7 @@ function assertChatGptWebMultipartInputWithinLimits(estimatedInputTokens, estima
10879
10916
  if (modelId === CHATGPT_WEB_LUNA_MODEL_ID) {
10880
10917
  throw new ChatGptWebAdapterError("Bigger Context is unavailable for Luna because every later browser request includes the accumulated transcript inside the same 28,000-token transport budget.", { status: 400, errorType: "invalid_request_error", code: "context_length_exceeded", retryable: false });
10881
10918
  }
10882
- if (modelId !== CHATGPT_WEB_MODEL_ID) {
10919
+ if (modelId !== CHATGPT_WEB_MODEL_ID && modelId !== CHATGPT_WEB_GPT6_MODEL_ID) {
10883
10920
  throw new Error(`ChatGPT Bigger Context limit is not defined for model: ${modelId}`);
10884
10921
  }
10885
10922
  const { contextWindow: baseContextWindow } = resolveChatGptWebContextLimits(modelId, effort, { ...capabilities, experimentalBiggerContext: false });
@@ -10908,7 +10945,7 @@ function resolveChatGptWebMultipartStagingMode(modelId, capabilities, requestedE
10908
10945
  if (modelId === CHATGPT_WEB_LUNA_MODEL_ID || !capabilities.solAvailable) {
10909
10946
  throw new ChatGptWebAdapterError("Bigger Context staging is unavailable for a Luna-only account.", { status: 400, errorType: "invalid_request_error", code: "context_length_exceeded", retryable: false });
10910
10947
  }
10911
- if (modelId !== CHATGPT_WEB_MODEL_ID) {
10948
+ if (modelId !== CHATGPT_WEB_MODEL_ID && modelId !== CHATGPT_WEB_GPT6_MODEL_ID) {
10912
10949
  throw new Error(`ChatGPT Bigger Context staging mode is not defined for model: ${modelId}`);
10913
10950
  }
10914
10951
  const efforts = capabilities.proAvailable ? ["low", "medium", "max"] : ["low", "medium"];
@@ -11560,6 +11597,34 @@ function insertPlainTextIntoComposer(element, value) {
11560
11597
  }
11561
11598
  return document.execCommand("insertText", false, value);
11562
11599
  }
11600
+ var CHATGPT_SEND_BUTTON_SELECTORS = [
11601
+ '[data-testid="send-button"]',
11602
+ 'button[aria-label="Send"]',
11603
+ 'button[aria-label="发送"]',
11604
+ 'button[aria-label="发送消息"]',
11605
+ 'button[aria-label*="Send" i]',
11606
+ 'button[aria-label*="发送"]'
11607
+ ].join(", ");
11608
+ async function resolveChatGptComposerContainer(composer, page) {
11609
+ const formScope = composer.locator("xpath=ancestor::form[1]");
11610
+ if (await formScope.count().catch(() => 0) > 0)
11611
+ return formScope.first();
11612
+ const containerScope = composer.locator("xpath=ancestor::*[self::form or self::div or @role='group' or @role='region'][1]");
11613
+ if (await containerScope.count().catch(() => 0) > 0)
11614
+ return containerScope.first();
11615
+ return composer;
11616
+ }
11617
+ async function resolveChatGptSendButton(composer, page) {
11618
+ const formScope = composer.locator("xpath=ancestor::form[1]").locator(CHATGPT_SEND_BUTTON_SELECTORS);
11619
+ if (await formScope.count().catch(() => 0) > 0)
11620
+ return formScope.first();
11621
+ const containerScope = composer.locator("xpath=ancestor::*[self::form or self::div or @role='group' or @role='region'][1]").locator(CHATGPT_SEND_BUTTON_SELECTORS);
11622
+ if (await containerScope.count().catch(() => 0) > 0)
11623
+ return containerScope.first();
11624
+ if (page)
11625
+ return page.locator(CHATGPT_SEND_BUTTON_SELECTORS).first();
11626
+ return composer.locator(CHATGPT_SEND_BUTTON_SELECTORS).first();
11627
+ }
11563
11628
 
11564
11629
  class ChatGptBrowserWorker {
11565
11630
  config;
@@ -11858,7 +11923,7 @@ class ChatGptBrowserWorker {
11858
11923
  await throwIfChatGptRateLimitDialog(page);
11859
11924
  if (ready === "session-expired")
11860
11925
  await throwIfChatGptSessionFailureAlert(page);
11861
- if (ready !== "slider" && await effortSlider.isVisible().catch(() => false))
11926
+ if (await effortSlider.isVisible().catch(() => false))
11862
11927
  ready = "slider";
11863
11928
  await captureDiagnostic?.(ready === "slider" ? "effort-slider-visible" : "effort-choice-visible");
11864
11929
  } catch (error) {
@@ -11875,11 +11940,11 @@ class ChatGptBrowserWorker {
11875
11940
  if (!sliderState) {
11876
11941
  throw chatGptModelControlUnavailableAdapterError("ChatGPT effort slider exposed an invalid ARIA range");
11877
11942
  }
11878
- const targetValue = sliderState.min + uiEffortIndex;
11879
- if (targetValue > sliderState.max) {
11880
- const proUsageLimitHint = uiEffortIndex === 4 && sliderState.min === 0 && sliderState.max === 3 ? " If you have made many Pro requests recently, ChatGPT may have temporarily hidden Pro because you reached its usage limit." : "";
11881
- throw chatGptModelControlUnavailableAdapterError(`ChatGPT effort slider does not expose item index ${uiEffortIndex}` + ` (min=${sliderState.min}; max=${sliderState.max})` + proUsageLimitHint);
11943
+ const requestedStop = sliderState.min + uiEffortIndex;
11944
+ if (requestedStop > sliderState.max) {
11945
+ throw chatGptModelControlUnavailableAdapterError(`ChatGPT effort slider does not expose item index ${uiEffortIndex}` + ` (min=${sliderState.min}; max=${sliderState.max})` + ". This account's picker exposes three effort stops only.");
11882
11946
  }
11947
+ const targetValue = requestedStop;
11883
11948
  const sliderControl = effortSlider.locator("xpath=ancestor::*[@role='menuitem'][1]");
11884
11949
  while (sliderState.value !== targetValue) {
11885
11950
  await throwIfChatGptRateLimitDialog(page);
@@ -12575,7 +12640,8 @@ class ChatGptBrowserWorker {
12575
12640
  }
12576
12641
  async sendAttachedPrompt(page, baseline, captureDiagnostic, abortSignal, externalProgress, submissionLifecycle, completionTracker, recoverObservation) {
12577
12642
  const composer = await this.activeComposer(page);
12578
- const sendButton = composer.locator("xpath=ancestor::form[1]").locator('[data-testid="send-button"], button[aria-label="Send"]').first();
12643
+ const sendButton = await resolveChatGptSendButton(composer, page);
12644
+ await captureDiagnostic?.("send-button-resolved");
12579
12645
  await sendButton.waitFor({ state: "visible", timeout: browserStageTimeouts.send });
12580
12646
  await settleChatGptUi();
12581
12647
  const sendEnableDeadline = Date.now() + this.config.tuning.sendEnableGraceMs;
@@ -12791,17 +12857,17 @@ class ChatGptBrowserWorker {
12791
12857
  if (files.length === 0)
12792
12858
  return;
12793
12859
  const composer = await this.activeComposer(page);
12794
- const composerForm = composer.locator("xpath=ancestor::form[1]");
12860
+ const composerContainer = await resolveChatGptComposerContainer(composer, page);
12795
12861
  const input = page.locator('input[data-testid="upload-photos-input"]');
12796
12862
  await input.waitFor({ state: "attached", timeout: 20000 });
12797
12863
  await input.setInputFiles(files);
12798
12864
  try {
12799
- await Promise.all(files.map((file) => composerForm.getByRole("group", { name: file.name, exact: true }).waitFor({ state: "visible", timeout: 60000 })));
12865
+ await Promise.all(files.map((file) => composerContainer.getByRole("group", { name: file.name, exact: true }).waitFor({ state: "visible", timeout: 60000 })));
12800
12866
  } catch {
12801
12867
  const alerts = (await page.locator('[role="alert"]').allInnerTexts().catch(() => [])).map((text) => text.replace(/\s+/g, " ").trim()).filter(Boolean);
12802
12868
  throw new Error(`ChatGPT did not accept all prompt attachments` + (alerts.length > 0 ? `: ${alerts.join(" | ")}` : ""));
12803
12869
  }
12804
- const send = composerForm.locator('[data-testid="send-button"], button[aria-label="Send"]').first();
12870
+ const send = await resolveChatGptSendButton(composer, page);
12805
12871
  const deadline = Date.now() + 60000;
12806
12872
  while (Date.now() < deadline) {
12807
12873
  if (await send.isEnabled().catch(() => false))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wlv-zedd/dsh-chatgpt-web",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "DeepSeek Harness Cordis plugin for free ChatGPT Web (GPT 5.6 Luna) pure chat and zero API fees.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,6 +24,7 @@ import {
24
24
  type ChatGptMarkdownSegment,
25
25
  } from "./markdown";
26
26
  import {
27
+ CHATGPT_WEB_GPT6_MODEL_ID,
27
28
  CHATGPT_WEB_LUNA_MODEL_ID,
28
29
  CHATGPT_WEB_MODEL_ID,
29
30
  resolveChatGptWebModelMode,
@@ -879,7 +880,11 @@ export function assertChatGptWebInputWithinLimits(
879
880
  promptChars?: number,
880
881
  composerCharLimitOverride?: number,
881
882
  ): void {
882
- if (modelId !== CHATGPT_WEB_MODEL_ID && modelId !== CHATGPT_WEB_LUNA_MODEL_ID) {
883
+ if (
884
+ modelId !== CHATGPT_WEB_MODEL_ID
885
+ && modelId !== CHATGPT_WEB_LUNA_MODEL_ID
886
+ && modelId !== CHATGPT_WEB_GPT6_MODEL_ID
887
+ ) {
883
888
  throw new Error(`ChatGPT web context limit is not defined for model: ${modelId}`);
884
889
  }
885
890
  if (
@@ -945,7 +950,8 @@ export function assertChatGptWebMultipartInputWithinLimits(
945
950
  { status: 400, errorType: "invalid_request_error", code: "context_length_exceeded", retryable: false },
946
951
  );
947
952
  }
948
- if (modelId !== CHATGPT_WEB_MODEL_ID) {
953
+ // GPT-6 shares the Sol-class Plus transport windows, so Bigger Context applies to it too.
954
+ if (modelId !== CHATGPT_WEB_MODEL_ID && modelId !== CHATGPT_WEB_GPT6_MODEL_ID) {
949
955
  throw new Error(`ChatGPT Bigger Context limit is not defined for model: ${modelId}`);
950
956
  }
951
957
  const { contextWindow: baseContextWindow } = resolveChatGptWebContextLimits(
@@ -1016,7 +1022,7 @@ export function resolveChatGptWebMultipartStagingMode(
1016
1022
  { status: 400, errorType: "invalid_request_error", code: "context_length_exceeded", retryable: false },
1017
1023
  );
1018
1024
  }
1019
- if (modelId !== CHATGPT_WEB_MODEL_ID) {
1025
+ if (modelId !== CHATGPT_WEB_MODEL_ID && modelId !== CHATGPT_WEB_GPT6_MODEL_ID) {
1020
1026
  throw new Error(`ChatGPT Bigger Context staging mode is not defined for model: ${modelId}`);
1021
1027
  }
1022
1028
  const efforts: readonly ChatGptWebModelMode["effort"][] = capabilities.proAvailable
@@ -2013,6 +2019,37 @@ export function insertPlainTextIntoComposer(element: HTMLElement, value: string)
2013
2019
  return document.execCommand("insertText", false, value);
2014
2020
  }
2015
2021
 
2022
+ export const CHATGPT_SEND_BUTTON_SELECTORS = [
2023
+ '[data-testid="send-button"]',
2024
+ 'button[aria-label="Send"]',
2025
+ 'button[aria-label="发送"]',
2026
+ 'button[aria-label="发送消息"]',
2027
+ 'button[aria-label*="Send" i]',
2028
+ 'button[aria-label*="发送"]',
2029
+ ].join(", ");
2030
+
2031
+ export async function resolveChatGptComposerContainer(composer: Locator, page?: Page): Promise<Locator> {
2032
+ const formScope = composer.locator("xpath=ancestor::form[1]");
2033
+ if (await formScope.count().catch(() => 0) > 0) return formScope.first();
2034
+ const containerScope = composer.locator("xpath=ancestor::*[self::form or self::div or @role='group' or @role='region'][1]");
2035
+ if (await containerScope.count().catch(() => 0) > 0) return containerScope.first();
2036
+ return composer;
2037
+ }
2038
+
2039
+ export async function resolveChatGptSendButton(composer: Locator, page?: Page): Promise<Locator> {
2040
+ // Strategy 1: Ancestor form if present
2041
+ const formScope = composer.locator("xpath=ancestor::form[1]").locator(CHATGPT_SEND_BUTTON_SELECTORS);
2042
+ if (await formScope.count().catch(() => 0) > 0) return formScope.first();
2043
+
2044
+ // Strategy 2: Ancestor container (div, role=group, role=region)
2045
+ const containerScope = composer.locator("xpath=ancestor::*[self::form or self::div or @role='group' or @role='region'][1]").locator(CHATGPT_SEND_BUTTON_SELECTORS);
2046
+ if (await containerScope.count().catch(() => 0) > 0) return containerScope.first();
2047
+
2048
+ // Strategy 3: Page-level active send button fallback
2049
+ if (page) return page.locator(CHATGPT_SEND_BUTTON_SELECTORS).first();
2050
+ return composer.locator(CHATGPT_SEND_BUTTON_SELECTORS).first();
2051
+ }
2052
+
2016
2053
  export class ChatGptBrowserWorker {
2017
2054
  static forProvider(provider: CodexProviderConfig): ChatGptBrowserWorker {
2018
2055
  const config = resolveBrowserConfig(provider);
@@ -2371,9 +2408,10 @@ export class ChatGptBrowserWorker {
2371
2408
  ]);
2372
2409
  if (ready === "rate-limit") await throwIfChatGptRateLimitDialog(page);
2373
2410
  if (ready === "session-expired") await throwIfChatGptSessionFailureAlert(page);
2374
- // The current picker exposes model rows as menuitemradio alongside the effort slider.
2375
- // Those rows can win the locator race even though they are not effort choices.
2376
- if (ready !== "slider" && await effortSlider.isVisible().catch(() => false)) ready = "slider";
2411
+ // The current picker exposes model rows as menuitemradio ALONGSIDE the effort slider.
2412
+ // Those rows are models, not effort choices, so a visible slider is authoritative:
2413
+ // we drive effort with arrow keys and never index the rows.
2414
+ if (await effortSlider.isVisible().catch(() => false)) ready = "slider";
2377
2415
  await captureDiagnostic?.(ready === "slider" ? "effort-slider-visible" : "effort-choice-visible");
2378
2416
  } catch (error) {
2379
2417
  if (error instanceof ChatGptWebAdapterError) throw error;
@@ -2397,17 +2435,18 @@ export class ChatGptBrowserWorker {
2397
2435
  "ChatGPT effort slider exposed an invalid ARIA range",
2398
2436
  );
2399
2437
  }
2400
- const targetValue = sliderState.min + uiEffortIndex;
2401
- if (targetValue > sliderState.max) {
2402
- const proUsageLimitHint = uiEffortIndex === 4 && sliderState.min === 0 && sliderState.max === 3
2403
- ? " If you have made many Pro requests recently, ChatGPT may have temporarily hidden Pro because you reached its usage limit."
2404
- : "";
2438
+ // The Plus picker only exposes three stops (Low/Medium/High). If the caller requested a
2439
+ // higher stop that the UI cannot reach, reject it here instead of letting the slider
2440
+ // loop fail silently or silently cap to a lower level.
2441
+ const requestedStop = sliderState.min + uiEffortIndex;
2442
+ if (requestedStop > sliderState.max) {
2405
2443
  throw chatGptModelControlUnavailableAdapterError(
2406
2444
  `ChatGPT effort slider does not expose item index ${uiEffortIndex}`
2407
2445
  + ` (min=${sliderState.min}; max=${sliderState.max})`
2408
- + proUsageLimitHint,
2446
+ + ". This account's picker exposes three effort stops only.",
2409
2447
  );
2410
2448
  }
2449
+ const targetValue = requestedStop;
2411
2450
  const sliderControl = effortSlider.locator("xpath=ancestor::*[@role='menuitem'][1]");
2412
2451
  while (sliderState.value !== targetValue) {
2413
2452
  await throwIfChatGptRateLimitDialog(page);
@@ -3414,10 +3453,8 @@ export class ChatGptBrowserWorker {
3414
3453
  recoverObservation?: ChatGptObservationRecovery,
3415
3454
  ): Promise<ChatGptSubmissionEvidence> {
3416
3455
  const composer = await this.activeComposer(page);
3417
- const sendButton = composer
3418
- .locator("xpath=ancestor::form[1]")
3419
- .locator('[data-testid="send-button"], button[aria-label="Send"]')
3420
- .first();
3456
+ const sendButton = await resolveChatGptSendButton(composer, page);
3457
+ await captureDiagnostic?.("send-button-resolved");
3421
3458
  await sendButton.waitFor({ state: "visible", timeout: browserStageTimeouts.send });
3422
3459
  await settleChatGptUi();
3423
3460
  const sendEnableDeadline = Date.now() + this.config.tuning.sendEnableGraceMs;
@@ -3734,13 +3771,13 @@ export class ChatGptBrowserWorker {
3734
3771
  const files = chatGptPromptFilePayloads(prompt);
3735
3772
  if (files.length === 0) return;
3736
3773
  const composer = await this.activeComposer(page);
3737
- const composerForm = composer.locator("xpath=ancestor::form[1]");
3774
+ const composerContainer = await resolveChatGptComposerContainer(composer, page);
3738
3775
  const input = page.locator('input[data-testid="upload-photos-input"]');
3739
3776
  await input.waitFor({ state: "attached", timeout: 20_000 });
3740
3777
  await input.setInputFiles(files);
3741
3778
  try {
3742
3779
  await Promise.all(files.map(file => (
3743
- composerForm.getByRole("group", { name: file.name, exact: true })
3780
+ composerContainer.getByRole("group", { name: file.name, exact: true })
3744
3781
  .waitFor({ state: "visible", timeout: 60_000 })
3745
3782
  )));
3746
3783
  } catch {
@@ -3752,7 +3789,7 @@ export class ChatGptBrowserWorker {
3752
3789
  + (alerts.length > 0 ? `: ${alerts.join(" | ")}` : ""),
3753
3790
  );
3754
3791
  }
3755
- const send = composerForm.locator('[data-testid="send-button"], button[aria-label="Send"]').first();
3792
+ const send = await resolveChatGptSendButton(composer, page);
3756
3793
  const deadline = Date.now() + 60_000;
3757
3794
  while (Date.now() < deadline) {
3758
3795
  if (await send.isEnabled().catch(() => false)) return;
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  CHATGPT_WEB_BACKEND_MODEL,
3
+ CHATGPT_WEB_GPT6_BACKEND_MODEL,
3
4
  CHATGPT_WEB_LUNA_BACKEND_MODEL,
4
5
  } from "../../chatgpt-web-models";
5
6
 
6
7
  export const CHATGPT_WEB_MODEL_ID = CHATGPT_WEB_BACKEND_MODEL;
8
+ export const CHATGPT_WEB_GPT6_MODEL_ID = CHATGPT_WEB_GPT6_BACKEND_MODEL;
7
9
  export const CHATGPT_WEB_LUNA_MODEL_ID = CHATGPT_WEB_LUNA_BACKEND_MODEL;
8
10
 
9
11
  export interface ChatGptWebCapabilities {
@@ -15,7 +17,7 @@ export interface ChatGptWebCapabilities {
15
17
  export interface ChatGptWebModelMode {
16
18
  modelId: string;
17
19
  effort: "low" | "medium" | "high" | "xhigh" | "max";
18
- displayLabel: "Luna" | "Think" | "Instant" | "Medium" | "High" | "Extra High" | "Pro";
20
+ displayLabel: "Luna" | "Think" | "Instant (即时)" | "Medium (中等)" | "High (高)" | "Extra High (超高)" | "Pro";
19
21
  uiEffortIndex: 0 | 1 | 2 | 3 | 4 | null;
20
22
  thinkEnabled: boolean;
21
23
  localTools: boolean;
@@ -44,7 +46,7 @@ export function resolveChatGptWebModelMode(
44
46
  localTools: capabilities.localToolsEnabled,
45
47
  };
46
48
  }
47
- if (modelId !== CHATGPT_WEB_MODEL_ID) {
49
+ if (modelId !== CHATGPT_WEB_MODEL_ID && modelId !== CHATGPT_WEB_GPT6_MODEL_ID) {
48
50
  throw new Error(`ChatGPT web model is not supported: ${modelId}`);
49
51
  }
50
52
  if (!capabilities.solAvailable) {
@@ -53,14 +55,16 @@ export function resolveChatGptWebModelMode(
53
55
  const effort = reasoning ?? "high";
54
56
  switch (effort) {
55
57
  case "low":
56
- return { modelId, effort, displayLabel: "Instant", uiEffortIndex: 0, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
58
+ return { modelId, effort, displayLabel: "Instant (即时)", uiEffortIndex: 0, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
57
59
  case "medium":
58
- return { modelId, effort, displayLabel: "Medium", uiEffortIndex: 1, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
60
+ return { modelId, effort, displayLabel: "Medium (中等)", uiEffortIndex: 1, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
59
61
  case "high":
60
- return { modelId, effort, displayLabel: "High", uiEffortIndex: 2, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
62
+ return { modelId, effort, displayLabel: "High (高)", uiEffortIndex: 2, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
63
+ // The Plus picker exposes only three effort stops (Low/Medium/High). Higher stops are
64
+ // Pro-account only; this check runs before the turn reaches the browser layer.
61
65
  case "xhigh":
62
66
  if (!capabilities.proAvailable) throw new Error("ChatGPT Extra High effort is not available for this account");
63
- return { modelId, effort, displayLabel: "Extra High", uiEffortIndex: 3, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
67
+ return { modelId, effort, displayLabel: "Extra High (超高)", uiEffortIndex: 3, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
64
68
  case "max":
65
69
  if (!capabilities.proAvailable) throw new Error("ChatGPT Pro effort is not available for this account");
66
70
  return { modelId, effort, displayLabel: "Pro", uiEffortIndex: 4, thinkEnabled: false, localTools: capabilities.localToolsEnabled };
@@ -12,14 +12,24 @@ export const CHATGPT_COMPOSER_SELECTOR = [
12
12
  export const CHATGPT_EFFORT_CONTROL_SELECTOR = [
13
13
  'button[aria-haspopup="menu"][data-tone="neutral"]',
14
14
  'button[data-testid="model-switcher-dropdown-button"][aria-haspopup="menu"]',
15
+ // 2026-10 UI: a plain button whose stable hooks are aria-haspopup="menu" plus a localized
16
+ // aria-label. Match the zh label and a bilingual pattern so the probe does not silently
17
+ // degrade to a Luna-only account on non-English UI.
18
+ 'button[aria-haspopup="menu"][aria-label="选择 ChatGPT 模型"]',
19
+ 'button[aria-haspopup="menu"][aria-label*="ChatGPT"][aria-label*="model" i]',
15
20
  ].join(", ");
16
21
  export const CHATGPT_EFFORT_MENU_SELECTOR = [
17
- '[data-testid="composer-intelligence-picker-content"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider])',
18
- '[role="menu"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider])',
19
- '[role="group"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider])',
22
+ '[data-testid="composer-intelligence-picker-content"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider], [data-model-picker-power-slider])',
23
+ '[role="menu"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider], [data-model-picker-power-slider])',
24
+ '[role="group"]:has([role="menuitemradio"], [data-model-reasoning-effort-slider], [data-model-picker-power-slider])',
20
25
  ].join(", ");
21
26
  export const CHATGPT_EFFORT_ITEM_SELECTOR = '[role="menuitemradio"]';
22
- export const CHATGPT_EFFORT_SLIDER_SELECTOR = '[data-model-reasoning-effort-slider] [role="slider"]';
27
+ // 2026-10 UI renamed the wrapper attribute to data-model-picker-power-slider. Keep the old
28
+ // anchor so a rollback still resolves the slider.
29
+ export const CHATGPT_EFFORT_SLIDER_SELECTOR = [
30
+ '[data-model-picker-power-slider] [role="slider"]',
31
+ '[data-model-reasoning-effort-slider] [role="slider"]',
32
+ ].join(", ");
23
33
  export const CHATGPT_EFFORT_SLIDER_MAX_OPTIONS = 5;
24
34
  export const CHATGPT_STOP_BUTTON_SELECTOR = [
25
35
  '[data-testid="stop-button"]',
@@ -1,6 +1,9 @@
1
1
  export const CHATGPT_WEB_MODEL_PREFIX = "chatgpt-web/";
2
2
  export const CHATGPT_WEB_BACKEND_MODEL = "gpt-5.6-sol";
3
3
  export const CHATGPT_WEB_LUNA_BACKEND_MODEL = "gpt-5.6-luna";
4
+ /** GPT-6 appears in the Plus picker (2026-10) alongside GPT-5.6 Sol. Selecting it is a
5
+ * pure browser-UI action (click the model row), so it needs no extra transport window. */
6
+ export const CHATGPT_WEB_GPT6_BACKEND_MODEL = "gpt-6";
4
7
  /** Internal adapter identity for a turn whose ChatGPT model is selected by the user in the launcher. */
5
8
  export const CHATGPT_WEB_ZERO_RISK_BACKEND_MODEL = "chatgpt-web-zero-risk";
6
9
  /** Internal adapter identity for the explicitly enabled, Pro-sized Zero Risk context profile. */
@@ -8,7 +11,8 @@ export const CHATGPT_WEB_ZERO_RISK_PRO_BACKEND_MODEL = "chatgpt-web-zero-risk-pr
8
11
 
9
12
  export type ChatGptWebAutomaticBackendModel =
10
13
  | typeof CHATGPT_WEB_BACKEND_MODEL
11
- | typeof CHATGPT_WEB_LUNA_BACKEND_MODEL;
14
+ | typeof CHATGPT_WEB_LUNA_BACKEND_MODEL
15
+ | typeof CHATGPT_WEB_GPT6_BACKEND_MODEL;
12
16
  export type ChatGptWebBackendModel =
13
17
  | ChatGptWebAutomaticBackendModel
14
18
  | ChatGptWebZeroRiskBackendModel;
@@ -291,8 +295,8 @@ export const CHATGPT_WEB_LUNA_MODEL_ROUTES: readonly ChatGptWebModelRoute[] = [
291
295
  export const CHATGPT_WEB_MODEL_ROUTES: readonly ChatGptWebAutomaticModelRoute[] = [
292
296
  {
293
297
  slug: "chatgpt-web/light",
294
- displayName: "ChatGPT Web — Instant",
295
- description: "ChatGPT Web Instant through the native Codex harness.",
298
+ displayName: "ChatGPT Web — GPT-5.6 Sol Instant (即时)",
299
+ description: "通过原生 Codex 通道的 ChatGPT Web 即时档 (Instant)。",
296
300
  interactionMode: "automatic",
297
301
  backendModel: CHATGPT_WEB_BACKEND_MODEL,
298
302
  codexEffort: "low",
@@ -301,8 +305,8 @@ export const CHATGPT_WEB_MODEL_ROUTES: readonly ChatGptWebAutomaticModelRoute[]
301
305
  },
302
306
  {
303
307
  slug: "chatgpt-web/medium",
304
- displayName: "ChatGPT Web — Medium",
305
- description: "ChatGPT Web Medium through the native Codex harness.",
308
+ displayName: "ChatGPT Web — GPT-5.6 Sol Medium (中等)",
309
+ description: "通过原生 Codex 通道的 ChatGPT Web 中等档 (Medium)。",
306
310
  interactionMode: "automatic",
307
311
  backendModel: CHATGPT_WEB_BACKEND_MODEL,
308
312
  codexEffort: "medium",
@@ -311,18 +315,48 @@ export const CHATGPT_WEB_MODEL_ROUTES: readonly ChatGptWebAutomaticModelRoute[]
311
315
  },
312
316
  {
313
317
  slug: "chatgpt-web/high",
314
- displayName: "ChatGPT Web — High",
315
- description: "ChatGPT Web High through the native Codex harness.",
318
+ displayName: "ChatGPT Web — GPT-5.6 Sol High (高)",
319
+ description: "通过原生 Codex 通道的 ChatGPT Web 高档 (High)。",
316
320
  interactionMode: "automatic",
317
321
  backendModel: CHATGPT_WEB_BACKEND_MODEL,
318
322
  codexEffort: "high",
319
323
  adapterEffort: "high",
320
324
  requiresPro: false,
321
325
  },
326
+ {
327
+ slug: "chatgpt-web/gpt-6-light",
328
+ displayName: "ChatGPT Web — GPT-6 Instant (即时)",
329
+ description: "通过原生 Codex 通道的 ChatGPT GPT-6 即时档(Plus 选择器,2026-10 / GPT-6 Instant)。",
330
+ interactionMode: "automatic",
331
+ backendModel: CHATGPT_WEB_GPT6_BACKEND_MODEL,
332
+ codexEffort: "low",
333
+ adapterEffort: "low",
334
+ requiresPro: false,
335
+ },
336
+ {
337
+ slug: "chatgpt-web/gpt-6-medium",
338
+ displayName: "ChatGPT Web — GPT-6 Medium (中等)",
339
+ description: "通过原生 Codex 通道的 ChatGPT GPT-6 中等档(Plus 选择器,2026-10 / GPT-6 Medium)。",
340
+ interactionMode: "automatic",
341
+ backendModel: CHATGPT_WEB_GPT6_BACKEND_MODEL,
342
+ codexEffort: "medium",
343
+ adapterEffort: "medium",
344
+ requiresPro: false,
345
+ },
346
+ {
347
+ slug: "chatgpt-web/gpt-6-high",
348
+ displayName: "ChatGPT Web — GPT-6 High (高)",
349
+ description: "通过原生 Codex 通道的 ChatGPT GPT-6 高档(Plus 选择器,2026-10 / GPT-6 High)。",
350
+ interactionMode: "automatic",
351
+ backendModel: CHATGPT_WEB_GPT6_BACKEND_MODEL,
352
+ codexEffort: "high",
353
+ adapterEffort: "high",
354
+ requiresPro: false,
355
+ },
322
356
  {
323
357
  slug: "chatgpt-web/extra-high",
324
- displayName: "ChatGPT Web — Extra High",
325
- description: "Account-gated ChatGPT Web Extra High through the native Codex harness.",
358
+ displayName: "ChatGPT Web — Extra High (超高)",
359
+ description: "按账号开放的超高档 ChatGPT Web (Extra High,通过原生 Codex 通道)。",
326
360
  interactionMode: "automatic",
327
361
  backendModel: CHATGPT_WEB_BACKEND_MODEL,
328
362
  codexEffort: "xhigh",
@@ -332,7 +366,7 @@ export const CHATGPT_WEB_MODEL_ROUTES: readonly ChatGptWebAutomaticModelRoute[]
332
366
  {
333
367
  slug: "chatgpt-web/pro",
334
368
  displayName: "ChatGPT Web — Pro",
335
- description: "Account-gated ChatGPT Pro through the native Codex harness.",
369
+ description: "按账号开放的 ChatGPT Pro (通过原生 Codex 通道)。",
336
370
  interactionMode: "automatic",
337
371
  backendModel: CHATGPT_WEB_BACKEND_MODEL,
338
372
  codexEffort: "ultra",
@@ -52,7 +52,7 @@ function routedModelPriority(
52
52
  throw new Error("Native Codex model template priority cannot reserve the Compatibility V1 roster");
53
53
  }
54
54
  // Codex V1 exposes at most five model overrides. Keep the native Sol row plus the four useful
55
- // delegated Web efforts (Medium, High, Extra High, Pro); Instant remains a selectable root model
55
+ // delegated Web efforts (中等 / 高 / 超高 / Pro); 即时 remains a selectable root model
56
56
  // but does not displace Pro from spawn_agent's bounded registry.
57
57
  return priority + 1;
58
58
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '1.0.4';
1
+ export const VERSION = '1.0.5';