codex-chatgpt-control 0.1.0-alpha.1 → 0.3.0-alpha.1

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 (255) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +55 -0
  3. package/contracts/v1/fixtures/backend-capabilities.json +8 -0
  4. package/contracts/v1/fixtures/blocker-explanation-profiles.json +114 -0
  5. package/contracts/v1/fixtures/command-descriptors.json +224 -16
  6. package/contracts/v1/fixtures/doctor-bridge-upload.json +8 -0
  7. package/contracts/v1/fixtures/doctor-scenario-preflight.json +119 -0
  8. package/contracts/v1/fixtures/existing-tab-diagnostics-blocker.json +57 -0
  9. package/contracts/v1/fixtures/files-preflight-success.json +32 -0
  10. package/contracts/v1/fixtures/help-root.json +1 -1
  11. package/contracts/v1/fixtures/output-json-parse-success.json +17 -2
  12. package/contracts/v1/fixtures/project-sources-plan-add.json +71 -0
  13. package/contracts/v1/fixtures/read-latest-clipped.json +31 -0
  14. package/contracts/v1/fixtures/reports-create-redacted.json +15 -0
  15. package/contracts/v1/fixtures/responses-basic-success.json +11 -0
  16. package/contracts/v1/fixtures/run-basic-success.json +15 -0
  17. package/contracts/v1/fixtures/run-browser-bridge-blocker.json +2 -2
  18. package/contracts/v1/fixtures/run-timeout-partial.json +2 -2
  19. package/contracts/v1/fixtures/runner-budget-blocker.json +1 -1
  20. package/contracts/v1/fixtures/stream-blocked.ndjson +1 -1
  21. package/contracts/v1/fixtures/stream-submitted-completed.ndjson +1 -1
  22. package/contracts/v1/manifest.json +30 -0
  23. package/contracts/v1/parity-suite.json +80 -7
  24. package/contracts/v1/schemas/backend-request.schema.json +3 -0
  25. package/contracts/v1/schemas/command-result.schema.json +48 -0
  26. package/contracts/v1/schemas/response.schema.json +28 -0
  27. package/contracts/v1/schemas/run-result.schema.json +84 -0
  28. package/contracts/v1/surface-drift-policy.json +210 -0
  29. package/dist/codex-chatgpt-control-backend.mjs +7478 -1789
  30. package/dist/codex-chatgpt-control.bundle.mjs +8023 -2266
  31. package/dist/src/backend/client.d.ts +6 -1
  32. package/dist/src/backend/client.js +5 -0
  33. package/dist/src/backend/protocol.d.ts +1 -1
  34. package/dist/src/backend/protocol.js +8 -0
  35. package/dist/src/backend/session.js +16 -0
  36. package/dist/src/browser/attach.js +128 -20
  37. package/dist/src/browser/clipboard.d.ts +11 -0
  38. package/dist/src/browser/clipboard.js +34 -7
  39. package/dist/src/browser/downloads.js +2 -1
  40. package/dist/src/browser/page-state.js +18 -5
  41. package/dist/src/client.d.ts +17 -1
  42. package/dist/src/client.js +89 -9
  43. package/dist/src/commands/artifacts.d.ts +5 -0
  44. package/dist/src/commands/artifacts.js +432 -0
  45. package/dist/src/commands/context.js +8 -3
  46. package/dist/src/commands/conversation.d.ts +15 -0
  47. package/dist/src/commands/conversation.js +44 -0
  48. package/dist/src/commands/deadline.d.ts +8 -0
  49. package/dist/src/commands/deadline.js +14 -0
  50. package/dist/src/commands/doctor.d.ts +10 -2
  51. package/dist/src/commands/doctor.js +249 -14
  52. package/dist/src/commands/files.d.ts +2 -1
  53. package/dist/src/commands/files.js +406 -18
  54. package/dist/src/commands/messages.d.ts +2 -1
  55. package/dist/src/commands/messages.js +430 -98
  56. package/dist/src/commands/modes.d.ts +4 -1
  57. package/dist/src/commands/modes.js +404 -37
  58. package/dist/src/commands/probes.d.ts +15 -0
  59. package/dist/src/commands/probes.js +64 -0
  60. package/dist/src/commands/project-sources.d.ts +10 -0
  61. package/dist/src/commands/project-sources.js +643 -0
  62. package/dist/src/commands/registry.js +108 -14
  63. package/dist/src/commands/reports.d.ts +9 -0
  64. package/dist/src/commands/reports.js +84 -7
  65. package/dist/src/commands/response-actions.js +33 -0
  66. package/dist/src/commands/sequence.js +14 -0
  67. package/dist/src/commands/threads.js +4 -25
  68. package/dist/src/commands/timeouts.d.ts +2 -0
  69. package/dist/src/commands/timeouts.js +18 -0
  70. package/dist/src/diagnostics/blockers.d.ts +54 -0
  71. package/dist/src/diagnostics/blockers.js +299 -0
  72. package/dist/src/dom/artifacts.d.ts +7 -0
  73. package/dist/src/dom/artifacts.js +198 -0
  74. package/dist/src/dom/generation-state.d.ts +15 -0
  75. package/dist/src/dom/generation-state.js +109 -0
  76. package/dist/src/dom/label-match.d.ts +3 -0
  77. package/dist/src/dom/label-match.js +25 -0
  78. package/dist/src/dom/locale/am.d.ts +34 -0
  79. package/dist/src/dom/locale/am.js +34 -0
  80. package/dist/src/dom/locale/ar.d.ts +35 -0
  81. package/dist/src/dom/locale/ar.js +35 -0
  82. package/dist/src/dom/locale/bg.d.ts +35 -0
  83. package/dist/src/dom/locale/bg.js +35 -0
  84. package/dist/src/dom/locale/bn.d.ts +35 -0
  85. package/dist/src/dom/locale/bn.js +35 -0
  86. package/dist/src/dom/locale/bs.d.ts +34 -0
  87. package/dist/src/dom/locale/bs.js +34 -0
  88. package/dist/src/dom/locale/ca.d.ts +34 -0
  89. package/dist/src/dom/locale/ca.js +34 -0
  90. package/dist/src/dom/locale/cs.d.ts +34 -0
  91. package/dist/src/dom/locale/cs.js +34 -0
  92. package/dist/src/dom/locale/da.d.ts +33 -0
  93. package/dist/src/dom/locale/da.js +33 -0
  94. package/dist/src/dom/locale/de.d.ts +36 -0
  95. package/dist/src/dom/locale/de.js +36 -0
  96. package/dist/src/dom/locale/el.d.ts +34 -0
  97. package/dist/src/dom/locale/el.js +34 -0
  98. package/dist/src/dom/locale/en.d.ts +64 -0
  99. package/dist/src/dom/locale/en.js +71 -0
  100. package/dist/src/dom/locale/es-419.d.ts +34 -0
  101. package/dist/src/dom/locale/es-419.js +34 -0
  102. package/dist/src/dom/locale/es-ES.d.ts +34 -0
  103. package/dist/src/dom/locale/es-ES.js +34 -0
  104. package/dist/src/dom/locale/et.d.ts +34 -0
  105. package/dist/src/dom/locale/et.js +34 -0
  106. package/dist/src/dom/locale/fa.d.ts +35 -0
  107. package/dist/src/dom/locale/fa.js +35 -0
  108. package/dist/src/dom/locale/fi.d.ts +34 -0
  109. package/dist/src/dom/locale/fi.js +34 -0
  110. package/dist/src/dom/locale/fr-CA.d.ts +35 -0
  111. package/dist/src/dom/locale/fr-CA.js +35 -0
  112. package/dist/src/dom/locale/fr-FR.d.ts +35 -0
  113. package/dist/src/dom/locale/fr-FR.js +35 -0
  114. package/dist/src/dom/locale/gu.d.ts +34 -0
  115. package/dist/src/dom/locale/gu.js +34 -0
  116. package/dist/src/dom/locale/hi.d.ts +34 -0
  117. package/dist/src/dom/locale/hi.js +34 -0
  118. package/dist/src/dom/locale/hr.d.ts +33 -0
  119. package/dist/src/dom/locale/hr.js +33 -0
  120. package/dist/src/dom/locale/hu.d.ts +34 -0
  121. package/dist/src/dom/locale/hu.js +34 -0
  122. package/dist/src/dom/locale/hy.d.ts +35 -0
  123. package/dist/src/dom/locale/hy.js +35 -0
  124. package/dist/src/dom/locale/id.d.ts +34 -0
  125. package/dist/src/dom/locale/id.js +34 -0
  126. package/dist/src/dom/locale/index.d.ts +83 -0
  127. package/dist/src/dom/locale/index.js +239 -0
  128. package/dist/src/dom/locale/is.d.ts +34 -0
  129. package/dist/src/dom/locale/is.js +34 -0
  130. package/dist/src/dom/locale/it.d.ts +33 -0
  131. package/dist/src/dom/locale/it.js +33 -0
  132. package/dist/src/dom/locale/ja.d.ts +33 -0
  133. package/dist/src/dom/locale/ja.js +33 -0
  134. package/dist/src/dom/locale/ka.d.ts +34 -0
  135. package/dist/src/dom/locale/ka.js +34 -0
  136. package/dist/src/dom/locale/kk.d.ts +34 -0
  137. package/dist/src/dom/locale/kk.js +34 -0
  138. package/dist/src/dom/locale/kn.d.ts +35 -0
  139. package/dist/src/dom/locale/kn.js +35 -0
  140. package/dist/src/dom/locale/ko.d.ts +34 -0
  141. package/dist/src/dom/locale/ko.js +34 -0
  142. package/dist/src/dom/locale/lt.d.ts +35 -0
  143. package/dist/src/dom/locale/lt.js +35 -0
  144. package/dist/src/dom/locale/lv.d.ts +34 -0
  145. package/dist/src/dom/locale/lv.js +34 -0
  146. package/dist/src/dom/locale/mk.d.ts +33 -0
  147. package/dist/src/dom/locale/mk.js +33 -0
  148. package/dist/src/dom/locale/ml.d.ts +35 -0
  149. package/dist/src/dom/locale/ml.js +35 -0
  150. package/dist/src/dom/locale/mn.d.ts +35 -0
  151. package/dist/src/dom/locale/mn.js +35 -0
  152. package/dist/src/dom/locale/mr.d.ts +35 -0
  153. package/dist/src/dom/locale/mr.js +35 -0
  154. package/dist/src/dom/locale/ms.d.ts +34 -0
  155. package/dist/src/dom/locale/ms.js +34 -0
  156. package/dist/src/dom/locale/my.d.ts +34 -0
  157. package/dist/src/dom/locale/my.js +34 -0
  158. package/dist/src/dom/locale/nb.d.ts +34 -0
  159. package/dist/src/dom/locale/nb.js +34 -0
  160. package/dist/src/dom/locale/nl.d.ts +34 -0
  161. package/dist/src/dom/locale/nl.js +34 -0
  162. package/dist/src/dom/locale/pa.d.ts +35 -0
  163. package/dist/src/dom/locale/pa.js +35 -0
  164. package/dist/src/dom/locale/pl.d.ts +34 -0
  165. package/dist/src/dom/locale/pl.js +34 -0
  166. package/dist/src/dom/locale/pt-BR.d.ts +34 -0
  167. package/dist/src/dom/locale/pt-BR.js +34 -0
  168. package/dist/src/dom/locale/pt-PT.d.ts +34 -0
  169. package/dist/src/dom/locale/pt-PT.js +34 -0
  170. package/dist/src/dom/locale/ro.d.ts +33 -0
  171. package/dist/src/dom/locale/ro.js +33 -0
  172. package/dist/src/dom/locale/ru.d.ts +31 -0
  173. package/dist/src/dom/locale/ru.js +31 -0
  174. package/dist/src/dom/locale/sk.d.ts +34 -0
  175. package/dist/src/dom/locale/sk.js +34 -0
  176. package/dist/src/dom/locale/sl.d.ts +34 -0
  177. package/dist/src/dom/locale/sl.js +34 -0
  178. package/dist/src/dom/locale/so.d.ts +34 -0
  179. package/dist/src/dom/locale/so.js +34 -0
  180. package/dist/src/dom/locale/sq.d.ts +34 -0
  181. package/dist/src/dom/locale/sq.js +34 -0
  182. package/dist/src/dom/locale/sr.d.ts +31 -0
  183. package/dist/src/dom/locale/sr.js +31 -0
  184. package/dist/src/dom/locale/sv.d.ts +34 -0
  185. package/dist/src/dom/locale/sv.js +34 -0
  186. package/dist/src/dom/locale/sw.d.ts +34 -0
  187. package/dist/src/dom/locale/sw.js +34 -0
  188. package/dist/src/dom/locale/ta.d.ts +35 -0
  189. package/dist/src/dom/locale/ta.js +35 -0
  190. package/dist/src/dom/locale/te.d.ts +35 -0
  191. package/dist/src/dom/locale/te.js +35 -0
  192. package/dist/src/dom/locale/th.d.ts +34 -0
  193. package/dist/src/dom/locale/th.js +34 -0
  194. package/dist/src/dom/locale/tl.d.ts +28 -0
  195. package/dist/src/dom/locale/tl.js +28 -0
  196. package/dist/src/dom/locale/tr.d.ts +34 -0
  197. package/dist/src/dom/locale/tr.js +34 -0
  198. package/dist/src/dom/locale/types.d.ts +52 -0
  199. package/dist/src/dom/locale/types.js +1 -0
  200. package/dist/src/dom/locale/uk.d.ts +34 -0
  201. package/dist/src/dom/locale/uk.js +34 -0
  202. package/dist/src/dom/locale/ur.d.ts +34 -0
  203. package/dist/src/dom/locale/ur.js +34 -0
  204. package/dist/src/dom/locale/vi.d.ts +34 -0
  205. package/dist/src/dom/locale/vi.js +34 -0
  206. package/dist/src/dom/locale/zh-HK.d.ts +35 -0
  207. package/dist/src/dom/locale/zh-HK.js +35 -0
  208. package/dist/src/dom/locale/zh-Hans.d.ts +35 -0
  209. package/dist/src/dom/locale/zh-Hans.js +35 -0
  210. package/dist/src/dom/locale/zh-TW.d.ts +36 -0
  211. package/dist/src/dom/locale/zh-TW.js +36 -0
  212. package/dist/src/dom/locale-labels.d.ts +26 -0
  213. package/dist/src/dom/locale-labels.js +26 -0
  214. package/dist/src/dom/menus.d.ts +6 -0
  215. package/dist/src/dom/menus.js +47 -10
  216. package/dist/src/dom/message-format.d.ts +2 -1
  217. package/dist/src/dom/message-format.js +67 -10
  218. package/dist/src/dom/messages.d.ts +2 -1
  219. package/dist/src/dom/messages.js +2 -4
  220. package/dist/src/dom/selectors.d.ts +1 -0
  221. package/dist/src/dom/selectors.js +42 -15
  222. package/dist/src/dom/wait-snapshot.d.ts +43 -0
  223. package/dist/src/dom/wait-snapshot.js +127 -0
  224. package/dist/src/index.d.ts +5 -0
  225. package/dist/src/index.js +5 -0
  226. package/dist/src/platform/local-paths.d.ts +4 -0
  227. package/dist/src/platform/local-paths.js +20 -0
  228. package/dist/src/runner/interruptions.js +15 -4
  229. package/dist/src/runner/responses.js +18 -1
  230. package/dist/src/runner/result.js +15 -0
  231. package/dist/src/runner/resume.js +1 -0
  232. package/dist/src/runner/types.d.ts +3 -0
  233. package/dist/src/safety/risk.d.ts +8 -0
  234. package/dist/src/safety/risk.js +8 -0
  235. package/dist/src/safety/untrusted-output.d.ts +73 -0
  236. package/dist/src/safety/untrusted-output.js +199 -0
  237. package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +1 -0
  238. package/dist/src/scripts/apply-intelligence-locale-captures.js +270 -0
  239. package/dist/src/scripts/capture-intelligence-locales.d.ts +25 -0
  240. package/dist/src/scripts/capture-intelligence-locales.js +669 -0
  241. package/dist/src/scripts/check-surface-drift.d.ts +1 -0
  242. package/dist/src/scripts/check-surface-drift.js +11 -0
  243. package/dist/src/scripts/live-smoke/scenarios.js +84 -0
  244. package/dist/src/scripts/locale-capture/language-coverage.d.ts +10 -0
  245. package/dist/src/scripts/locale-capture/language-coverage.js +39 -0
  246. package/dist/src/testing/surface-drift.d.ts +66 -0
  247. package/dist/src/testing/surface-drift.js +363 -0
  248. package/dist/src/types.d.ts +229 -2
  249. package/package.json +3 -2
  250. package/references/backend-protocol.md +96 -2
  251. package/references/language-coverage.md +142 -0
  252. package/references/localization.md +257 -0
  253. package/references/python-parity.md +67 -3
  254. package/references/safety.md +2 -0
  255. package/references/troubleshooting.md +83 -2
@@ -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 { BootstrapArgs, CommandResult, CopyResponseArgs, DownloadLatestArgs, NewThreadArgs, OpenThreadArgs, ReadLatestArgs, SearchThreadsArgs, SelectToolArgs, SequencePlan, SetModeArgs, WaitArgs } from "../types.js";
9
+ import type { ArtifactDownloadArgs, ArtifactWaitArgs, BootstrapArgs, CommandResult, CopyResponseArgs, DownloadLatestArgs, ListArtifactsArgs, NewThreadArgs, OpenThreadArgs, ReadLatestArgs, SearchThreadsArgs, SelectToolArgs, SequencePlan, SetModeArgs, WaitArgs } 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>;
@@ -44,6 +44,11 @@ export type ChatGPTBackendClient = {
44
44
  readLatest(args?: ReadLatestArgs): Promise<CommandResult<unknown>>;
45
45
  copyLatest(args?: CopyResponseArgs): Promise<CommandResult<unknown>>;
46
46
  downloadLatest(args: DownloadLatestArgs): Promise<CommandResult<unknown>>;
47
+ artifacts: {
48
+ listLatest(args?: ListArtifactsArgs): Promise<CommandResult<unknown>>;
49
+ wait(args?: ArtifactWaitArgs): Promise<CommandResult<unknown>>;
50
+ downloadLatest(args: ArtifactDownloadArgs): Promise<CommandResult<unknown>>;
51
+ };
47
52
  runPlan(plan: SequencePlan | NamedWorkflowInvocation): Promise<CommandResult<unknown>>;
48
53
  doctor(args?: DoctorArgs): Promise<CommandResult<DoctorReport>>;
49
54
  createReport(result: CommandResult<unknown>, args?: RunReportOptions): Promise<CommandResult<RunReportData>>;
@@ -76,6 +76,11 @@ export function createChatGPTBackendClient(transport) {
76
76
  readLatest: args => request("messages.readLatest", args ?? {}),
77
77
  waitAndRead: args => request("messages.waitAndRead", args)
78
78
  },
79
+ artifacts: {
80
+ listLatest: args => request("artifacts.listLatest", args ?? {}),
81
+ wait: args => request("artifacts.wait", args ?? {}),
82
+ downloadLatest: args => request("artifacts.downloadLatest", args)
83
+ },
79
84
  files: {
80
85
  attach: args => request("files.attach", args),
81
86
  downloadLatest: args => request("files.downloadLatest", args)
@@ -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", "threads.new", "threads.search", "threads.open", "messages.compose", "messages.submit", "messages.ask", "messages.wait", "messages.readLatest", "messages.waitAndRead", "files.attach", "files.downloadLatest", "modes.set", "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", "threads.new", "threads.search", "threads.open", "messages.compose", "messages.submit", "messages.ask", "messages.wait", "messages.readLatest", "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;
@@ -37,9 +37,17 @@ export const backendCommands = [
37
37
  "messages.wait",
38
38
  "messages.readLatest",
39
39
  "messages.waitAndRead",
40
+ "artifacts.listLatest",
41
+ "artifacts.wait",
42
+ "artifacts.downloadLatest",
43
+ "files.preflight",
40
44
  "files.attach",
41
45
  "files.downloadLatest",
46
+ "projects.sources.list",
47
+ "projects.sources.planAdd",
48
+ "projects.sources.add",
42
49
  "modes.set",
50
+ "modes.get",
43
51
  "tools.select",
44
52
  "response.copy"
45
53
  ];
@@ -141,12 +141,28 @@ async function dispatchBackendCommand(client, request) {
141
141
  return client.messages.readLatest(emptyToUndefined(payload));
142
142
  case "messages.waitAndRead":
143
143
  return client.messages.waitAndRead(payload);
144
+ case "artifacts.listLatest":
145
+ return client.artifacts.listLatest(emptyToUndefined(payload));
146
+ case "artifacts.wait":
147
+ return client.artifacts.wait(emptyToUndefined(payload));
148
+ case "artifacts.downloadLatest":
149
+ return client.artifacts.downloadLatest(payload);
150
+ case "files.preflight":
151
+ return client.files.preflight(payload);
144
152
  case "files.attach":
145
153
  return client.files.attach(payload);
146
154
  case "files.downloadLatest":
147
155
  return client.files.downloadLatest(payload);
156
+ case "projects.sources.list":
157
+ return client.projects.sources.list(payload);
158
+ case "projects.sources.planAdd":
159
+ return client.projects.sources.planAdd(payload);
160
+ case "projects.sources.add":
161
+ return client.projects.sources.add(payload);
148
162
  case "modes.set":
149
163
  return client.modes.set(payload);
164
+ case "modes.get":
165
+ return client.modes.get(emptyToUndefined(payload));
150
166
  case "tools.select":
151
167
  return client.tools.select(payload);
152
168
  case "response.copy":
@@ -2,6 +2,8 @@ import { BrowserBridgeUnavailableError, ChatGPTControlError, LoginRequiredError
2
2
  import { parseConversationId, readPageState } from "./page-state.js";
3
3
  const CHATGPT_HOME = "https://chatgpt.com/";
4
4
  const CHATGPT_HOSTS = new Set(["chatgpt.com", "www.chatgpt.com", "chat.openai.com"]);
5
+ const MAX_EXISTING_TAB_DIAGNOSTIC_CANDIDATES = 10;
6
+ const MAX_EXISTING_TAB_DIAGNOSTIC_FIELD_LENGTH = 240;
5
7
  export async function attachChatGPTBrowser(env, args = {}) {
6
8
  const browser = await getBrowser(env);
7
9
  const page = await getOrCreateChatGPTPage(browser, env, args);
@@ -102,12 +104,12 @@ async function getOrCreateChatGPTPage(browser, env, args) {
102
104
  }
103
105
  if (explicitExistingPolicy !== undefined) {
104
106
  const existing = await selectExistingTab(browser, explicitExistingPolicy);
105
- if (existing !== undefined) {
106
- return existing;
107
+ if (existing.page !== undefined) {
108
+ return existing.page;
107
109
  }
108
110
  const ifMissing = explicitExistingPolicy.ifMissing ?? "block";
109
111
  if (ifMissing === "block") {
110
- throw new ExistingTabSelectionError("No already-open ChatGPT tab matched the requested existing-tab target.", "existing_tab_not_found");
112
+ throw new ExistingTabSelectionError("No already-open ChatGPT tab matched the requested existing-tab target.", "existing_tab_not_found", existing.diagnostics?.candidateTabs, existing.diagnostics);
111
113
  }
112
114
  const missingUrl = ifMissing === "open"
113
115
  ? urlFromExistingTarget(explicitExistingPolicy.target) ?? targetUrl
@@ -163,8 +165,8 @@ function normalizeExplicitExistingTabPolicy(args) {
163
165
  };
164
166
  }
165
167
  async function selectExistingTab(browser, policy) {
166
- const userMatch = await selectExistingUserTab(browser, policy);
167
- if (userMatch !== undefined) {
168
+ const userMatch = await selectExistingUserTab(browser, policy, shouldCollectExistingTabDiagnostics(policy));
169
+ if (userMatch.page !== undefined) {
168
170
  return userMatch;
169
171
  }
170
172
  if (policy.target?.type === "selected" && typeof browser.tabs?.selected === "function") {
@@ -172,7 +174,7 @@ async function selectExistingTab(browser, policy) {
172
174
  if (selected !== undefined) {
173
175
  const normalized = normalizePage(selected);
174
176
  if (await pageMatchesExistingTarget(normalized, policy)) {
175
- return normalized;
177
+ return { page: normalized };
176
178
  }
177
179
  }
178
180
  }
@@ -181,28 +183,37 @@ async function selectExistingTab(browser, policy) {
181
183
  if (tab !== undefined) {
182
184
  const normalized = normalizePage(tab);
183
185
  if (await pageMatchesExistingTarget(normalized, policy)) {
184
- return normalized;
186
+ return { page: normalized };
185
187
  }
186
188
  }
187
189
  }
188
- return undefined;
190
+ return userMatch.diagnostics === undefined
191
+ ? { diagnostics: diagnosticsForUnavailableUserTabs(policy) }
192
+ : userMatch;
189
193
  }
190
- async function selectExistingUserTab(browser, policy) {
194
+ async function selectExistingUserTab(browser, policy, collectDiagnostics) {
191
195
  const openTabs = browser.user?.openTabs;
192
196
  const claimTab = browser.user?.claimTab;
193
197
  if (typeof openTabs !== "function" || typeof claimTab !== "function") {
194
- return undefined;
198
+ return {};
199
+ }
200
+ const tabs = await Promise.resolve(openTabs.call(browser.user)).catch(() => undefined);
201
+ if (tabs === undefined) {
202
+ return collectDiagnostics
203
+ ? { diagnostics: diagnosticsForUnavailableUserTabs(policy, "user_open_tabs_unavailable") }
204
+ : {};
195
205
  }
196
- const tabs = await Promise.resolve(openTabs.call(browser.user)).catch(() => []);
197
206
  const matches = tabs.filter(tab => userTabMatchesTarget(tab, policy));
207
+ const diagnostics = collectDiagnostics ? diagnosticsForUserTabs(policy, tabs, matches) : undefined;
198
208
  if (matches.length === 0) {
199
- return undefined;
209
+ return diagnostics === undefined ? {} : { diagnostics };
200
210
  }
201
211
  if (matches.length > 1 && (policy.ifMultiple ?? "block") !== "first") {
202
- throw new ExistingTabSelectionError("Multiple already-open ChatGPT tabs matched the requested existing-tab target.", "existing_tab_ambiguous", matches);
212
+ throw new ExistingTabSelectionError("Multiple already-open ChatGPT tabs matched the requested existing-tab target.", "existing_tab_ambiguous", matches, diagnostics);
203
213
  }
204
214
  const selected = matches[0];
205
- return normalizePage(await claimTab.call(browser.user, selected));
215
+ const page = normalizePage(await claimTab.call(browser.user, selected));
216
+ return diagnostics === undefined ? { page } : { page, diagnostics };
206
217
  }
207
218
  function userTabMatchesTarget(tab, policy) {
208
219
  const target = policy.target ?? { type: "selected", host: "chatgpt" };
@@ -224,6 +235,100 @@ function userTabMatchesTarget(tab, policy) {
224
235
  return titleMatches(tab.title, target.title, target.exact ?? true);
225
236
  }
226
237
  }
238
+ function diagnosticsForUserTabs(policy, tabs, matches) {
239
+ const chatgptTabs = tabs.filter(tab => isChatGPTUrl(tab.url));
240
+ const candidateTabs = matches.length > 1 ? matches : chatgptTabs;
241
+ const cappedTabs = candidateTabs.slice(0, MAX_EXISTING_TAB_DIAGNOSTIC_CANDIDATES);
242
+ const diagnostics = {
243
+ requestedTarget: diagnosticTarget(policy.target ?? { type: "selected", host: "chatgpt" }),
244
+ userOpenTabsAvailable: true,
245
+ chatgptTabCount: chatgptTabs.length,
246
+ mismatchReason: matches.length > 1 ? "multiple_candidates" : mismatchReasonForNoMatches(policy, tabs, chatgptTabs),
247
+ candidateTabs: cappedTabs.map(diagnosticCandidate)
248
+ };
249
+ const omittedCandidateCount = candidateTabs.length - cappedTabs.length;
250
+ if (omittedCandidateCount > 0)
251
+ diagnostics.omittedCandidateCount = omittedCandidateCount;
252
+ return diagnostics;
253
+ }
254
+ function shouldCollectExistingTabDiagnostics(policy) {
255
+ return (policy.ifMissing ?? "block") === "block" || (policy.ifMultiple ?? "block") !== "first";
256
+ }
257
+ function diagnosticsForUnavailableUserTabs(policy, mismatchReason = undefined) {
258
+ const target = policy.target ?? { type: "selected", host: "chatgpt" };
259
+ return {
260
+ requestedTarget: diagnosticTarget(target),
261
+ userOpenTabsAvailable: false,
262
+ chatgptTabCount: 0,
263
+ mismatchReason: mismatchReason ?? (target.type === "tabId" ? "explicit_tab_id_not_open" : "selected_tab_unavailable"),
264
+ candidateTabs: []
265
+ };
266
+ }
267
+ function diagnosticTarget(target) {
268
+ switch (target.type) {
269
+ case "selected": {
270
+ const value = { type: target.type };
271
+ if (target.host !== undefined)
272
+ value.host = target.host;
273
+ return value;
274
+ }
275
+ case "tabId":
276
+ return { type: target.type, tabId: target.tabId };
277
+ case "conversationId":
278
+ case "conversation_id":
279
+ return { type: target.type, conversationId: target.conversationId };
280
+ case "url":
281
+ return { type: target.type, url: target.url };
282
+ case "title": {
283
+ const value = { type: target.type, title: target.title };
284
+ if (target.exact !== undefined)
285
+ value.exact = target.exact;
286
+ return value;
287
+ }
288
+ }
289
+ }
290
+ function diagnosticCandidate(tab) {
291
+ const candidate = { id: tab.id };
292
+ if (tab.url !== undefined) {
293
+ candidate.url = truncateDiagnosticField(tab.url);
294
+ const conversationId = parseConversationId(tab.url);
295
+ if (conversationId !== undefined)
296
+ candidate.conversationId = conversationId;
297
+ }
298
+ if (tab.title !== undefined)
299
+ candidate.title = truncateDiagnosticField(tab.title);
300
+ if (tab.lastOpened !== undefined)
301
+ candidate.lastOpened = truncateDiagnosticField(tab.lastOpened);
302
+ if (tab.tabGroup !== undefined)
303
+ candidate.tabGroup = truncateDiagnosticField(tab.tabGroup);
304
+ return candidate;
305
+ }
306
+ function truncateDiagnosticField(value) {
307
+ return value.length <= MAX_EXISTING_TAB_DIAGNOSTIC_FIELD_LENGTH
308
+ ? value
309
+ : `${value.slice(0, MAX_EXISTING_TAB_DIAGNOSTIC_FIELD_LENGTH - 1)}…`;
310
+ }
311
+ function mismatchReasonForNoMatches(policy, tabs, chatgptTabs) {
312
+ const target = policy.target ?? { type: "selected", host: "chatgpt" };
313
+ if (tabs.length === 0)
314
+ return "no_candidate";
315
+ if (chatgptTabs.length === 0 && (policy.requireChatGPT ?? targetRequiresChatGPT(target))) {
316
+ return "non_chatgpt_tab";
317
+ }
318
+ switch (target.type) {
319
+ case "tabId":
320
+ return tabs.some(tab => tab.id === target.tabId) ? "non_chatgpt_tab" : "explicit_tab_id_not_open";
321
+ case "conversationId":
322
+ case "conversation_id":
323
+ return "conversation_id_mismatch";
324
+ case "url":
325
+ return "url_mismatch";
326
+ case "title":
327
+ return "title_mismatch";
328
+ case "selected":
329
+ return "selected_tab_unavailable";
330
+ }
331
+ }
227
332
  async function pageMatchesExistingTarget(page, policy) {
228
333
  const url = await Promise.resolve(page.url?.()).catch(() => undefined);
229
334
  const title = await Promise.resolve(page.title?.()).catch(() => undefined);
@@ -239,9 +344,9 @@ async function findExistingChatGPTTab(browser) {
239
344
  target: { type: "selected", host: "chatgpt" },
240
345
  ifMultiple: "first",
241
346
  requireChatGPT: true
242
- });
243
- if (userTab !== undefined) {
244
- return userTab;
347
+ }, false).catch(() => ({ page: undefined }));
348
+ if (userTab.page !== undefined) {
349
+ return userTab.page;
245
350
  }
246
351
  const selected = browser.tabs?.selected;
247
352
  if (typeof selected === "function") {
@@ -282,8 +387,8 @@ async function findExistingChatGPTTab(browser) {
282
387
  return undefined;
283
388
  }
284
389
  class ExistingTabSelectionError extends ChatGPTControlError {
285
- constructor(message, code, candidates = []) {
286
- super(message, "not_found", true, undefined, {
390
+ constructor(message, code, candidates = [], diagnostics) {
391
+ const details = {
287
392
  code,
288
393
  candidates: candidates.map(tab => ({ label: userTabCandidateLabel(tab) })),
289
394
  remediation: [
@@ -298,7 +403,10 @@ class ExistingTabSelectionError extends ChatGPTControlError {
298
403
  userActionRequired: false
299
404
  }
300
405
  ]
301
- });
406
+ };
407
+ if (diagnostics !== undefined)
408
+ details.diagnostics = { existingTab: diagnostics };
409
+ super(message, "not_found", true, undefined, details);
302
410
  }
303
411
  }
304
412
  function targetRequiresChatGPT(target) {
@@ -1,2 +1,13 @@
1
+ export type ClipboardReadCommand = {
2
+ command: string;
3
+ args: string[];
4
+ };
5
+ /**
6
+ * Ordered clipboard-read command candidates for a platform. The first command that
7
+ * succeeds wins; callers fall back to DOM extraction when none do. Linux ordering
8
+ * prefers Wayland's wl-paste only when a Wayland session is detectable, otherwise the
9
+ * X11 tools go first so plain X sessions do not pay a doomed wl-paste attempt.
10
+ */
11
+ export declare function clipboardReadCommandsForPlatform(platform: NodeJS.Platform, env?: Record<string, string | undefined>): ClipboardReadCommand[];
1
12
  export declare function readSystemClipboard(): Promise<string | undefined>;
2
13
  export declare function waitForClipboardChange(before: string | undefined, timeoutMs: number, pollMs?: number): Promise<string | undefined>;
@@ -1,17 +1,44 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
  const execFileAsync = promisify(execFile);
4
- export async function readSystemClipboard() {
5
- if (typeof process === "undefined" || process.platform !== "darwin") {
6
- return undefined;
4
+ /**
5
+ * Ordered clipboard-read command candidates for a platform. The first command that
6
+ * succeeds wins; callers fall back to DOM extraction when none do. Linux ordering
7
+ * prefers Wayland's wl-paste only when a Wayland session is detectable, otherwise the
8
+ * X11 tools go first so plain X sessions do not pay a doomed wl-paste attempt.
9
+ */
10
+ export function clipboardReadCommandsForPlatform(platform, env = {}) {
11
+ if (platform === "darwin") {
12
+ return [{ command: "pbpaste", args: [] }];
13
+ }
14
+ if (platform === "win32") {
15
+ return [{ command: "powershell.exe", args: ["-NoProfile", "-Command", "Get-Clipboard -Raw"] }];
7
16
  }
8
- try {
9
- const { stdout } = await execFileAsync("pbpaste", [], { timeout: 2000, maxBuffer: 10 * 1024 * 1024 });
10
- return stdout;
17
+ if (platform === "linux") {
18
+ const waylandCommand = { command: "wl-paste", args: ["--no-newline"] };
19
+ const x11Commands = [
20
+ { command: "xclip", args: ["-selection", "clipboard", "-o"] },
21
+ { command: "xsel", args: ["--clipboard", "--output"] }
22
+ ];
23
+ const isWayland = typeof env.WAYLAND_DISPLAY === "string" && env.WAYLAND_DISPLAY.length > 0;
24
+ return isWayland ? [waylandCommand, ...x11Commands] : [...x11Commands, waylandCommand];
11
25
  }
12
- catch {
26
+ return [];
27
+ }
28
+ export async function readSystemClipboard() {
29
+ if (typeof process === "undefined") {
13
30
  return undefined;
14
31
  }
32
+ for (const { command, args } of clipboardReadCommandsForPlatform(process.platform, process.env)) {
33
+ try {
34
+ const { stdout } = await execFileAsync(command, args, { timeout: 2000, maxBuffer: 10 * 1024 * 1024 });
35
+ return stdout;
36
+ }
37
+ catch {
38
+ // Missing tool or empty clipboard: try the next candidate.
39
+ }
40
+ }
41
+ return undefined;
15
42
  }
16
43
  export async function waitForClipboardChange(before, timeoutMs, pollMs = 150) {
17
44
  const started = Date.now();
@@ -1,5 +1,6 @@
1
1
  import { mkdir, stat } from "node:fs/promises";
2
2
  import { basename, join, resolve } from "node:path";
3
+ import { localGuardTimeout, withTimeout } from "../commands/timeouts.js";
3
4
  export async function waitForDownloadFromClick(page, click, destDir, timeoutMs) {
4
5
  const absoluteDest = resolve(destDir);
5
6
  await mkdir(absoluteDest, { recursive: true });
@@ -7,7 +8,7 @@ export async function waitForDownloadFromClick(page, click, destDir, timeoutMs)
7
8
  if (downloadPromise === undefined) {
8
9
  throw new Error("The active browser page does not expose download events.");
9
10
  }
10
- await click();
11
+ await withTimeout(click(), localGuardTimeout(timeoutMs, 10000), "Download control click did not complete before the local guard timeout.");
11
12
  const download = await downloadPromise;
12
13
  const suggestedFilename = download.suggestedFilename?.() ?? `chatgpt-download-${Date.now()}`;
13
14
  const targetPath = join(absoluteDest, basename(suggestedFilename));
@@ -1,8 +1,20 @@
1
1
  import { classifyVisibleText } from "../safety/blockers.js";
2
2
  import { compactVisibleText } from "../safety/redaction.js";
3
+ import { escapeRegExp, localeLabels } from "../dom/locale-labels.js";
4
+ import { withTimeout } from "../commands/timeouts.js";
3
5
  export function parseConversationId(url) {
4
- const match = /\/c\/([A-Za-z0-9-]+)/.exec(url);
5
- return match?.[1];
6
+ let parsed;
7
+ try {
8
+ parsed = new URL(url, "https://chatgpt.com");
9
+ }
10
+ catch {
11
+ return undefined;
12
+ }
13
+ const segments = parsed.pathname.split("/").filter(Boolean);
14
+ if (segments[0] !== "c" || segments[1] === undefined || segments[1].length === 0) {
15
+ return undefined;
16
+ }
17
+ return segments[1];
6
18
  }
7
19
  export async function readPageState(page) {
8
20
  const rawUrl = typeof page.url === "function" ? await Promise.resolve(page.url()).catch(() => "") : "";
@@ -32,7 +44,7 @@ export async function readPageState(page) {
32
44
  export async function readVisibleText(page) {
33
45
  if (typeof page.evaluate === "function") {
34
46
  try {
35
- return await page.evaluate(() => document.body?.innerText ?? "");
47
+ return await withTimeout(page.evaluate(() => document.body?.innerText ?? ""), 1000, "Timed out while reading visible page text.");
36
48
  }
37
49
  catch {
38
50
  // Fall back to content parsing below.
@@ -40,7 +52,7 @@ export async function readVisibleText(page) {
40
52
  }
41
53
  if (typeof page.content === "function") {
42
54
  try {
43
- const html = await page.content();
55
+ const html = await withTimeout(page.content(), 1000, "Timed out while reading page content.");
44
56
  return htmlToText(html);
45
57
  }
46
58
  catch {
@@ -62,5 +74,6 @@ export function htmlToText(html) {
62
74
  .trim();
63
75
  }
64
76
  function isLikelySignedIn(visibleText) {
65
- return /\b(New chat|Search chats|Chat with ChatGPT|Recents|Projects)\b/i.test(visibleText);
77
+ const markers = localeLabels.signedInMarkers.map(escapeRegExp).join("|");
78
+ return new RegExp(`\\b(${markers})\\b`, "i").test(visibleText);
66
79
  }
@@ -1,10 +1,11 @@
1
- import type { AskArgs, AttachFilesArgs, BootstrapArgs, CommandResult, CopyResponseArgs, DownloadLatestArgs, NewThreadArgs, OpenThreadArgs, ReadLatestArgs, RuntimeEnv, SearchThreadsArgs, SelectToolArgs, SequencePlan, SetModeArgs, ThreadTarget, WaitArgs } from "./types.js";
1
+ import type { AskArgs, ArtifactDownloadArgs, ArtifactWaitArgs, AttachFilesArgs, BootstrapArgs, CommandResult, CopyResponseArgs, DownloadLatestArgs, FilePreflightArgs, FilePreflightData, GetModeArgs, GetModeData, ListArtifactsArgs, NewThreadArgs, OpenThreadArgs, ProjectSourcesAddArgs, ProjectSourcesAddData, ProjectSourcesAddPlanData, ProjectSourcesListArgs, ProjectSourcesListData, ProjectSourcesPlanAddArgs, ReadLatestArgs, RuntimeEnv, SearchThreadsArgs, SelectToolArgs, SequencePlan, SetModeArgs, ThreadTarget, WaitArgs } 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";
5
5
  import type { ChatGPTAgent, ChatGPTAgentConfig, ChatGPTResponse, ChatGPTRunner, ChatGPTRunInput, ChatGPTRunResult } from "./runner/types.js";
6
6
  import { type ChatGPTResponsesCreateArgs } from "./runner/responses.js";
7
7
  import { type ReportRedactionOptions } from "./safety/report-redaction.js";
8
+ import { type BlockerExplanation, type ExplainBlockerOptions } from "./diagnostics/blockers.js";
8
9
  export type ChatGPTClientOptions = RuntimeEnv & {
9
10
  defaults?: {
10
11
  mode?: SetModeArgs;
@@ -112,9 +113,15 @@ export type ChatGPTClient = {
112
113
  readLatest(args?: ReadLatestArgs): Promise<CommandResult<unknown>>;
113
114
  copyLatest(args?: CopyResponseArgs): Promise<CommandResult<unknown>>;
114
115
  downloadLatest(args: DownloadLatestArgs): Promise<CommandResult<unknown>>;
116
+ artifacts: {
117
+ listLatest(args?: ListArtifactsArgs): Promise<CommandResult<unknown>>;
118
+ wait(args?: ArtifactWaitArgs): Promise<CommandResult<unknown>>;
119
+ downloadLatest(args: ArtifactDownloadArgs): Promise<CommandResult<unknown>>;
120
+ };
115
121
  runPlan(plan: SequencePlan | NamedWorkflowInvocation): Promise<CommandResult<unknown>>;
116
122
  doctor(args?: DoctorArgs): Promise<CommandResult<DoctorReport>>;
117
123
  createReport(result: CommandResult<unknown>, args?: RunReportOptions): Promise<CommandResult<RunReportData>>;
124
+ explainBlocker(resultOrBlocker: CommandResult<unknown> | NonNullable<CommandResult["blocker"]> | undefined, options?: ExplainBlockerOptions): BlockerExplanation;
118
125
  reports: {
119
126
  create(result: CommandResult<unknown>, args?: RunReportOptions): Promise<CommandResult<RunReportData>>;
120
127
  redact(value: unknown, args?: ReportRedactionOptions): Promise<CommandResult<unknown>>;
@@ -151,11 +158,20 @@ export type ChatGPTClient = {
151
158
  waitAndRead(args?: WaitArgs & ReadLatestArgs): Promise<CommandResult<unknown>>;
152
159
  };
153
160
  files: {
161
+ preflight(args: FilePreflightArgs): Promise<CommandResult<FilePreflightData>>;
154
162
  attach(args: AttachFilesArgs): Promise<CommandResult<unknown>>;
155
163
  downloadLatest(args: DownloadLatestArgs): Promise<CommandResult<unknown>>;
156
164
  };
165
+ projects: {
166
+ sources: {
167
+ list(args: ProjectSourcesListArgs): Promise<CommandResult<ProjectSourcesListData>>;
168
+ planAdd(args: ProjectSourcesPlanAddArgs): Promise<CommandResult<ProjectSourcesAddPlanData>>;
169
+ add(args: ProjectSourcesAddArgs): Promise<CommandResult<ProjectSourcesAddData | ProjectSourcesAddPlanData>>;
170
+ };
171
+ };
157
172
  modes: {
158
173
  set(args: SetModeArgs): Promise<CommandResult<unknown>>;
174
+ get(args?: GetModeArgs): Promise<CommandResult<GetModeData>>;
159
175
  };
160
176
  tools: {
161
177
  select(args: SelectToolArgs): Promise<CommandResult<unknown>>;