codex-chatgpt-control 0.1.0-alpha.1 → 0.2.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 (242) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +55 -0
  3. package/contracts/v1/fixtures/backend-capabilities.json +7 -0
  4. package/contracts/v1/fixtures/blocker-explanation-profiles.json +114 -0
  5. package/contracts/v1/fixtures/command-descriptors.json +192 -15
  6. package/contracts/v1/fixtures/doctor-bridge-upload.json +8 -0
  7. package/contracts/v1/fixtures/doctor-scenario-preflight.json +108 -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 +72 -6
  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 +5137 -488
  30. package/dist/codex-chatgpt-control.bundle.mjs +5001 -286
  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 +7 -0
  35. package/dist/src/backend/session.js +14 -0
  36. package/dist/src/browser/attach.js +128 -20
  37. package/dist/src/browser/downloads.js +2 -1
  38. package/dist/src/browser/page-state.js +6 -3
  39. package/dist/src/client.d.ts +16 -1
  40. package/dist/src/client.js +86 -7
  41. package/dist/src/commands/artifacts.d.ts +5 -0
  42. package/dist/src/commands/artifacts.js +432 -0
  43. package/dist/src/commands/context.js +5 -2
  44. package/dist/src/commands/doctor.d.ts +10 -2
  45. package/dist/src/commands/doctor.js +242 -14
  46. package/dist/src/commands/files.d.ts +2 -1
  47. package/dist/src/commands/files.js +352 -18
  48. package/dist/src/commands/messages.d.ts +2 -1
  49. package/dist/src/commands/messages.js +232 -40
  50. package/dist/src/commands/modes.js +202 -20
  51. package/dist/src/commands/project-sources.d.ts +10 -0
  52. package/dist/src/commands/project-sources.js +643 -0
  53. package/dist/src/commands/registry.js +76 -14
  54. package/dist/src/commands/reports.d.ts +9 -0
  55. package/dist/src/commands/reports.js +83 -6
  56. package/dist/src/commands/response-actions.js +33 -0
  57. package/dist/src/commands/sequence.js +14 -0
  58. package/dist/src/commands/threads.js +2 -1
  59. package/dist/src/commands/timeouts.d.ts +2 -0
  60. package/dist/src/commands/timeouts.js +18 -0
  61. package/dist/src/diagnostics/blockers.d.ts +54 -0
  62. package/dist/src/diagnostics/blockers.js +299 -0
  63. package/dist/src/dom/artifacts.d.ts +7 -0
  64. package/dist/src/dom/artifacts.js +198 -0
  65. package/dist/src/dom/generation-state.d.ts +8 -0
  66. package/dist/src/dom/generation-state.js +103 -0
  67. package/dist/src/dom/locale/am.d.ts +28 -0
  68. package/dist/src/dom/locale/am.js +28 -0
  69. package/dist/src/dom/locale/ar.d.ts +28 -0
  70. package/dist/src/dom/locale/ar.js +28 -0
  71. package/dist/src/dom/locale/bg.d.ts +28 -0
  72. package/dist/src/dom/locale/bg.js +28 -0
  73. package/dist/src/dom/locale/bn.d.ts +28 -0
  74. package/dist/src/dom/locale/bn.js +28 -0
  75. package/dist/src/dom/locale/bs.d.ts +28 -0
  76. package/dist/src/dom/locale/bs.js +28 -0
  77. package/dist/src/dom/locale/ca.d.ts +28 -0
  78. package/dist/src/dom/locale/ca.js +28 -0
  79. package/dist/src/dom/locale/cs.d.ts +28 -0
  80. package/dist/src/dom/locale/cs.js +28 -0
  81. package/dist/src/dom/locale/da.d.ts +28 -0
  82. package/dist/src/dom/locale/da.js +28 -0
  83. package/dist/src/dom/locale/de.d.ts +30 -0
  84. package/dist/src/dom/locale/de.js +30 -0
  85. package/dist/src/dom/locale/el.d.ts +28 -0
  86. package/dist/src/dom/locale/el.js +28 -0
  87. package/dist/src/dom/locale/en.d.ts +50 -0
  88. package/dist/src/dom/locale/en.js +56 -0
  89. package/dist/src/dom/locale/es-419.d.ts +28 -0
  90. package/dist/src/dom/locale/es-419.js +28 -0
  91. package/dist/src/dom/locale/es-ES.d.ts +28 -0
  92. package/dist/src/dom/locale/es-ES.js +28 -0
  93. package/dist/src/dom/locale/et.d.ts +28 -0
  94. package/dist/src/dom/locale/et.js +28 -0
  95. package/dist/src/dom/locale/fa.d.ts +28 -0
  96. package/dist/src/dom/locale/fa.js +28 -0
  97. package/dist/src/dom/locale/fi.d.ts +28 -0
  98. package/dist/src/dom/locale/fi.js +28 -0
  99. package/dist/src/dom/locale/fr-CA.d.ts +29 -0
  100. package/dist/src/dom/locale/fr-CA.js +29 -0
  101. package/dist/src/dom/locale/fr-FR.d.ts +30 -0
  102. package/dist/src/dom/locale/fr-FR.js +30 -0
  103. package/dist/src/dom/locale/gu.d.ts +28 -0
  104. package/dist/src/dom/locale/gu.js +28 -0
  105. package/dist/src/dom/locale/hi.d.ts +28 -0
  106. package/dist/src/dom/locale/hi.js +28 -0
  107. package/dist/src/dom/locale/hr.d.ts +28 -0
  108. package/dist/src/dom/locale/hr.js +28 -0
  109. package/dist/src/dom/locale/hu.d.ts +28 -0
  110. package/dist/src/dom/locale/hu.js +28 -0
  111. package/dist/src/dom/locale/hy.d.ts +28 -0
  112. package/dist/src/dom/locale/hy.js +28 -0
  113. package/dist/src/dom/locale/id.d.ts +28 -0
  114. package/dist/src/dom/locale/id.js +28 -0
  115. package/dist/src/dom/locale/index.d.ts +79 -0
  116. package/dist/src/dom/locale/index.js +202 -0
  117. package/dist/src/dom/locale/is.d.ts +28 -0
  118. package/dist/src/dom/locale/is.js +28 -0
  119. package/dist/src/dom/locale/it.d.ts +27 -0
  120. package/dist/src/dom/locale/it.js +27 -0
  121. package/dist/src/dom/locale/ja.d.ts +27 -0
  122. package/dist/src/dom/locale/ja.js +27 -0
  123. package/dist/src/dom/locale/ka.d.ts +28 -0
  124. package/dist/src/dom/locale/ka.js +28 -0
  125. package/dist/src/dom/locale/kk.d.ts +28 -0
  126. package/dist/src/dom/locale/kk.js +28 -0
  127. package/dist/src/dom/locale/kn.d.ts +28 -0
  128. package/dist/src/dom/locale/kn.js +28 -0
  129. package/dist/src/dom/locale/ko.d.ts +28 -0
  130. package/dist/src/dom/locale/ko.js +28 -0
  131. package/dist/src/dom/locale/lt.d.ts +28 -0
  132. package/dist/src/dom/locale/lt.js +28 -0
  133. package/dist/src/dom/locale/lv.d.ts +28 -0
  134. package/dist/src/dom/locale/lv.js +28 -0
  135. package/dist/src/dom/locale/mk.d.ts +28 -0
  136. package/dist/src/dom/locale/mk.js +28 -0
  137. package/dist/src/dom/locale/ml.d.ts +28 -0
  138. package/dist/src/dom/locale/ml.js +28 -0
  139. package/dist/src/dom/locale/mn.d.ts +28 -0
  140. package/dist/src/dom/locale/mn.js +28 -0
  141. package/dist/src/dom/locale/mr.d.ts +28 -0
  142. package/dist/src/dom/locale/mr.js +28 -0
  143. package/dist/src/dom/locale/ms.d.ts +28 -0
  144. package/dist/src/dom/locale/ms.js +28 -0
  145. package/dist/src/dom/locale/my.d.ts +28 -0
  146. package/dist/src/dom/locale/my.js +28 -0
  147. package/dist/src/dom/locale/nb.d.ts +28 -0
  148. package/dist/src/dom/locale/nb.js +28 -0
  149. package/dist/src/dom/locale/nl.d.ts +28 -0
  150. package/dist/src/dom/locale/nl.js +28 -0
  151. package/dist/src/dom/locale/pa.d.ts +28 -0
  152. package/dist/src/dom/locale/pa.js +28 -0
  153. package/dist/src/dom/locale/pl.d.ts +28 -0
  154. package/dist/src/dom/locale/pl.js +28 -0
  155. package/dist/src/dom/locale/pt-BR.d.ts +28 -0
  156. package/dist/src/dom/locale/pt-BR.js +28 -0
  157. package/dist/src/dom/locale/pt-PT.d.ts +28 -0
  158. package/dist/src/dom/locale/pt-PT.js +28 -0
  159. package/dist/src/dom/locale/ro.d.ts +28 -0
  160. package/dist/src/dom/locale/ro.js +28 -0
  161. package/dist/src/dom/locale/ru.d.ts +28 -0
  162. package/dist/src/dom/locale/ru.js +28 -0
  163. package/dist/src/dom/locale/sk.d.ts +28 -0
  164. package/dist/src/dom/locale/sk.js +28 -0
  165. package/dist/src/dom/locale/sl.d.ts +28 -0
  166. package/dist/src/dom/locale/sl.js +28 -0
  167. package/dist/src/dom/locale/so.d.ts +28 -0
  168. package/dist/src/dom/locale/so.js +28 -0
  169. package/dist/src/dom/locale/sq.d.ts +28 -0
  170. package/dist/src/dom/locale/sq.js +28 -0
  171. package/dist/src/dom/locale/sr.d.ts +28 -0
  172. package/dist/src/dom/locale/sr.js +28 -0
  173. package/dist/src/dom/locale/sv.d.ts +28 -0
  174. package/dist/src/dom/locale/sv.js +28 -0
  175. package/dist/src/dom/locale/sw.d.ts +28 -0
  176. package/dist/src/dom/locale/sw.js +28 -0
  177. package/dist/src/dom/locale/ta.d.ts +28 -0
  178. package/dist/src/dom/locale/ta.js +28 -0
  179. package/dist/src/dom/locale/te.d.ts +28 -0
  180. package/dist/src/dom/locale/te.js +28 -0
  181. package/dist/src/dom/locale/th.d.ts +28 -0
  182. package/dist/src/dom/locale/th.js +28 -0
  183. package/dist/src/dom/locale/tl.d.ts +28 -0
  184. package/dist/src/dom/locale/tl.js +28 -0
  185. package/dist/src/dom/locale/tr.d.ts +28 -0
  186. package/dist/src/dom/locale/tr.js +28 -0
  187. package/dist/src/dom/locale/types.d.ts +45 -0
  188. package/dist/src/dom/locale/types.js +1 -0
  189. package/dist/src/dom/locale/uk.d.ts +28 -0
  190. package/dist/src/dom/locale/uk.js +28 -0
  191. package/dist/src/dom/locale/ur.d.ts +28 -0
  192. package/dist/src/dom/locale/ur.js +28 -0
  193. package/dist/src/dom/locale/vi.d.ts +28 -0
  194. package/dist/src/dom/locale/vi.js +28 -0
  195. package/dist/src/dom/locale/zh-HK.d.ts +28 -0
  196. package/dist/src/dom/locale/zh-HK.js +28 -0
  197. package/dist/src/dom/locale/zh-Hans.d.ts +28 -0
  198. package/dist/src/dom/locale/zh-Hans.js +28 -0
  199. package/dist/src/dom/locale/zh-TW.d.ts +29 -0
  200. package/dist/src/dom/locale/zh-TW.js +29 -0
  201. package/dist/src/dom/locale-labels.d.ts +26 -0
  202. package/dist/src/dom/locale-labels.js +26 -0
  203. package/dist/src/dom/menus.d.ts +6 -0
  204. package/dist/src/dom/menus.js +34 -8
  205. package/dist/src/dom/message-format.d.ts +2 -1
  206. package/dist/src/dom/message-format.js +67 -10
  207. package/dist/src/dom/messages.d.ts +2 -1
  208. package/dist/src/dom/messages.js +2 -4
  209. package/dist/src/dom/selectors.d.ts +1 -0
  210. package/dist/src/dom/selectors.js +42 -15
  211. package/dist/src/index.d.ts +5 -0
  212. package/dist/src/index.js +5 -0
  213. package/dist/src/platform/local-paths.d.ts +4 -0
  214. package/dist/src/platform/local-paths.js +20 -0
  215. package/dist/src/runner/interruptions.js +15 -4
  216. package/dist/src/runner/responses.js +18 -1
  217. package/dist/src/runner/result.js +15 -0
  218. package/dist/src/runner/resume.js +1 -0
  219. package/dist/src/runner/types.d.ts +3 -0
  220. package/dist/src/safety/risk.d.ts +7 -0
  221. package/dist/src/safety/risk.js +7 -0
  222. package/dist/src/safety/untrusted-output.d.ts +73 -0
  223. package/dist/src/safety/untrusted-output.js +199 -0
  224. package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +1 -0
  225. package/dist/src/scripts/apply-intelligence-locale-captures.js +184 -0
  226. package/dist/src/scripts/capture-intelligence-locales.d.ts +25 -0
  227. package/dist/src/scripts/capture-intelligence-locales.js +669 -0
  228. package/dist/src/scripts/check-surface-drift.d.ts +1 -0
  229. package/dist/src/scripts/check-surface-drift.js +11 -0
  230. package/dist/src/scripts/live-smoke/scenarios.js +84 -0
  231. package/dist/src/scripts/locale-capture/language-coverage.d.ts +10 -0
  232. package/dist/src/scripts/locale-capture/language-coverage.js +39 -0
  233. package/dist/src/testing/surface-drift.d.ts +66 -0
  234. package/dist/src/testing/surface-drift.js +363 -0
  235. package/dist/src/types.d.ts +201 -2
  236. package/package.json +2 -1
  237. package/references/backend-protocol.md +93 -1
  238. package/references/language-coverage.md +142 -0
  239. package/references/localization.md +247 -0
  240. package/references/python-parity.md +63 -1
  241. package/references/safety.md +2 -0
  242. package/references/troubleshooting.md +58 -0
@@ -1,21 +1,50 @@
1
+ import { constants } from "node:fs";
2
+ import { access, stat } from "node:fs/promises";
1
3
  import { readSystemClipboard } from "../browser/clipboard.js";
2
4
  import { readPageState } from "../browser/page-state.js";
5
+ import { localeLabels } from "../dom/locale-labels.js";
3
6
  import { BROWSER_BRIDGE_REMEDIATION, resultOk } from "../errors.js";
7
+ import { explainCommandBlocker } from "../diagnostics/blockers.js";
8
+ import { preflightFiles } from "./files.js";
4
9
  import { contextFromPage } from "./context.js";
5
10
  import { bootstrap } from "./session.js";
6
11
  const DEFAULT_CHECKS = ["bridge", "login", "upload", "download", "clipboard", "modes", "tools", "selectors"];
12
+ const BOOTSTRAP_CHECKS = new Set(["bridge", "login", "upload", "download", "modes", "tools", "selectors"]);
7
13
  const UPLOAD_REMEDIATION = [
8
14
  "Codex Settings > Computer Use > Chrome > Permissions > Uploads: set to Always allow, or add chatgpt.com to the allowed upload domains.",
9
15
  "Chrome chrome://extensions > Codex extension > Details: enable Allow access to file URLs."
10
16
  ];
17
+ const REQUIRED_LOCALE_KEYS = [
18
+ "composerTextbox",
19
+ "sendButton",
20
+ "searchChatsButton",
21
+ "searchChatsPlaceholder",
22
+ "newChat",
23
+ "addFilesButton",
24
+ "addPhotosFilesMenuItem",
25
+ "copyResponse",
26
+ "download",
27
+ "modeLabels",
28
+ "signedInMarkers",
29
+ "loginBlocker",
30
+ "captchaBlocker",
31
+ "rateLimitBlocker"
32
+ ];
33
+ const REQUIRED_TOOL_IDS = ["web_search", "deep_research", "create_image"];
11
34
  export async function doctor(env, args = {}) {
12
35
  const wanted = args.check ?? DEFAULT_CHECKS;
13
36
  const checks = {};
14
- const boot = await bootstrap(env, { preferExistingTab: true, timeoutMs: 30000 });
37
+ const wantsExistingTab = wanted.includes("existing_tab");
38
+ const existingTab = wantsExistingTab ? normalizeDoctorExistingTab(args.existingTab) : undefined;
39
+ const boot = wantsExistingTab || wanted.some(check => BOOTSTRAP_CHECKS.has(check))
40
+ ? await bootstrap(env, existingTab === undefined
41
+ ? { preferExistingTab: true, timeoutMs: 30000 }
42
+ : { existingTab, preferExistingTab: false, timeoutMs: 30000 })
43
+ : undefined;
15
44
  for (const check of wanted) {
16
45
  switch (check) {
17
46
  case "bridge":
18
- checks.bridge = boot.ok
47
+ checks.bridge = boot?.ok
19
48
  ? ok("Chrome bridge is available.")
20
49
  : bridgeCheck(boot);
21
50
  break;
@@ -40,14 +69,34 @@ export async function doctor(env, args = {}) {
40
69
  case "selectors":
41
70
  checks.selectors = selectorCheck(env, "Basic page selectors are available.");
42
71
  break;
72
+ case "existing_tab":
73
+ checks.existing_tab = existingTab === undefined || boot === undefined
74
+ ? blocked("Existing-tab readiness was requested, but bootstrap was not initialized.")
75
+ : existingTabCheck(existingTab, boot);
76
+ break;
77
+ case "artifacts":
78
+ checks.artifacts = artifactsCheck(env);
79
+ break;
80
+ case "file_preflight":
81
+ checks.file_preflight = await filePreflightCheck(env, args);
82
+ break;
83
+ case "localization":
84
+ checks.localization = localizationCheck(env);
85
+ break;
86
+ case "reports":
87
+ checks.reports = await reportsCheck(args.report);
88
+ break;
43
89
  }
44
90
  }
45
91
  const ready = Object.values(checks).every(check => check?.status === "ok" || check?.status === "unknown");
46
92
  return resultOk({ ready, checks }, await contextFromPage(env.page));
47
93
  }
48
94
  function bridgeCheck(boot) {
95
+ if (boot === undefined) {
96
+ return unknown("Bridge readiness was not requested.");
97
+ }
49
98
  if (boot.blocker?.kind === "browser_bridge_unavailable") {
50
- return blocked(boot.blocker.message, bridgeRemediation(boot));
99
+ return withBlockerDetails(blocked(boot.blocker.message, bridgeRemediation(boot)), boot, "session.bootstrap");
51
100
  }
52
101
  if (boot.blocker?.kind === "login_required") {
53
102
  return ok("Chrome bridge is available; ChatGPT login is required before browser-control commands can continue.");
@@ -58,11 +107,11 @@ function bridgeCheck(boot) {
58
107
  return blocked(boot.error?.message ?? "Chrome bridge is unavailable.");
59
108
  }
60
109
  async function loginCheck(env, boot) {
61
- if (!boot.ok && boot.blocker?.kind === "login_required") {
62
- return blocked("ChatGPT login is required.", ["Ask the user to sign in to ChatGPT in Chrome, then retry."]);
110
+ if (boot !== undefined && !boot.ok && boot.blocker?.kind === "login_required") {
111
+ return withBlockerDetails(blocked("ChatGPT login is required.", ["Ask the user to sign in to ChatGPT in Chrome, then retry."]), boot, "session.bootstrap");
63
112
  }
64
113
  if (env.page === undefined) {
65
- return boot.ok ? ok("Bootstrap completed; login appears usable.") : blocked("Cannot determine login because bootstrap failed.");
114
+ return boot?.ok ? ok("Bootstrap completed; login appears usable.") : blocked("Cannot determine login because bootstrap failed.");
66
115
  }
67
116
  const state = await readPageState(env.page).catch(() => undefined);
68
117
  if (state?.blocker?.kind === "login_required") {
@@ -102,19 +151,198 @@ function selectorCheck(env, message) {
102
151
  ? ok(message)
103
152
  : unsupported("The active page object does not expose locator or role selector helpers.");
104
153
  }
154
+ function existingTabCheck(existingTab, boot) {
155
+ if (boot.ok) {
156
+ return ok("Existing ChatGPT tab target can be claimed.", {
157
+ target: existingTab.target,
158
+ tabId: boot.context.tabId,
159
+ url: boot.context.url,
160
+ conversationId: boot.context.conversationId
161
+ });
162
+ }
163
+ return withBlockerDetails(blocked(boot.blocker?.message ?? boot.error?.message ?? "Existing ChatGPT tab target could not be claimed."), boot, "session.bootstrap");
164
+ }
165
+ function normalizeDoctorExistingTab(existingTab) {
166
+ if (existingTab !== undefined && existingTab !== true && existingTab !== false) {
167
+ return {
168
+ requireChatGPT: true,
169
+ ifMissing: "block",
170
+ ifMultiple: existingTab.target?.type === "selected" ? "first" : "block",
171
+ ...existingTab
172
+ };
173
+ }
174
+ return {
175
+ target: { type: "selected", host: "chatgpt" },
176
+ ifMissing: "block",
177
+ ifMultiple: "block",
178
+ requireChatGPT: true
179
+ };
180
+ }
181
+ function artifactsCheck(env) {
182
+ const page = env.page;
183
+ if (page === undefined) {
184
+ return unknown("Artifact readiness requires an already bootstrapped ChatGPT page.", undefined, {
185
+ pageAvailable: false
186
+ }, "session.bootstrap");
187
+ }
188
+ const selectorsAvailable = typeof page.locator === "function" || typeof page.getByRole === "function";
189
+ const downloadEventsAvailable = typeof page.waitForEvent === "function";
190
+ const domEvaluateAvailable = typeof page.evaluate === "function";
191
+ const pageAssetsAvailable = typeof page.capabilities?.get === "function";
192
+ const details = {
193
+ pageAvailable: true,
194
+ selectorsAvailable,
195
+ downloadEventsAvailable,
196
+ domEvaluateAvailable,
197
+ pageAssetsAvailable
198
+ };
199
+ if (selectorsAvailable && (downloadEventsAvailable || domEvaluateAvailable || pageAssetsAvailable)) {
200
+ return ok("Artifact primitives can inspect the current page without requesting generation.", details);
201
+ }
202
+ return unknown("Artifact primitives need selector support plus download, DOM, or page-assets support to prove readiness.", undefined, details);
203
+ }
204
+ async function filePreflightCheck(env, args) {
205
+ const paths = args.files ?? [];
206
+ const result = await preflightFiles(env, { paths });
207
+ const pathCount = paths.length;
208
+ if (result.ok && result.data !== undefined) {
209
+ return ok(pathCount === 0
210
+ ? "No file paths were supplied; file preflight has no local files to validate."
211
+ : "File preflight completed without blocking local file issues.", {
212
+ pathCount,
213
+ totalBytes: result.data.totalBytes,
214
+ warnings: result.warnings,
215
+ files: result.data.files.map(file => ({
216
+ name: file.name,
217
+ bytes: file.bytes,
218
+ extension: file.extension,
219
+ mimeType: file.mimeType,
220
+ category: file.category
221
+ }))
222
+ });
223
+ }
224
+ return withBlockerDetails(blocked(result.blocker?.message ?? result.error?.message ?? "File preflight failed.", result.blocker?.remediation?.map(step => `${step.label}: ${step.instruction}`), {
225
+ pathCount,
226
+ warnings: result.warnings
227
+ }), result, "files.preflight");
228
+ }
229
+ function localizationCheck(env) {
230
+ const requiredKeysMissing = REQUIRED_LOCALE_KEYS.filter(key => localeLabels[key].length === 0);
231
+ const missingToolIds = REQUIRED_TOOL_IDS.filter(id => (localeLabels.tools[id]?.length ?? 0) === 0);
232
+ const toolIds = Object.keys(localeLabels.tools);
233
+ const englishCanonicalPresent = localeLabels.composerTextbox[0] === "Chat with ChatGPT"
234
+ && localeLabels.sendButton[0] === "Send prompt"
235
+ && localeLabels.modeLabels.includes("Thinking")
236
+ && localeLabels.tools.web_search?.[0] === "Web search";
237
+ const labelCandidateCount = REQUIRED_LOCALE_KEYS.reduce((total, key) => total + localeLabels[key].length, 0)
238
+ + Object.values(localeLabels.tools).reduce((total, values) => total + values.length, 0);
239
+ const details = {
240
+ englishCanonicalPresent,
241
+ requiredKeysMissing,
242
+ missingToolIds,
243
+ toolIds,
244
+ labelCandidateCount,
245
+ pageAvailable: env.page !== undefined,
246
+ runtimeSelectorCoverage: "registry_only_stage_2"
247
+ };
248
+ if (englishCanonicalPresent && requiredKeysMissing.length === 0 && missingToolIds.length === 0) {
249
+ return unknown("The locale registry is loaded; localized runtime selector coverage is registry-only in Stage 2 and not fully proven.", undefined, details);
250
+ }
251
+ return blocked("The locale registry is missing canonical labels required for selector fallback.", ["Update src/dom/locale-labels.ts or src/dom/locale/* with verified visible labels before relying on localized controls."], details, "selector_drift");
252
+ }
253
+ async function reportsCheck(options) {
254
+ const destDir = options?.destDir ?? "reports/runs";
255
+ const includeContent = options?.includeContent === true;
256
+ const details = {
257
+ destDir,
258
+ includeContent,
259
+ redactionDefault: !includeContent,
260
+ maxPreviewChars: options?.maxPreviewChars ?? 240
261
+ };
262
+ try {
263
+ const current = await stat(destDir);
264
+ if (!current.isDirectory()) {
265
+ return unsupported("Report destination exists but is not a directory.", undefined, details);
266
+ }
267
+ await access(destDir, constants.W_OK);
268
+ return ok(includeContent
269
+ ? "Report destination is writable; raw content persistence is enabled by request."
270
+ : "Report destination is writable and redaction is enabled by default.", details);
271
+ }
272
+ catch (error) {
273
+ if (isNodeError(error) && error.code === "ENOENT") {
274
+ return unknown("Report destination does not exist yet; createReport will create it when a report is written.", undefined, {
275
+ ...details,
276
+ exists: false
277
+ }, "createReport");
278
+ }
279
+ if (isNodeError(error) && (error.code === "EACCES" || error.code === "EPERM")) {
280
+ return blocked("Report destination is not writable.", ["Choose a writable report destDir or update filesystem permissions."], details, "permission");
281
+ }
282
+ return unknown(`Report destination writability could not be proven: ${error instanceof Error ? error.message : String(error)}`, undefined, details);
283
+ }
284
+ }
105
285
  function bridgeRemediation(boot) {
106
286
  const remediation = boot.blocker?.remediation ?? BROWSER_BRIDGE_REMEDIATION;
107
287
  return remediation.map(step => `${step.label}: ${step.instruction}`);
108
288
  }
109
- function ok(message) {
110
- return { status: "ok", message };
289
+ function withBlockerDetails(check, result, command) {
290
+ if (result.blocker === undefined) {
291
+ return check;
292
+ }
293
+ const explanation = explainCommandBlocker(result, { command });
294
+ const details = {
295
+ severity: explanation.severity,
296
+ category: explanation.category,
297
+ userActionRequired: explanation.userActionRequired
298
+ };
299
+ if (explanation.diagnostics?.existingTab !== undefined) {
300
+ details.existingTab = explanation.diagnostics.existingTab;
301
+ }
302
+ if (explanation.candidates !== undefined) {
303
+ details.candidates = explanation.candidates;
304
+ }
305
+ const nextCommand = explanation.nextCommands[0];
306
+ const enriched = {
307
+ ...check,
308
+ blockerKind: explanation.kind,
309
+ details
310
+ };
311
+ if (result.blocker.code !== undefined)
312
+ enriched.code = result.blocker.code;
313
+ if (check.remediation === undefined && explanation.remediation.length > 0) {
314
+ enriched.remediation = explanation.remediation.map(step => `${step.label}: ${step.instruction}`);
315
+ }
316
+ if (nextCommand !== undefined)
317
+ enriched.nextCommand = nextCommand;
318
+ return enriched;
319
+ }
320
+ function ok(message, details) {
321
+ return details === undefined ? { status: "ok", message } : { status: "ok", message, details };
322
+ }
323
+ function blocked(message, remediation, details, blockerKind, code) {
324
+ return capability("blocked", message, remediation, details, undefined, blockerKind, code);
325
+ }
326
+ function unsupported(message, remediation, details, nextCommand, code) {
327
+ return capability("unsupported", message, remediation, details, nextCommand, undefined, code);
111
328
  }
112
- function blocked(message, remediation) {
113
- return remediation === undefined ? { status: "blocked", message } : { status: "blocked", message, remediation };
329
+ function unknown(message, remediation, details, nextCommand) {
330
+ return capability("unknown", message, remediation, details, nextCommand);
114
331
  }
115
- function unsupported(message, remediation) {
116
- return remediation === undefined ? { status: "unsupported", message } : { status: "unsupported", message, remediation };
332
+ function capability(status, message, remediation, details, nextCommand, blockerKind, code) {
333
+ const check = { status, message };
334
+ if (remediation !== undefined)
335
+ check.remediation = remediation;
336
+ if (details !== undefined)
337
+ check.details = details;
338
+ if (nextCommand !== undefined)
339
+ check.nextCommand = nextCommand;
340
+ if (blockerKind !== undefined)
341
+ check.blockerKind = blockerKind;
342
+ if (code !== undefined)
343
+ check.code = code;
344
+ return check;
117
345
  }
118
- function unknown(message, remediation) {
119
- return remediation === undefined ? { status: "unknown", message } : { status: "unknown", message, remediation };
346
+ function isNodeError(error) {
347
+ return error instanceof Error && "code" in error;
120
348
  }
@@ -1,4 +1,5 @@
1
- import type { AttachedFile, AttachFilesArgs, AttachFilesData, CommandResult, DownloadedFile, DownloadLatestArgs, RuntimeEnv } from "../types.js";
1
+ import type { AttachedFile, AttachFilesArgs, AttachFilesData, CommandResult, DownloadedFile, DownloadLatestArgs, FilePreflightArgs, FilePreflightData, RuntimeEnv } from "../types.js";
2
2
  export declare function validateAttachPaths(paths: string[]): Promise<AttachedFile[]>;
3
+ export declare function preflightFiles(env: RuntimeEnv, args: FilePreflightArgs): Promise<CommandResult<FilePreflightData>>;
3
4
  export declare function attachFiles(env: RuntimeEnv, args: AttachFilesArgs): Promise<CommandResult<AttachFilesData>>;
4
5
  export declare function downloadLatestFile(env: RuntimeEnv, args: DownloadLatestArgs): Promise<CommandResult<DownloadedFile>>;