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.
- package/CHANGELOG.md +6 -0
- package/README.md +55 -0
- package/contracts/v1/fixtures/backend-capabilities.json +7 -0
- package/contracts/v1/fixtures/blocker-explanation-profiles.json +114 -0
- package/contracts/v1/fixtures/command-descriptors.json +192 -15
- package/contracts/v1/fixtures/doctor-bridge-upload.json +8 -0
- package/contracts/v1/fixtures/doctor-scenario-preflight.json +108 -0
- package/contracts/v1/fixtures/existing-tab-diagnostics-blocker.json +57 -0
- package/contracts/v1/fixtures/files-preflight-success.json +32 -0
- package/contracts/v1/fixtures/help-root.json +1 -1
- package/contracts/v1/fixtures/output-json-parse-success.json +17 -2
- package/contracts/v1/fixtures/project-sources-plan-add.json +71 -0
- package/contracts/v1/fixtures/read-latest-clipped.json +31 -0
- package/contracts/v1/fixtures/reports-create-redacted.json +15 -0
- package/contracts/v1/fixtures/responses-basic-success.json +11 -0
- package/contracts/v1/fixtures/run-basic-success.json +15 -0
- package/contracts/v1/fixtures/run-browser-bridge-blocker.json +2 -2
- package/contracts/v1/fixtures/run-timeout-partial.json +2 -2
- package/contracts/v1/fixtures/runner-budget-blocker.json +1 -1
- package/contracts/v1/fixtures/stream-blocked.ndjson +1 -1
- package/contracts/v1/fixtures/stream-submitted-completed.ndjson +1 -1
- package/contracts/v1/manifest.json +30 -0
- package/contracts/v1/parity-suite.json +72 -6
- package/contracts/v1/schemas/backend-request.schema.json +3 -0
- package/contracts/v1/schemas/command-result.schema.json +48 -0
- package/contracts/v1/schemas/response.schema.json +28 -0
- package/contracts/v1/schemas/run-result.schema.json +84 -0
- package/contracts/v1/surface-drift-policy.json +210 -0
- package/dist/codex-chatgpt-control-backend.mjs +5137 -488
- package/dist/codex-chatgpt-control.bundle.mjs +5001 -286
- package/dist/src/backend/client.d.ts +6 -1
- package/dist/src/backend/client.js +5 -0
- package/dist/src/backend/protocol.d.ts +1 -1
- package/dist/src/backend/protocol.js +7 -0
- package/dist/src/backend/session.js +14 -0
- package/dist/src/browser/attach.js +128 -20
- package/dist/src/browser/downloads.js +2 -1
- package/dist/src/browser/page-state.js +6 -3
- package/dist/src/client.d.ts +16 -1
- package/dist/src/client.js +86 -7
- package/dist/src/commands/artifacts.d.ts +5 -0
- package/dist/src/commands/artifacts.js +432 -0
- package/dist/src/commands/context.js +5 -2
- package/dist/src/commands/doctor.d.ts +10 -2
- package/dist/src/commands/doctor.js +242 -14
- package/dist/src/commands/files.d.ts +2 -1
- package/dist/src/commands/files.js +352 -18
- package/dist/src/commands/messages.d.ts +2 -1
- package/dist/src/commands/messages.js +232 -40
- package/dist/src/commands/modes.js +202 -20
- package/dist/src/commands/project-sources.d.ts +10 -0
- package/dist/src/commands/project-sources.js +643 -0
- package/dist/src/commands/registry.js +76 -14
- package/dist/src/commands/reports.d.ts +9 -0
- package/dist/src/commands/reports.js +83 -6
- package/dist/src/commands/response-actions.js +33 -0
- package/dist/src/commands/sequence.js +14 -0
- package/dist/src/commands/threads.js +2 -1
- package/dist/src/commands/timeouts.d.ts +2 -0
- package/dist/src/commands/timeouts.js +18 -0
- package/dist/src/diagnostics/blockers.d.ts +54 -0
- package/dist/src/diagnostics/blockers.js +299 -0
- package/dist/src/dom/artifacts.d.ts +7 -0
- package/dist/src/dom/artifacts.js +198 -0
- package/dist/src/dom/generation-state.d.ts +8 -0
- package/dist/src/dom/generation-state.js +103 -0
- package/dist/src/dom/locale/am.d.ts +28 -0
- package/dist/src/dom/locale/am.js +28 -0
- package/dist/src/dom/locale/ar.d.ts +28 -0
- package/dist/src/dom/locale/ar.js +28 -0
- package/dist/src/dom/locale/bg.d.ts +28 -0
- package/dist/src/dom/locale/bg.js +28 -0
- package/dist/src/dom/locale/bn.d.ts +28 -0
- package/dist/src/dom/locale/bn.js +28 -0
- package/dist/src/dom/locale/bs.d.ts +28 -0
- package/dist/src/dom/locale/bs.js +28 -0
- package/dist/src/dom/locale/ca.d.ts +28 -0
- package/dist/src/dom/locale/ca.js +28 -0
- package/dist/src/dom/locale/cs.d.ts +28 -0
- package/dist/src/dom/locale/cs.js +28 -0
- package/dist/src/dom/locale/da.d.ts +28 -0
- package/dist/src/dom/locale/da.js +28 -0
- package/dist/src/dom/locale/de.d.ts +30 -0
- package/dist/src/dom/locale/de.js +30 -0
- package/dist/src/dom/locale/el.d.ts +28 -0
- package/dist/src/dom/locale/el.js +28 -0
- package/dist/src/dom/locale/en.d.ts +50 -0
- package/dist/src/dom/locale/en.js +56 -0
- package/dist/src/dom/locale/es-419.d.ts +28 -0
- package/dist/src/dom/locale/es-419.js +28 -0
- package/dist/src/dom/locale/es-ES.d.ts +28 -0
- package/dist/src/dom/locale/es-ES.js +28 -0
- package/dist/src/dom/locale/et.d.ts +28 -0
- package/dist/src/dom/locale/et.js +28 -0
- package/dist/src/dom/locale/fa.d.ts +28 -0
- package/dist/src/dom/locale/fa.js +28 -0
- package/dist/src/dom/locale/fi.d.ts +28 -0
- package/dist/src/dom/locale/fi.js +28 -0
- package/dist/src/dom/locale/fr-CA.d.ts +29 -0
- package/dist/src/dom/locale/fr-CA.js +29 -0
- package/dist/src/dom/locale/fr-FR.d.ts +30 -0
- package/dist/src/dom/locale/fr-FR.js +30 -0
- package/dist/src/dom/locale/gu.d.ts +28 -0
- package/dist/src/dom/locale/gu.js +28 -0
- package/dist/src/dom/locale/hi.d.ts +28 -0
- package/dist/src/dom/locale/hi.js +28 -0
- package/dist/src/dom/locale/hr.d.ts +28 -0
- package/dist/src/dom/locale/hr.js +28 -0
- package/dist/src/dom/locale/hu.d.ts +28 -0
- package/dist/src/dom/locale/hu.js +28 -0
- package/dist/src/dom/locale/hy.d.ts +28 -0
- package/dist/src/dom/locale/hy.js +28 -0
- package/dist/src/dom/locale/id.d.ts +28 -0
- package/dist/src/dom/locale/id.js +28 -0
- package/dist/src/dom/locale/index.d.ts +79 -0
- package/dist/src/dom/locale/index.js +202 -0
- package/dist/src/dom/locale/is.d.ts +28 -0
- package/dist/src/dom/locale/is.js +28 -0
- package/dist/src/dom/locale/it.d.ts +27 -0
- package/dist/src/dom/locale/it.js +27 -0
- package/dist/src/dom/locale/ja.d.ts +27 -0
- package/dist/src/dom/locale/ja.js +27 -0
- package/dist/src/dom/locale/ka.d.ts +28 -0
- package/dist/src/dom/locale/ka.js +28 -0
- package/dist/src/dom/locale/kk.d.ts +28 -0
- package/dist/src/dom/locale/kk.js +28 -0
- package/dist/src/dom/locale/kn.d.ts +28 -0
- package/dist/src/dom/locale/kn.js +28 -0
- package/dist/src/dom/locale/ko.d.ts +28 -0
- package/dist/src/dom/locale/ko.js +28 -0
- package/dist/src/dom/locale/lt.d.ts +28 -0
- package/dist/src/dom/locale/lt.js +28 -0
- package/dist/src/dom/locale/lv.d.ts +28 -0
- package/dist/src/dom/locale/lv.js +28 -0
- package/dist/src/dom/locale/mk.d.ts +28 -0
- package/dist/src/dom/locale/mk.js +28 -0
- package/dist/src/dom/locale/ml.d.ts +28 -0
- package/dist/src/dom/locale/ml.js +28 -0
- package/dist/src/dom/locale/mn.d.ts +28 -0
- package/dist/src/dom/locale/mn.js +28 -0
- package/dist/src/dom/locale/mr.d.ts +28 -0
- package/dist/src/dom/locale/mr.js +28 -0
- package/dist/src/dom/locale/ms.d.ts +28 -0
- package/dist/src/dom/locale/ms.js +28 -0
- package/dist/src/dom/locale/my.d.ts +28 -0
- package/dist/src/dom/locale/my.js +28 -0
- package/dist/src/dom/locale/nb.d.ts +28 -0
- package/dist/src/dom/locale/nb.js +28 -0
- package/dist/src/dom/locale/nl.d.ts +28 -0
- package/dist/src/dom/locale/nl.js +28 -0
- package/dist/src/dom/locale/pa.d.ts +28 -0
- package/dist/src/dom/locale/pa.js +28 -0
- package/dist/src/dom/locale/pl.d.ts +28 -0
- package/dist/src/dom/locale/pl.js +28 -0
- package/dist/src/dom/locale/pt-BR.d.ts +28 -0
- package/dist/src/dom/locale/pt-BR.js +28 -0
- package/dist/src/dom/locale/pt-PT.d.ts +28 -0
- package/dist/src/dom/locale/pt-PT.js +28 -0
- package/dist/src/dom/locale/ro.d.ts +28 -0
- package/dist/src/dom/locale/ro.js +28 -0
- package/dist/src/dom/locale/ru.d.ts +28 -0
- package/dist/src/dom/locale/ru.js +28 -0
- package/dist/src/dom/locale/sk.d.ts +28 -0
- package/dist/src/dom/locale/sk.js +28 -0
- package/dist/src/dom/locale/sl.d.ts +28 -0
- package/dist/src/dom/locale/sl.js +28 -0
- package/dist/src/dom/locale/so.d.ts +28 -0
- package/dist/src/dom/locale/so.js +28 -0
- package/dist/src/dom/locale/sq.d.ts +28 -0
- package/dist/src/dom/locale/sq.js +28 -0
- package/dist/src/dom/locale/sr.d.ts +28 -0
- package/dist/src/dom/locale/sr.js +28 -0
- package/dist/src/dom/locale/sv.d.ts +28 -0
- package/dist/src/dom/locale/sv.js +28 -0
- package/dist/src/dom/locale/sw.d.ts +28 -0
- package/dist/src/dom/locale/sw.js +28 -0
- package/dist/src/dom/locale/ta.d.ts +28 -0
- package/dist/src/dom/locale/ta.js +28 -0
- package/dist/src/dom/locale/te.d.ts +28 -0
- package/dist/src/dom/locale/te.js +28 -0
- package/dist/src/dom/locale/th.d.ts +28 -0
- package/dist/src/dom/locale/th.js +28 -0
- package/dist/src/dom/locale/tl.d.ts +28 -0
- package/dist/src/dom/locale/tl.js +28 -0
- package/dist/src/dom/locale/tr.d.ts +28 -0
- package/dist/src/dom/locale/tr.js +28 -0
- package/dist/src/dom/locale/types.d.ts +45 -0
- package/dist/src/dom/locale/types.js +1 -0
- package/dist/src/dom/locale/uk.d.ts +28 -0
- package/dist/src/dom/locale/uk.js +28 -0
- package/dist/src/dom/locale/ur.d.ts +28 -0
- package/dist/src/dom/locale/ur.js +28 -0
- package/dist/src/dom/locale/vi.d.ts +28 -0
- package/dist/src/dom/locale/vi.js +28 -0
- package/dist/src/dom/locale/zh-HK.d.ts +28 -0
- package/dist/src/dom/locale/zh-HK.js +28 -0
- package/dist/src/dom/locale/zh-Hans.d.ts +28 -0
- package/dist/src/dom/locale/zh-Hans.js +28 -0
- package/dist/src/dom/locale/zh-TW.d.ts +29 -0
- package/dist/src/dom/locale/zh-TW.js +29 -0
- package/dist/src/dom/locale-labels.d.ts +26 -0
- package/dist/src/dom/locale-labels.js +26 -0
- package/dist/src/dom/menus.d.ts +6 -0
- package/dist/src/dom/menus.js +34 -8
- package/dist/src/dom/message-format.d.ts +2 -1
- package/dist/src/dom/message-format.js +67 -10
- package/dist/src/dom/messages.d.ts +2 -1
- package/dist/src/dom/messages.js +2 -4
- package/dist/src/dom/selectors.d.ts +1 -0
- package/dist/src/dom/selectors.js +42 -15
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +5 -0
- package/dist/src/platform/local-paths.d.ts +4 -0
- package/dist/src/platform/local-paths.js +20 -0
- package/dist/src/runner/interruptions.js +15 -4
- package/dist/src/runner/responses.js +18 -1
- package/dist/src/runner/result.js +15 -0
- package/dist/src/runner/resume.js +1 -0
- package/dist/src/runner/types.d.ts +3 -0
- package/dist/src/safety/risk.d.ts +7 -0
- package/dist/src/safety/risk.js +7 -0
- package/dist/src/safety/untrusted-output.d.ts +73 -0
- package/dist/src/safety/untrusted-output.js +199 -0
- package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +1 -0
- package/dist/src/scripts/apply-intelligence-locale-captures.js +184 -0
- package/dist/src/scripts/capture-intelligence-locales.d.ts +25 -0
- package/dist/src/scripts/capture-intelligence-locales.js +669 -0
- package/dist/src/scripts/check-surface-drift.d.ts +1 -0
- package/dist/src/scripts/check-surface-drift.js +11 -0
- package/dist/src/scripts/live-smoke/scenarios.js +84 -0
- package/dist/src/scripts/locale-capture/language-coverage.d.ts +10 -0
- package/dist/src/scripts/locale-capture/language-coverage.js +39 -0
- package/dist/src/testing/surface-drift.d.ts +66 -0
- package/dist/src/testing/surface-drift.js +363 -0
- package/dist/src/types.d.ts +201 -2
- package/package.json +2 -1
- package/references/backend-protocol.md +93 -1
- package/references/language-coverage.md +142 -0
- package/references/localization.md +247 -0
- package/references/python-parity.md +63 -1
- package/references/safety.md +2 -0
- 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
|
|
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
|
|
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
|
|
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
|
|
110
|
-
|
|
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
|
|
113
|
-
return
|
|
329
|
+
function unknown(message, remediation, details, nextCommand) {
|
|
330
|
+
return capability("unknown", message, remediation, details, nextCommand);
|
|
114
331
|
}
|
|
115
|
-
function
|
|
116
|
-
|
|
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
|
|
119
|
-
return
|
|
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>>;
|