codex-plus-patcher 0.7.1 → 0.8.0
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/package.json +1 -1
- package/src/cli.js +6 -0
- package/src/core/asar.js +6 -4
- package/src/core/dev-mode.js +69 -4
- package/src/core/plugin-audit.js +461 -13
- package/src/patches/26.623.31921-4452.js +45 -0
- package/src/patches/26.623.42026-4514.js +44 -0
- package/src/patches/index.js +4 -0
- package/src/patches/lib/common-patches.js +531 -2
- package/src/patches/lib/project-selector-shortcut-patch.js +130 -2
- package/src/runtime/host/projectSelector.js +7 -2
- package/src/runtime/plugins/projectColors.js +2 -0
- package/src/runtime/plugins/projectSelectorShortcut.js +27 -12
- package/src/runtime/plugins/userBubbleColors.js +4 -0
package/src/core/plugin-audit.js
CHANGED
|
@@ -40,6 +40,7 @@ function parseArgs(argv) {
|
|
|
40
40
|
includeNativeOpenProbes: false,
|
|
41
41
|
noProgress: false,
|
|
42
42
|
quiet: false,
|
|
43
|
+
devInstanceId: "audit",
|
|
43
44
|
};
|
|
44
45
|
for (let index = 0; index < argv.length; index += 1) {
|
|
45
46
|
const arg = argv[index];
|
|
@@ -53,6 +54,7 @@ function parseArgs(argv) {
|
|
|
53
54
|
else if (arg === "--source-home") args.sourceHome = path.resolve(expandPath(next()));
|
|
54
55
|
else if (arg === "--dev-home") args.devHome = path.resolve(expandPath(next()));
|
|
55
56
|
else if (arg === "--electron-user-data") args.electronUserDataPath = path.resolve(expandPath(next()));
|
|
57
|
+
else if (arg === "--dev-instance-id") args.devInstanceId = next();
|
|
56
58
|
else if (arg === "--remote-debugging-port" || arg === "--port") args.remoteDebuggingPort = Number(next());
|
|
57
59
|
else if (arg === "--no-apply") args.apply = false;
|
|
58
60
|
else if (arg === "--no-launch") args.launch = false;
|
|
@@ -168,6 +170,152 @@ async function findRendererTargetOnPort(port) {
|
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
172
|
|
|
173
|
+
async function waitForMermaidViewerTarget(port, beforeIds = new Set(), timeoutMs = 10000) {
|
|
174
|
+
const deadline = Date.now() + timeoutMs;
|
|
175
|
+
let lastError = null;
|
|
176
|
+
while (Date.now() < deadline) {
|
|
177
|
+
try {
|
|
178
|
+
const targets = await getJson(`http://127.0.0.1:${port}/json/list`);
|
|
179
|
+
const target = targets.find((entry) =>
|
|
180
|
+
!beforeIds.has(entry.id) &&
|
|
181
|
+
entry.url?.startsWith("file://") &&
|
|
182
|
+
entry.url.includes("codex-plus-mermaid-"));
|
|
183
|
+
if (target) return target;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
lastError = error;
|
|
186
|
+
}
|
|
187
|
+
await delay(250);
|
|
188
|
+
}
|
|
189
|
+
throw new Error(`Timed out waiting for Mermaid viewer target on port ${port}${lastError ? `: ${lastError.message}` : ""}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function verifyMermaidViewerRender(appCdp, port, { Session = CdpSession, timeoutMs = 15000 } = {}) {
|
|
193
|
+
const beforeTargets = await getJson(`http://127.0.0.1:${port}/json/list`).catch(() => []);
|
|
194
|
+
const beforeIds = new Set(beforeTargets.map((target) => target.id));
|
|
195
|
+
await appCdp.evaluate(`(() => {
|
|
196
|
+
const host = document.createElement("div");
|
|
197
|
+
host.setAttribute("data-markdown-copy", "code-block");
|
|
198
|
+
const pre = document.createElement("pre");
|
|
199
|
+
pre.className = "sr-only";
|
|
200
|
+
pre.textContent = "graph TD;A-->B";
|
|
201
|
+
const diagram = document.createElement("div");
|
|
202
|
+
diagram.setAttribute("data-codex-plus-mermaid-diagram", "");
|
|
203
|
+
host.append(pre, diagram);
|
|
204
|
+
document.body.appendChild(host);
|
|
205
|
+
window.CodexPlus.plugins.get("mermaidFullscreen").exports.openViewer(diagram);
|
|
206
|
+
setTimeout(() => host.remove(), 1000);
|
|
207
|
+
return true;
|
|
208
|
+
})()`);
|
|
209
|
+
const viewerTarget = await waitForMermaidViewerTarget(port, beforeIds, timeoutMs);
|
|
210
|
+
const viewer = new Session(viewerTarget.webSocketDebuggerUrl);
|
|
211
|
+
try {
|
|
212
|
+
await viewer.connect();
|
|
213
|
+
await viewer.send("Runtime.enable");
|
|
214
|
+
const deadline = Date.now() + timeoutMs;
|
|
215
|
+
let status = null;
|
|
216
|
+
while (Date.now() < deadline) {
|
|
217
|
+
status = await viewer.evaluate(`(() => {
|
|
218
|
+
const svg = document.querySelector("#stage svg");
|
|
219
|
+
const status = document.getElementById("render-status")?.textContent || "";
|
|
220
|
+
return {
|
|
221
|
+
hasSvg: Boolean(svg && svg.outerHTML.length > 1000),
|
|
222
|
+
status,
|
|
223
|
+
statusHidden: document.getElementById("render-status")?.hidden ?? null,
|
|
224
|
+
svgLength: svg?.outerHTML?.length || 0,
|
|
225
|
+
bodyText: document.body?.innerText?.slice(0, 500) || "",
|
|
226
|
+
};
|
|
227
|
+
})()`);
|
|
228
|
+
if (status.hasSvg && !/Mermaid render failed:/i.test(status.status)) {
|
|
229
|
+
return {
|
|
230
|
+
ok: true,
|
|
231
|
+
url: viewerTarget.url,
|
|
232
|
+
status: status.status,
|
|
233
|
+
svgLength: status.svgLength,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (/Mermaid render failed:/i.test(status.status) || /Mermaid render failed:/i.test(status.bodyText)) break;
|
|
237
|
+
await delay(250);
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
ok: false,
|
|
241
|
+
url: viewerTarget.url,
|
|
242
|
+
message: status?.status || "Mermaid viewer did not render an SVG",
|
|
243
|
+
status,
|
|
244
|
+
};
|
|
245
|
+
} finally {
|
|
246
|
+
try {
|
|
247
|
+
await viewer.send("Page.close");
|
|
248
|
+
} catch {
|
|
249
|
+
// The viewer may already be closed.
|
|
250
|
+
}
|
|
251
|
+
await viewer.close();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function verifyProjectSelectorShortcutKey(cdp, { wait = delay, timeoutMs = 5000 } = {}) {
|
|
256
|
+
const setup = await cdp.evaluate(`new Promise((resolve) => {
|
|
257
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, cancelable: true, key: "Escape" }));
|
|
258
|
+
const newChatButton = Array.from(document.querySelectorAll("button")).find((button) => {
|
|
259
|
+
const rect = button.getBoundingClientRect();
|
|
260
|
+
return rect.width > 0 && rect.height > 0 && (button.innerText || "").includes("New chat");
|
|
261
|
+
});
|
|
262
|
+
newChatButton?.click?.();
|
|
263
|
+
let attempts = 0;
|
|
264
|
+
const check = () => {
|
|
265
|
+
const triggerCount = document.querySelectorAll("[data-codex-plus-project-selector-trigger]").length;
|
|
266
|
+
if (triggerCount > 0 || attempts >= 30) {
|
|
267
|
+
resolve({ triggerCount, clickedNewChat: Boolean(newChatButton) });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
attempts += 1;
|
|
271
|
+
setTimeout(check, 100);
|
|
272
|
+
};
|
|
273
|
+
check();
|
|
274
|
+
})`);
|
|
275
|
+
if (!setup?.triggerCount) {
|
|
276
|
+
return { ok: false, ...setup, message: "Project selector shortcut trigger marker is missing from the main composer" };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await cdp.send("Input.dispatchKeyEvent", {
|
|
280
|
+
type: "keyDown",
|
|
281
|
+
key: ".",
|
|
282
|
+
code: "Period",
|
|
283
|
+
windowsVirtualKeyCode: 190,
|
|
284
|
+
nativeVirtualKeyCode: 47,
|
|
285
|
+
modifiers: 4,
|
|
286
|
+
});
|
|
287
|
+
await cdp.send("Input.dispatchKeyEvent", {
|
|
288
|
+
type: "keyUp",
|
|
289
|
+
key: ".",
|
|
290
|
+
code: "Period",
|
|
291
|
+
windowsVirtualKeyCode: 190,
|
|
292
|
+
nativeVirtualKeyCode: 47,
|
|
293
|
+
modifiers: 4,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const deadline = Date.now() + timeoutMs;
|
|
297
|
+
let status = null;
|
|
298
|
+
while (Date.now() < deadline) {
|
|
299
|
+
status = await cdp.evaluate(`(() => {
|
|
300
|
+
const searchInput = document.querySelector("input[placeholder='Search projects']");
|
|
301
|
+
const menuCount = document.querySelectorAll("[data-radix-menu-content], [data-radix-popper-content-wrapper], [role='menu']").length;
|
|
302
|
+
return {
|
|
303
|
+
triggerCount: document.querySelectorAll("[data-codex-plus-project-selector-trigger]").length,
|
|
304
|
+
menuCount,
|
|
305
|
+
opened: Boolean(searchInput || menuCount > 0),
|
|
306
|
+
activePlaceholder: document.activeElement?.getAttribute?.("placeholder") ?? "",
|
|
307
|
+
};
|
|
308
|
+
})()`);
|
|
309
|
+
if (status.opened) {
|
|
310
|
+
await cdp.send("Input.dispatchKeyEvent", { type: "keyDown", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27, nativeVirtualKeyCode: 53 });
|
|
311
|
+
await cdp.send("Input.dispatchKeyEvent", { type: "keyUp", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27, nativeVirtualKeyCode: 53 });
|
|
312
|
+
return { ok: true, ...setup, ...status };
|
|
313
|
+
}
|
|
314
|
+
await wait(100);
|
|
315
|
+
}
|
|
316
|
+
return { ok: false, ...setup, ...status, message: `Cmd+. did not open the project selector: ${JSON.stringify(status)}` };
|
|
317
|
+
}
|
|
318
|
+
|
|
171
319
|
function listRunningAuditApps({
|
|
172
320
|
targetApp = DEFAULT_TARGET,
|
|
173
321
|
electronUserDataPath = DEFAULT_ELECTRON_USER_DATA,
|
|
@@ -361,16 +509,21 @@ async function waitForAppShellMounted(cdp, timeoutMs = 90000) {
|
|
|
361
509
|
const root = document.getElementById("root");
|
|
362
510
|
const bodyText = document.body?.innerText?.trim() ?? "";
|
|
363
511
|
const interactiveCount = document.querySelectorAll("button,a,nav,[role=navigation]").length;
|
|
512
|
+
const hasErrorBoundary = /^Oops, an error has occurred\\b/.test(bodyText);
|
|
364
513
|
return {
|
|
365
514
|
readyState: document.readyState,
|
|
366
515
|
hasRoot: Boolean(root),
|
|
367
516
|
hasStartupLoader: Boolean(document.querySelector("#root .startup-loader")),
|
|
517
|
+
hasErrorBoundary,
|
|
368
518
|
bodyTextLength: bodyText.length,
|
|
369
519
|
elementCount: document.querySelectorAll("*").length,
|
|
370
520
|
interactiveCount,
|
|
371
521
|
sampleText: bodyText.slice(0, 120),
|
|
372
522
|
};
|
|
373
523
|
})()`);
|
|
524
|
+
if (lastStatus.hasErrorBoundary) {
|
|
525
|
+
throw new Error(`Codex app shell rendered error boundary: ${JSON.stringify(lastStatus)}`);
|
|
526
|
+
}
|
|
374
527
|
if (
|
|
375
528
|
lastStatus.readyState === "complete" &&
|
|
376
529
|
lastStatus.hasRoot &&
|
|
@@ -407,10 +560,12 @@ function formatAuditJson(result) {
|
|
|
407
560
|
}
|
|
408
561
|
|
|
409
562
|
function formatAuditResult(result, { quiet = false } = {}) {
|
|
563
|
+
const expectedWarnings = result.expectedWarnings || [];
|
|
410
564
|
if (quiet) {
|
|
411
|
-
return result.
|
|
412
|
-
|
|
413
|
-
|
|
565
|
+
if (!result.ok) return `Plugin audit failed: ${result.failures.length} failures\n`;
|
|
566
|
+
return expectedWarnings.length > 0
|
|
567
|
+
? "All plugin probes passed with expected warnings.\n"
|
|
568
|
+
: "All plugin probes passed.\n";
|
|
414
569
|
}
|
|
415
570
|
|
|
416
571
|
if (!result.ok) {
|
|
@@ -440,6 +595,13 @@ function formatAuditResult(result, { quiet = false } = {}) {
|
|
|
440
595
|
}
|
|
441
596
|
lines.push("");
|
|
442
597
|
}
|
|
598
|
+
if (expectedWarnings.length > 0) {
|
|
599
|
+
lines.push("Expected warnings:");
|
|
600
|
+
for (const warning of expectedWarnings) {
|
|
601
|
+
lines.push(` ${warning.plugin || "audit"} ${warning.code || "warning"}: ${warning.message || "expected warning"}`);
|
|
602
|
+
}
|
|
603
|
+
lines.push("");
|
|
604
|
+
}
|
|
443
605
|
lines.push("Re-run with --json for full probe details.");
|
|
444
606
|
return `${lines.join("\n").replace(/\n{3,}/g, "\n\n")}\n`;
|
|
445
607
|
}
|
|
@@ -466,11 +628,18 @@ function formatAuditResult(result, { quiet = false } = {}) {
|
|
|
466
628
|
`Runtime ready: ${runtime.registered ?? result.registeredPlugins?.length ?? 0} registered, ${runtime.started ?? result.startedPlugins?.length ?? 0} started`,
|
|
467
629
|
`App shell: ${appShell.hasStartupLoader === false ? "mounted" : "unknown"}`,
|
|
468
630
|
`Probed ${probeCount} plugins`,
|
|
631
|
+
`Warnings: ${expectedWarnings.length} expected`,
|
|
469
632
|
`Native open probes: ${result.nativeOpenProbes?.included ? "included" : "skipped"}`,
|
|
470
633
|
`Cleanup: ${cleanupText}`,
|
|
471
634
|
"",
|
|
472
635
|
"All plugin probes passed.",
|
|
473
636
|
);
|
|
637
|
+
if (expectedWarnings.length > 0) {
|
|
638
|
+
lines.push("", "Expected warnings:");
|
|
639
|
+
for (const warning of expectedWarnings) {
|
|
640
|
+
lines.push(`${warning.plugin || "audit"} ${warning.code || "warning"}: ${warning.message || "expected warning"}`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
474
643
|
return `${lines.join("\n")}\n`;
|
|
475
644
|
}
|
|
476
645
|
|
|
@@ -661,12 +830,16 @@ function pluginAuditExpression({ includeNativeOpenProbes = false } = {}) {
|
|
|
661
830
|
];
|
|
662
831
|
const pluginResults = {};
|
|
663
832
|
const failures = [];
|
|
833
|
+
const expectedWarnings = [];
|
|
664
834
|
const add = (id, ok, details = {}) => {
|
|
665
835
|
pluginResults[id] = { ok, ...details };
|
|
666
836
|
if (!ok) failures.push({ plugin: id, message: details.message || "probe failed", details });
|
|
667
837
|
};
|
|
668
838
|
const fail = (id, error, details = {}) => add(id, false, { message: error?.message || String(error), ...details });
|
|
669
839
|
const pass = (id, details = {}) => add(id, true, details);
|
|
840
|
+
const warn = (id, code, message, details = {}) => {
|
|
841
|
+
expectedWarnings.push({ plugin: id, code, message, details });
|
|
842
|
+
};
|
|
670
843
|
const pluginIds = () => {
|
|
671
844
|
if (typeof window.CodexPlus?.plugins?.list !== "function") {
|
|
672
845
|
throw new Error("CodexPlus.plugins.list is not available");
|
|
@@ -721,6 +894,120 @@ function pluginAuditExpression({ includeNativeOpenProbes = false } = {}) {
|
|
|
721
894
|
evidence,
|
|
722
895
|
};
|
|
723
896
|
};
|
|
897
|
+
const waitForProjectThreadRows = async (timeoutMs = 45000) => {
|
|
898
|
+
const startedAt = Date.now();
|
|
899
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
900
|
+
const rows = document.querySelectorAll("[data-app-action-sidebar-project-list-id] [data-app-action-sidebar-thread-row]");
|
|
901
|
+
if (rows.length > 0) return rows.length;
|
|
902
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
903
|
+
}
|
|
904
|
+
return 0;
|
|
905
|
+
};
|
|
906
|
+
const isTransparentColor = (value) => value === "rgba(0, 0, 0, 0)" || value === "transparent";
|
|
907
|
+
const waitForMountedProjectComposer = async (expectedAccent, timeoutMs = 20000) => {
|
|
908
|
+
const startedAt = Date.now();
|
|
909
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
910
|
+
const editor = document.querySelector("[data-codex-composer]");
|
|
911
|
+
const surface = editor?.closest("[data-codex-plus-user-entry]") || editor?.closest(".composer-surface-chrome");
|
|
912
|
+
if (surface) {
|
|
913
|
+
const computed = getComputedStyle(surface);
|
|
914
|
+
const surfaceAccent = computed.getPropertyValue("--codex-plus-project-accent").trim();
|
|
915
|
+
if (
|
|
916
|
+
surface.hasAttribute("data-codex-plus-user-entry") &&
|
|
917
|
+
surface.hasAttribute("data-codex-plus-project-color") &&
|
|
918
|
+
surfaceAccent === expectedAccent &&
|
|
919
|
+
computed.boxShadow !== "none"
|
|
920
|
+
) {
|
|
921
|
+
return {
|
|
922
|
+
marked: true,
|
|
923
|
+
projectMarked: true,
|
|
924
|
+
accent: surfaceAccent,
|
|
925
|
+
boxShadow: computed.boxShadow,
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
930
|
+
}
|
|
931
|
+
const editor = document.querySelector("[data-codex-composer]");
|
|
932
|
+
const surface = editor?.closest("[data-codex-plus-user-entry]") || editor?.closest(".composer-surface-chrome");
|
|
933
|
+
const computed = surface ? getComputedStyle(surface) : null;
|
|
934
|
+
return {
|
|
935
|
+
marked: surface?.hasAttribute("data-codex-plus-user-entry") || false,
|
|
936
|
+
projectMarked: surface?.hasAttribute("data-codex-plus-project-color") || false,
|
|
937
|
+
accent: computed?.getPropertyValue("--codex-plus-project-accent").trim() || "",
|
|
938
|
+
boxShadow: computed?.boxShadow || "",
|
|
939
|
+
};
|
|
940
|
+
};
|
|
941
|
+
const composerPermissionPickerStatus = () => {
|
|
942
|
+
const editor = document.querySelector("[data-codex-composer]");
|
|
943
|
+
const labels = ["Full access", "Ask for approval", "Approve for me", "Custom"];
|
|
944
|
+
const normalize = (value) => String(value || "").replace(/\s+/g, " ").trim();
|
|
945
|
+
const rgb = (value) => {
|
|
946
|
+
const match = String(value || "").match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
947
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
948
|
+
};
|
|
949
|
+
const luminance = (color) => {
|
|
950
|
+
if (!color) return null;
|
|
951
|
+
const channel = (value) => {
|
|
952
|
+
const normalized = value / 255;
|
|
953
|
+
return normalized <= 0.03928 ? normalized / 12.92 : Math.pow((normalized + 0.055) / 1.055, 2.4);
|
|
954
|
+
};
|
|
955
|
+
return 0.2126 * channel(color[0]) + 0.7152 * channel(color[1]) + 0.0722 * channel(color[2]);
|
|
956
|
+
};
|
|
957
|
+
const contrast = (foreground, background) => {
|
|
958
|
+
const fg = luminance(rgb(foreground));
|
|
959
|
+
const bg = luminance(rgb(background));
|
|
960
|
+
if (fg == null || bg == null) return null;
|
|
961
|
+
const lighter = Math.max(fg, bg);
|
|
962
|
+
const darker = Math.min(fg, bg);
|
|
963
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
964
|
+
};
|
|
965
|
+
const isTransparent = (value) => {
|
|
966
|
+
const text = String(value || "").trim();
|
|
967
|
+
return text === "transparent" || text === "rgba(0, 0, 0, 0)" || /rgba\([^)]*,\s*0\)$/.test(text);
|
|
968
|
+
};
|
|
969
|
+
const trigger = Array.from(document.querySelectorAll("button")).find((button) => {
|
|
970
|
+
const text = normalize(button.textContent);
|
|
971
|
+
return labels.some((label) => text === label || text.startsWith(`${label} `));
|
|
972
|
+
});
|
|
973
|
+
const triggerStyle = trigger ? getComputedStyle(trigger) : null;
|
|
974
|
+
const surface = editor?.closest("[data-codex-plus-user-entry]");
|
|
975
|
+
const surfaceStyle = surface ? getComputedStyle(surface) : null;
|
|
976
|
+
let labelStyle = null;
|
|
977
|
+
if (trigger) {
|
|
978
|
+
const walker = document.createTreeWalker(trigger, NodeFilter.SHOW_TEXT);
|
|
979
|
+
while (walker.nextNode()) {
|
|
980
|
+
const node = walker.currentNode;
|
|
981
|
+
if (normalize(node.nodeValue) !== "") {
|
|
982
|
+
labelStyle = getComputedStyle(node.parentElement);
|
|
983
|
+
break;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
const triggerColor = triggerStyle?.color || null;
|
|
988
|
+
const labelColor = labelStyle?.color || triggerColor;
|
|
989
|
+
const labelTextFillColor = labelStyle?.webkitTextFillColor || triggerStyle?.webkitTextFillColor || null;
|
|
990
|
+
const effectiveLabelColor = labelTextFillColor && !isTransparent(labelTextFillColor) ? labelTextFillColor : labelColor;
|
|
991
|
+
const surfaceBackground = surfaceStyle?.backgroundColor || null;
|
|
992
|
+
return {
|
|
993
|
+
editorMounted: Boolean(editor),
|
|
994
|
+
editorEditable: editor?.getAttribute("contenteditable") === "true",
|
|
995
|
+
editorText: normalize(editor?.textContent),
|
|
996
|
+
triggerMounted: Boolean(trigger),
|
|
997
|
+
triggerText: normalize(trigger?.textContent),
|
|
998
|
+
triggerDisabled: Boolean(trigger?.disabled),
|
|
999
|
+
triggerAriaDisabled: trigger?.getAttribute("aria-disabled") || null,
|
|
1000
|
+
triggerState: trigger?.getAttribute("data-state") || null,
|
|
1001
|
+
triggerOpacity: triggerStyle?.opacity || null,
|
|
1002
|
+
triggerColor,
|
|
1003
|
+
labelColor,
|
|
1004
|
+
labelTextFillColor,
|
|
1005
|
+
surfaceBackground,
|
|
1006
|
+
triggerContrast: contrast(effectiveLabelColor, surfaceBackground),
|
|
1007
|
+
labelTextFillTransparent: isTransparent(labelTextFillColor),
|
|
1008
|
+
triggerClassName: String(trigger?.className || ""),
|
|
1009
|
+
};
|
|
1010
|
+
};
|
|
724
1011
|
const jsx = (type, props, key) => ({ type, props: props || {}, key });
|
|
725
1012
|
const jsxs = jsx;
|
|
726
1013
|
const reviewDeps = {
|
|
@@ -835,14 +1122,63 @@ function pluginAuditExpression({ includeNativeOpenProbes = false } = {}) {
|
|
|
835
1122
|
props?.style?.["--codex-plus-project-accent"] === accent);
|
|
836
1123
|
const liveRows = Array.from(document.querySelectorAll("[data-codex-plus-project-color]"));
|
|
837
1124
|
const liveAccents = liveRows.map((row) => getComputedStyle(row).getPropertyValue("--codex-plus-project-accent").trim()).filter(Boolean);
|
|
1125
|
+
const projectThreadRowCount = await waitForProjectThreadRows();
|
|
1126
|
+
let selectedProjectAccent = "";
|
|
1127
|
+
let mountedComposer = null;
|
|
1128
|
+
const unstyledProjectThreadLists = Array.from(document.querySelectorAll("[data-app-action-sidebar-project-list-id]"))
|
|
1129
|
+
.map((list) => {
|
|
1130
|
+
const projectId = list.getAttribute("data-app-action-sidebar-project-list-id") || "";
|
|
1131
|
+
const projectRow = document.querySelector(`[data-app-action-sidebar-project-row][data-app-action-sidebar-project-id="${CSS.escape(projectId)}"]`);
|
|
1132
|
+
const threadRows = Array.from(list.querySelectorAll("[data-app-action-sidebar-thread-row]"));
|
|
1133
|
+
if (!projectRow || threadRows.length === 0) return null;
|
|
1134
|
+
const projectAccent = getComputedStyle(projectRow).getPropertyValue("--codex-plus-project-accent").trim();
|
|
1135
|
+
const listComputed = getComputedStyle(list);
|
|
1136
|
+
const listAccent = listComputed.getPropertyValue("--codex-plus-project-accent").trim();
|
|
1137
|
+
const listBackground = listComputed.backgroundColor;
|
|
1138
|
+
if (!selectedProjectAccent) {
|
|
1139
|
+
selectedProjectAccent = projectAccent;
|
|
1140
|
+
threadRows[0].click();
|
|
1141
|
+
}
|
|
1142
|
+
const unstyledRows = threadRows.filter((row) => {
|
|
1143
|
+
const computed = getComputedStyle(row);
|
|
1144
|
+
const rowAccent = computed.getPropertyValue("--codex-plus-project-accent").trim();
|
|
1145
|
+
return rowAccent !== projectAccent || isTransparentColor(computed.backgroundColor);
|
|
1146
|
+
});
|
|
1147
|
+
return list.hasAttribute("data-codex-plus-project-sidebar-color") &&
|
|
1148
|
+
listAccent === projectAccent &&
|
|
1149
|
+
!isTransparentColor(listBackground) &&
|
|
1150
|
+
unstyledRows.length === 0
|
|
1151
|
+
? null
|
|
1152
|
+
: {
|
|
1153
|
+
projectId,
|
|
1154
|
+
projectLabel: projectRow.getAttribute("data-app-action-sidebar-project-label") || projectRow.innerText.trim(),
|
|
1155
|
+
projectAccent,
|
|
1156
|
+
listAccent,
|
|
1157
|
+
listBackground,
|
|
1158
|
+
threadRows: threadRows.length,
|
|
1159
|
+
unstyledRows: unstyledRows.length,
|
|
1160
|
+
listMarked: list.hasAttribute("data-codex-plus-project-sidebar-color"),
|
|
1161
|
+
};
|
|
1162
|
+
})
|
|
1163
|
+
.filter(Boolean);
|
|
1164
|
+
if (selectedProjectAccent) mountedComposer = await waitForMountedProjectComposer(selectedProjectAccent);
|
|
838
1165
|
if (!accent) throw new Error("Project accent was not computed");
|
|
839
1166
|
if (!matchingProps) throw new Error("Project, thread, bubble, and composer props do not share an accent");
|
|
1167
|
+
if (projectThreadRowCount === 0) throw new Error("No visible project child thread rows appeared; project sidebar styling was not proven");
|
|
1168
|
+
if (unstyledProjectThreadLists.length > 0) {
|
|
1169
|
+
throw new Error(`Project sidebar child rows or list containers are not styled like their project rows: ${JSON.stringify(unstyledProjectThreadLists.slice(0, 4))}`);
|
|
1170
|
+
}
|
|
1171
|
+
if (!mountedComposer?.marked || !mountedComposer?.projectMarked || mountedComposer?.accent !== selectedProjectAccent) {
|
|
1172
|
+
throw new Error(`Mounted composer does not carry the selected project accent: ${JSON.stringify(mountedComposer)}`);
|
|
1173
|
+
}
|
|
840
1174
|
pass("projectColors", {
|
|
841
1175
|
...details,
|
|
842
1176
|
accent,
|
|
843
1177
|
matchingProps,
|
|
844
1178
|
liveRows: liveRows.length,
|
|
845
1179
|
liveAccents: Array.from(new Set(liveAccents)).slice(0, 8),
|
|
1180
|
+
styledProjectThreadLists: projectThreadRowCount,
|
|
1181
|
+
mountedComposer,
|
|
846
1182
|
});
|
|
847
1183
|
} catch (error) {
|
|
848
1184
|
fail("projectColors", error);
|
|
@@ -860,6 +1196,33 @@ function pluginAuditExpression({ includeNativeOpenProbes = false } = {}) {
|
|
|
860
1196
|
fail("projectPathHeader", error);
|
|
861
1197
|
}
|
|
862
1198
|
|
|
1199
|
+
try {
|
|
1200
|
+
const status = composerPermissionPickerStatus();
|
|
1201
|
+
if (status.editorMounted && status.editorEditable && status.triggerMounted) {
|
|
1202
|
+
const lowOpacity = Number(status.triggerOpacity) < 0.5;
|
|
1203
|
+
const lowContrast = status.triggerContrast != null && status.triggerContrast < 4.5;
|
|
1204
|
+
if (lowOpacity || lowContrast || status.labelTextFillTransparent) {
|
|
1205
|
+
throw new Error(`Composer permissions picker text is unreadable: ${JSON.stringify(status)}`);
|
|
1206
|
+
}
|
|
1207
|
+
const ariaDisabled = status.triggerAriaDisabled === "true";
|
|
1208
|
+
const visuallyDisabled = /\bopacity-40\b/.test(status.triggerClassName);
|
|
1209
|
+
if (status.triggerDisabled || ariaDisabled || visuallyDisabled) {
|
|
1210
|
+
warn(
|
|
1211
|
+
"audit",
|
|
1212
|
+
"composer-permission-picker-disabled",
|
|
1213
|
+
"Composer permissions picker is disabled while the composer is editable",
|
|
1214
|
+
status,
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (!status.editorMounted || !status.triggerMounted) {
|
|
1219
|
+
throw new Error(`Composer permissions picker was not found: ${JSON.stringify(status)}`);
|
|
1220
|
+
}
|
|
1221
|
+
pass("audit", { composerPermissionPicker: status });
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
fail("audit", error);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
863
1226
|
try {
|
|
864
1227
|
const details = checkCommon("sidebarNameBlur");
|
|
865
1228
|
const metadata = window.CodexPlus.ui.commands.commandMetadata().some((command) => command.id === "codexPlusToggleSidebarNameBlur");
|
|
@@ -938,7 +1301,34 @@ function pluginAuditExpression({ includeNativeOpenProbes = false } = {}) {
|
|
|
938
1301
|
if (ranked[0] !== "hassio-dev") throw new Error(`Fuzzy ranking returned ${ranked.join(", ")}`);
|
|
939
1302
|
if (highlightCount === 0) throw new Error("Fuzzy match highlight did not render");
|
|
940
1303
|
if (selected[0] !== "hassio-dev" || events.length !== 2) throw new Error("Enter-to-first-result adapter did not select first ranked result");
|
|
941
|
-
|
|
1304
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, cancelable: true, key: "Escape" }));
|
|
1305
|
+
const newChatButton = Array.from(document.querySelectorAll("button")).find((button) => {
|
|
1306
|
+
const rect = button.getBoundingClientRect();
|
|
1307
|
+
return rect.width > 0 && rect.height > 0 && (button.innerText || "").includes("New chat");
|
|
1308
|
+
});
|
|
1309
|
+
newChatButton?.click?.();
|
|
1310
|
+
let triggerCount = 0;
|
|
1311
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
1312
|
+
triggerCount = document.querySelectorAll("[data-codex-plus-project-selector-trigger]").length;
|
|
1313
|
+
if (triggerCount > 0) break;
|
|
1314
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1315
|
+
}
|
|
1316
|
+
if (triggerCount === 0) throw new Error("Project selector shortcut trigger marker is missing from the main composer");
|
|
1317
|
+
const syntheticShortcut = await new Promise((resolve) => {
|
|
1318
|
+
const event = new KeyboardEvent("keydown", { bubbles: true, cancelable: true, key: ".", metaKey: true });
|
|
1319
|
+
document.dispatchEvent(event);
|
|
1320
|
+
setTimeout(() => {
|
|
1321
|
+
const searchInput = document.querySelector("input[placeholder='Search projects']");
|
|
1322
|
+
const menu = document.querySelector("[data-radix-menu-content], [data-radix-popper-content-wrapper], [role='menu']");
|
|
1323
|
+
resolve({
|
|
1324
|
+
defaultPrevented: event.defaultPrevented,
|
|
1325
|
+
opened: Boolean(searchInput || menu),
|
|
1326
|
+
activePlaceholder: document.activeElement?.getAttribute?.("placeholder") ?? "",
|
|
1327
|
+
});
|
|
1328
|
+
}, 400);
|
|
1329
|
+
});
|
|
1330
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, cancelable: true, key: "Escape" }));
|
|
1331
|
+
pass("projectSelectorShortcut", { ...details, ranked, highlightCount, selected, triggerCount, syntheticShortcut });
|
|
942
1332
|
} catch (error) {
|
|
943
1333
|
fail("projectSelectorShortcut", error);
|
|
944
1334
|
}
|
|
@@ -991,6 +1381,7 @@ function pluginAuditExpression({ includeNativeOpenProbes = false } = {}) {
|
|
|
991
1381
|
ok: failures.length === 0,
|
|
992
1382
|
failures,
|
|
993
1383
|
pluginResults,
|
|
1384
|
+
expectedWarnings,
|
|
994
1385
|
registeredPlugins: typeof window.CodexPlus?.plugins?.list === "function" ? pluginIds() : null,
|
|
995
1386
|
startedPlugins: started(),
|
|
996
1387
|
};
|
|
@@ -1009,6 +1400,8 @@ async function runAudit(args, {
|
|
|
1009
1400
|
const Session = operations.CdpSession || CdpSession;
|
|
1010
1401
|
const waitRuntime = operations.waitForLiveRuntime || waitForLiveRuntime;
|
|
1011
1402
|
const waitAppShell = operations.waitForAppShellMounted || waitForAppShellMounted;
|
|
1403
|
+
const verifyMermaidViewer = operations.verifyMermaidViewerRender || verifyMermaidViewerRender;
|
|
1404
|
+
const verifyProjectSelectorShortcut = operations.verifyProjectSelectorShortcutKey || verifyProjectSelectorShortcutKey;
|
|
1012
1405
|
const cleanupApp = operations.cleanupLaunchedAuditApp || cleanupLaunchedAuditApp;
|
|
1013
1406
|
const checkStability = operations.checkKeepOpenAppStability || checkKeepOpenAppStability;
|
|
1014
1407
|
const preflightAudit = operations.auditPreflight || auditPreflight;
|
|
@@ -1041,15 +1434,17 @@ async function runAudit(args, {
|
|
|
1041
1434
|
}),
|
|
1042
1435
|
);
|
|
1043
1436
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1437
|
+
if (args.apply || args.launch) {
|
|
1438
|
+
syncResult = await withAuditProgress(
|
|
1439
|
+
progress,
|
|
1440
|
+
"Syncing dev home",
|
|
1441
|
+
"Synced dev home",
|
|
1442
|
+
() => syncHome({
|
|
1443
|
+
sourceHome: args.sourceHome,
|
|
1444
|
+
devHome: args.devHome,
|
|
1445
|
+
}),
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1053
1448
|
if (preflight.launch) {
|
|
1054
1449
|
launchResult = await withAuditProgress(
|
|
1055
1450
|
progress,
|
|
@@ -1060,6 +1455,7 @@ async function runAudit(args, {
|
|
|
1060
1455
|
devHome: args.devHome,
|
|
1061
1456
|
electronUserDataPath: args.electronUserDataPath,
|
|
1062
1457
|
remoteDebuggingPort: port,
|
|
1458
|
+
devInstanceId: args.devInstanceId,
|
|
1063
1459
|
}),
|
|
1064
1460
|
);
|
|
1065
1461
|
}
|
|
@@ -1090,9 +1486,53 @@ async function runAudit(args, {
|
|
|
1090
1486
|
"Probed plugins",
|
|
1091
1487
|
() => cdp.evaluate(pluginAuditExpression({ includeNativeOpenProbes: args.includeNativeOpenProbes })),
|
|
1092
1488
|
);
|
|
1489
|
+
if (live.pluginResults?.projectSelectorShortcut?.ok) {
|
|
1490
|
+
const shortcut = await withAuditProgress(
|
|
1491
|
+
progress,
|
|
1492
|
+
"Verifying project selector shortcut",
|
|
1493
|
+
"Project selector shortcut opened",
|
|
1494
|
+
() => verifyProjectSelectorShortcut(cdp),
|
|
1495
|
+
);
|
|
1496
|
+
live.pluginResults.projectSelectorShortcut.shortcut = shortcut;
|
|
1497
|
+
if (!shortcut.ok) {
|
|
1498
|
+
live.ok = false;
|
|
1499
|
+
live.pluginResults.projectSelectorShortcut.ok = false;
|
|
1500
|
+
live.failures.push({
|
|
1501
|
+
plugin: "projectSelectorShortcut",
|
|
1502
|
+
message: shortcut.message || "Cmd+. did not open the project selector",
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
const shouldProbeMermaidViewer = live.pluginResults?.mermaidFullscreen?.ok;
|
|
1507
|
+
const mermaidViewerRender = shouldProbeMermaidViewer
|
|
1508
|
+
? await withAuditProgress(
|
|
1509
|
+
progress,
|
|
1510
|
+
"Verifying Mermaid viewer render",
|
|
1511
|
+
"Mermaid viewer rendered",
|
|
1512
|
+
() => verifyMermaidViewer(cdp, port, { Session }),
|
|
1513
|
+
)
|
|
1514
|
+
: null;
|
|
1515
|
+
if (mermaidViewerRender != null) {
|
|
1516
|
+
live.pluginResults.mermaidFullscreen.viewerRenderProbe = mermaidViewerRender;
|
|
1517
|
+
if (!mermaidViewerRender.ok) {
|
|
1518
|
+
live.ok = false;
|
|
1519
|
+
live.pluginResults.mermaidFullscreen.ok = false;
|
|
1520
|
+
live.failures.push({
|
|
1521
|
+
plugin: "mermaidFullscreen",
|
|
1522
|
+
message: `Mermaid viewer render failed: ${mermaidViewerRender.message || "no SVG rendered"}`,
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
appShellStatus = await withAuditProgress(
|
|
1527
|
+
progress,
|
|
1528
|
+
"Verifying Codex app shell after probes",
|
|
1529
|
+
"App shell still healthy",
|
|
1530
|
+
() => waitAppShell(cdp),
|
|
1531
|
+
);
|
|
1093
1532
|
result = {
|
|
1094
1533
|
ok: live.ok,
|
|
1095
1534
|
failures: live.failures,
|
|
1535
|
+
expectedWarnings: live.expectedWarnings || [],
|
|
1096
1536
|
pluginResults: live.pluginResults,
|
|
1097
1537
|
target: {
|
|
1098
1538
|
app: path.resolve(args.target),
|
|
@@ -1115,6 +1555,8 @@ async function runAudit(args, {
|
|
|
1115
1555
|
command: launchResult.command,
|
|
1116
1556
|
args: launchResult.args,
|
|
1117
1557
|
pid: launchResult.pid,
|
|
1558
|
+
devBundle: launchResult.devBundle,
|
|
1559
|
+
instanceIdentity: launchResult.instanceIdentity,
|
|
1118
1560
|
},
|
|
1119
1561
|
registeredPlugins: live.registeredPlugins,
|
|
1120
1562
|
startedPlugins: live.startedPlugins,
|
|
@@ -1124,6 +1566,7 @@ async function runAudit(args, {
|
|
|
1124
1566
|
nativeOpenProbes: {
|
|
1125
1567
|
included: Boolean(args.includeNativeOpenProbes),
|
|
1126
1568
|
},
|
|
1569
|
+
mermaidViewerRender,
|
|
1127
1570
|
preflight,
|
|
1128
1571
|
};
|
|
1129
1572
|
return result;
|
|
@@ -1135,6 +1578,7 @@ async function runAudit(args, {
|
|
|
1135
1578
|
message: error.message,
|
|
1136
1579
|
details: error.details,
|
|
1137
1580
|
}],
|
|
1581
|
+
expectedWarnings: [],
|
|
1138
1582
|
pluginResults: {},
|
|
1139
1583
|
target: {
|
|
1140
1584
|
app: path.resolve(args.target),
|
|
@@ -1157,6 +1601,8 @@ async function runAudit(args, {
|
|
|
1157
1601
|
command: launchResult.command,
|
|
1158
1602
|
args: launchResult.args,
|
|
1159
1603
|
pid: launchResult.pid,
|
|
1604
|
+
devBundle: launchResult.devBundle,
|
|
1605
|
+
instanceIdentity: launchResult.instanceIdentity,
|
|
1160
1606
|
},
|
|
1161
1607
|
registeredPlugins: null,
|
|
1162
1608
|
startedPlugins: null,
|
|
@@ -1263,5 +1709,7 @@ module.exports = {
|
|
|
1263
1709
|
shouldShowAuditProgress,
|
|
1264
1710
|
waitForAppShellMounted,
|
|
1265
1711
|
waitForLiveRuntime,
|
|
1712
|
+
verifyMermaidViewerRender,
|
|
1713
|
+
verifyProjectSelectorShortcutKey,
|
|
1266
1714
|
waitForRendererTarget,
|
|
1267
1715
|
};
|