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.
Files changed (217) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/contracts/v1/fixtures/backend-capabilities.json +1 -0
  3. package/contracts/v1/fixtures/command-descriptors.json +29 -2
  4. package/contracts/v1/fixtures/doctor-bridge-upload.json +2 -2
  5. package/contracts/v1/fixtures/doctor-scenario-preflight.json +26 -4
  6. package/contracts/v1/fixtures/files-attach-indeterminate.json +30 -0
  7. package/contracts/v1/fixtures/help-root.json +1 -1
  8. package/contracts/v1/fixtures/messages-stop-indeterminate.json +31 -0
  9. package/contracts/v1/fixtures/messages-stop-needs-confirmation.json +20 -0
  10. package/contracts/v1/fixtures/messages-stop-noop.json +19 -0
  11. package/contracts/v1/fixtures/messages-stop-success.json +21 -0
  12. package/contracts/v1/fixtures/primitive-bootstrap-blocker.json +3 -3
  13. package/contracts/v1/fixtures/reports-create-redacted.json +3 -3
  14. package/contracts/v1/fixtures/reports-summarize-redacted.json +1 -1
  15. package/contracts/v1/fixtures/run-browser-bridge-blocker.json +12 -12
  16. package/contracts/v1/fixtures/stream-blocked.ndjson +2 -2
  17. package/contracts/v1/manifest.json +25 -0
  18. package/contracts/v1/parity-suite.json +19 -1
  19. package/dist/codex-chatgpt-control-backend.mjs +3654 -603
  20. package/dist/codex-chatgpt-control.bundle.mjs +3627 -546
  21. package/dist/src/backend/client.d.ts +2 -1
  22. package/dist/src/backend/client.js +1 -0
  23. package/dist/src/backend/protocol.d.ts +1 -1
  24. package/dist/src/backend/protocol.js +1 -0
  25. package/dist/src/backend/session.js +2 -0
  26. package/dist/src/browser/attach.d.ts +1 -0
  27. package/dist/src/browser/attach.js +69 -35
  28. package/dist/src/browser/chatgpt-url.d.ts +8 -0
  29. package/dist/src/browser/chatgpt-url.js +28 -0
  30. package/dist/src/browser/downloads.d.ts +1 -1
  31. package/dist/src/browser/downloads.js +13 -4
  32. package/dist/src/browser/page-state.js +211 -4
  33. package/dist/src/client.d.ts +2 -1
  34. package/dist/src/client.js +2 -1
  35. package/dist/src/commands/configuration.js +330 -73
  36. package/dist/src/commands/context.d.ts +5 -1
  37. package/dist/src/commands/context.js +2 -2
  38. package/dist/src/commands/conversation.js +13 -1
  39. package/dist/src/commands/doctor.js +24 -2
  40. package/dist/src/commands/experience.d.ts +1 -0
  41. package/dist/src/commands/experience.js +156 -23
  42. package/dist/src/commands/files.d.ts +1 -0
  43. package/dist/src/commands/files.js +877 -170
  44. package/dist/src/commands/messages.d.ts +2 -1
  45. package/dist/src/commands/messages.js +310 -2
  46. package/dist/src/commands/modes.js +197 -52
  47. package/dist/src/commands/registry.js +8 -0
  48. package/dist/src/commands/sequence.js +3 -1
  49. package/dist/src/commands/session.d.ts +4 -1
  50. package/dist/src/commands/session.js +34 -3
  51. package/dist/src/dom/generation-state.d.ts +6 -1
  52. package/dist/src/dom/generation-state.js +206 -45
  53. package/dist/src/dom/locale/am.d.ts +23 -1
  54. package/dist/src/dom/locale/am.js +23 -1
  55. package/dist/src/dom/locale/ar.d.ts +21 -1
  56. package/dist/src/dom/locale/ar.js +24 -4
  57. package/dist/src/dom/locale/bg.d.ts +19 -1
  58. package/dist/src/dom/locale/bg.js +22 -4
  59. package/dist/src/dom/locale/bn.d.ts +23 -1
  60. package/dist/src/dom/locale/bn.js +23 -1
  61. package/dist/src/dom/locale/bs.d.ts +17 -1
  62. package/dist/src/dom/locale/bs.js +21 -5
  63. package/dist/src/dom/locale/ca.d.ts +20 -1
  64. package/dist/src/dom/locale/ca.js +25 -6
  65. package/dist/src/dom/locale/cs.d.ts +17 -1
  66. package/dist/src/dom/locale/cs.js +17 -1
  67. package/dist/src/dom/locale/da.d.ts +17 -1
  68. package/dist/src/dom/locale/da.js +18 -2
  69. package/dist/src/dom/locale/de.d.ts +16 -1
  70. package/dist/src/dom/locale/de.js +18 -3
  71. package/dist/src/dom/locale/el.d.ts +18 -1
  72. package/dist/src/dom/locale/el.js +21 -4
  73. package/dist/src/dom/locale/en.d.ts +2 -1
  74. package/dist/src/dom/locale/en.js +2 -1
  75. package/dist/src/dom/locale/es-419.d.ts +18 -1
  76. package/dist/src/dom/locale/es-419.js +18 -1
  77. package/dist/src/dom/locale/es-ES.d.ts +18 -1
  78. package/dist/src/dom/locale/es-ES.js +18 -1
  79. package/dist/src/dom/locale/et.d.ts +16 -1
  80. package/dist/src/dom/locale/et.js +16 -1
  81. package/dist/src/dom/locale/fa.d.ts +21 -1
  82. package/dist/src/dom/locale/fa.js +23 -3
  83. package/dist/src/dom/locale/fi.d.ts +18 -1
  84. package/dist/src/dom/locale/fi.js +18 -1
  85. package/dist/src/dom/locale/fr-CA.d.ts +15 -1
  86. package/dist/src/dom/locale/fr-CA.js +15 -1
  87. package/dist/src/dom/locale/fr-FR.d.ts +18 -1
  88. package/dist/src/dom/locale/fr-FR.js +22 -5
  89. package/dist/src/dom/locale/gu.d.ts +23 -1
  90. package/dist/src/dom/locale/gu.js +27 -5
  91. package/dist/src/dom/locale/hi.d.ts +23 -1
  92. package/dist/src/dom/locale/hi.js +25 -3
  93. package/dist/src/dom/locale/hr.d.ts +16 -1
  94. package/dist/src/dom/locale/hr.js +19 -4
  95. package/dist/src/dom/locale/hu.d.ts +17 -1
  96. package/dist/src/dom/locale/hu.js +20 -4
  97. package/dist/src/dom/locale/hy.d.ts +23 -1
  98. package/dist/src/dom/locale/hy.js +26 -4
  99. package/dist/src/dom/locale/id.d.ts +17 -1
  100. package/dist/src/dom/locale/id.js +19 -3
  101. package/dist/src/dom/locale/index.js +1 -0
  102. package/dist/src/dom/locale/is.d.ts +21 -1
  103. package/dist/src/dom/locale/is.js +21 -1
  104. package/dist/src/dom/locale/it.d.ts +15 -1
  105. package/dist/src/dom/locale/it.js +20 -6
  106. package/dist/src/dom/locale/ja.d.ts +19 -1
  107. package/dist/src/dom/locale/ja.js +23 -5
  108. package/dist/src/dom/locale/ka.d.ts +23 -1
  109. package/dist/src/dom/locale/ka.js +23 -1
  110. package/dist/src/dom/locale/kk.d.ts +23 -1
  111. package/dist/src/dom/locale/kk.js +25 -3
  112. package/dist/src/dom/locale/kn.d.ts +22 -1
  113. package/dist/src/dom/locale/kn.js +26 -5
  114. package/dist/src/dom/locale/ko.d.ts +17 -1
  115. package/dist/src/dom/locale/ko.js +17 -1
  116. package/dist/src/dom/locale/lt.d.ts +18 -1
  117. package/dist/src/dom/locale/lt.js +20 -3
  118. package/dist/src/dom/locale/lv.d.ts +18 -1
  119. package/dist/src/dom/locale/lv.js +18 -1
  120. package/dist/src/dom/locale/mk.d.ts +22 -1
  121. package/dist/src/dom/locale/mk.js +26 -5
  122. package/dist/src/dom/locale/ml.d.ts +23 -1
  123. package/dist/src/dom/locale/ml.js +25 -3
  124. package/dist/src/dom/locale/mn.d.ts +23 -1
  125. package/dist/src/dom/locale/mn.js +23 -1
  126. package/dist/src/dom/locale/mr.d.ts +23 -1
  127. package/dist/src/dom/locale/mr.js +25 -3
  128. package/dist/src/dom/locale/ms.d.ts +20 -1
  129. package/dist/src/dom/locale/ms.js +20 -1
  130. package/dist/src/dom/locale/my.d.ts +22 -1
  131. package/dist/src/dom/locale/my.js +24 -3
  132. package/dist/src/dom/locale/nb.d.ts +17 -1
  133. package/dist/src/dom/locale/nb.js +17 -1
  134. package/dist/src/dom/locale/nl.d.ts +20 -2
  135. package/dist/src/dom/locale/nl.js +22 -4
  136. package/dist/src/dom/locale/pa.d.ts +23 -1
  137. package/dist/src/dom/locale/pa.js +27 -5
  138. package/dist/src/dom/locale/pl.d.ts +17 -1
  139. package/dist/src/dom/locale/pl.js +19 -3
  140. package/dist/src/dom/locale/pt-BR.d.ts +18 -1
  141. package/dist/src/dom/locale/pt-BR.js +20 -3
  142. package/dist/src/dom/locale/pt-PT.d.ts +18 -1
  143. package/dist/src/dom/locale/pt-PT.js +22 -5
  144. package/dist/src/dom/locale/ro.d.ts +15 -1
  145. package/dist/src/dom/locale/ro.js +18 -4
  146. package/dist/src/dom/locale/ru.d.ts +23 -1
  147. package/dist/src/dom/locale/ru.js +24 -2
  148. package/dist/src/dom/locale/sk.d.ts +17 -1
  149. package/dist/src/dom/locale/sk.js +19 -3
  150. package/dist/src/dom/locale/sl.d.ts +21 -1
  151. package/dist/src/dom/locale/sl.js +24 -4
  152. package/dist/src/dom/locale/so.d.ts +22 -1
  153. package/dist/src/dom/locale/so.js +24 -3
  154. package/dist/src/dom/locale/sq.d.ts +21 -1
  155. package/dist/src/dom/locale/sq.js +25 -5
  156. package/dist/src/dom/locale/sr.d.ts +24 -1
  157. package/dist/src/dom/locale/sr.js +25 -2
  158. package/dist/src/dom/locale/sv.d.ts +16 -1
  159. package/dist/src/dom/locale/sv.js +16 -1
  160. package/dist/src/dom/locale/sw.d.ts +18 -1
  161. package/dist/src/dom/locale/sw.js +18 -1
  162. package/dist/src/dom/locale/ta.d.ts +23 -1
  163. package/dist/src/dom/locale/ta.js +27 -5
  164. package/dist/src/dom/locale/te.d.ts +23 -1
  165. package/dist/src/dom/locale/te.js +27 -5
  166. package/dist/src/dom/locale/th.d.ts +11 -1
  167. package/dist/src/dom/locale/th.js +11 -1
  168. package/dist/src/dom/locale/tl.d.ts +8 -1
  169. package/dist/src/dom/locale/tl.js +8 -1
  170. package/dist/src/dom/locale/tr.d.ts +21 -1
  171. package/dist/src/dom/locale/tr.js +23 -3
  172. package/dist/src/dom/locale/types.d.ts +1 -1
  173. package/dist/src/dom/locale/uk.d.ts +23 -1
  174. package/dist/src/dom/locale/uk.js +25 -3
  175. package/dist/src/dom/locale/ur.d.ts +22 -1
  176. package/dist/src/dom/locale/ur.js +25 -4
  177. package/dist/src/dom/locale/vi.d.ts +22 -1
  178. package/dist/src/dom/locale/vi.js +25 -4
  179. package/dist/src/dom/locale/zh-HK.d.ts +21 -1
  180. package/dist/src/dom/locale/zh-HK.js +23 -3
  181. package/dist/src/dom/locale/zh-Hans.d.ts +23 -1
  182. package/dist/src/dom/locale/zh-Hans.js +27 -5
  183. package/dist/src/dom/locale/zh-TW.d.ts +23 -1
  184. package/dist/src/dom/locale/zh-TW.js +25 -3
  185. package/dist/src/dom/menus.js +26 -2
  186. package/dist/src/dom/selectors.d.ts +1 -0
  187. package/dist/src/dom/selectors.js +9 -1
  188. package/dist/src/dom/wait-snapshot.d.ts +1 -0
  189. package/dist/src/dom/wait-snapshot.js +45 -22
  190. package/dist/src/errors.d.ts +1 -1
  191. package/dist/src/errors.js +2 -2
  192. package/dist/src/platform/local-paths.d.ts +1 -0
  193. package/dist/src/platform/local-paths.js +7 -3
  194. package/dist/src/safety/risk.d.ts +1 -0
  195. package/dist/src/safety/risk.js +1 -0
  196. package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +10 -1
  197. package/dist/src/scripts/apply-intelligence-locale-captures.js +219 -7
  198. package/dist/src/scripts/capture-intelligence-locales.d.ts +119 -1
  199. package/dist/src/scripts/capture-intelligence-locales.js +653 -232
  200. package/dist/src/scripts/capture-surface-profile.d.ts +3 -1
  201. package/dist/src/scripts/capture-surface-profile.js +75 -25
  202. package/dist/src/scripts/live-smoke/scenarios.d.ts +3 -0
  203. package/dist/src/scripts/live-smoke/scenarios.js +321 -13
  204. package/dist/src/scripts/live-smoke.js +1 -0
  205. package/dist/src/scripts/locale-capture/surface-graph.d.ts +31 -0
  206. package/dist/src/scripts/locale-capture/surface-graph.js +50 -0
  207. package/dist/src/scripts/release-canary-module.d.ts +20 -0
  208. package/dist/src/scripts/release-canary-module.js +86 -0
  209. package/dist/src/types.d.ts +30 -6
  210. package/package.json +8 -2
  211. package/references/2026-07-16-chat-work-surfaces.md +13 -0
  212. package/references/2026-08-08-model-selection-locale-sweep.md +32 -0
  213. package/references/backend-protocol.md +22 -7
  214. package/references/language-coverage.md +24 -13
  215. package/references/localization.md +36 -14
  216. package/references/python-parity.md +9 -3
  217. 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;
@@ -46,6 +46,7 @@ export const backendCommands = [
46
46
  "messages.wait",
47
47
  "messages.readLatest",
48
48
  "messages.status",
49
+ "messages.stop",
49
50
  "messages.waitAndRead",
50
51
  "artifacts.listLatest",
51
52
  "artifacts.wait",
@@ -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: "create",
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
- const userTab = await selectExistingUserTab(browser, {
344
- target: { type: "selected", host: "chatgpt" },
345
- ifMultiple: "first",
346
- requireChatGPT: true
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?.())?.includes("chatgpt.com") === true) {
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 !== "function") {
373
- return undefined;
374
- }
375
- const tabs = await list.call(browser.tabs);
376
- const normalized = await Promise.all(tabs.map(tab => hydrateTab(browser, tab)));
377
- for (const tab of normalized) {
378
- try {
379
- if ((await tab.url?.())?.includes("chatgpt.com") === true) {
380
- return tab;
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
- catch {
384
- // Keep looking.
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
- function isChatGPTUrl(url) {
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?.includes("chatgpt.com") === true) {
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 suggestedFilename = download.suggestedFilename?.() ?? `chatgpt-download-${Date.now()}`;
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 does not expose saveAs().");
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 visibleText = await readVisibleText(page);
25
- const signedIn = isLikelySignedIn(visibleText);
26
- const classifiedBlocker = classifyVisibleText(visibleText);
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
+ }
@@ -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: {
@@ -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: {