minecodex 0.1.14 → 0.1.16
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/README.md +2 -0
- package/features/model-slider/codex-feature.json +1 -0
- package/package.json +1 -1
- package/packages/cli/src/chatgpt-launch-coordinator.mjs +77 -0
- package/packages/cli/src/control-server.mjs +2 -0
- package/packages/cli/src/platform.mjs +220 -29
- package/packages/cli/src/runtime-manager.mjs +178 -100
- package/packages/runtime-host/src/codex-runtime.mjs +180 -109
- package/packages/runtime-host/src/main.mjs +1 -3
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
2
|
import { mkdir, stat } from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
|
|
@@ -321,6 +320,9 @@ export function createInjectionSource(features, {
|
|
|
321
320
|
[data-codex-model-slider-trigger-label] {
|
|
322
321
|
display: inline; overflow: visible; text-overflow: clip; white-space: nowrap;
|
|
323
322
|
}
|
|
323
|
+
[data-codex-model-slider-trigger] [class*="_ModelPickerTriggerEffortLabel_"] {
|
|
324
|
+
display: inline-flex; align-items: center; align-self: center;
|
|
325
|
+
}
|
|
324
326
|
html[data-codex-model-slider-selecting-effort] [role="menu"][data-state="open"]:not(:has([data-reasoning-slider])) {
|
|
325
327
|
visibility: hidden !important; opacity: 0 !important; animation: none !important; pointer-events: none !important;
|
|
326
328
|
}
|
|
@@ -374,7 +376,6 @@ export function createInjectionSource(features, {
|
|
|
374
376
|
if (modelSelectorFeature) {
|
|
375
377
|
document.documentElement.setAttribute("data-codex-model-slider-catalog-ready", "");
|
|
376
378
|
document.documentElement.append(modelSelectorStyle);
|
|
377
|
-
void loadNativeFastIcon();
|
|
378
379
|
}
|
|
379
380
|
|
|
380
381
|
for (const feature of pageScriptFeatures) {
|
|
@@ -617,32 +618,6 @@ export function createInjectionSource(features, {
|
|
|
617
618
|
return icon;
|
|
618
619
|
}
|
|
619
620
|
|
|
620
|
-
async function loadNativeFastIcon() {
|
|
621
|
-
const sourceLink = document.querySelector('link[href*="/assets/app-initial-"][href$=".js"]');
|
|
622
|
-
if (!sourceLink?.href) return;
|
|
623
|
-
try {
|
|
624
|
-
const source = await fetch(sourceLink.href).then((response) => response.text());
|
|
625
|
-
const active = source.match(/d:`(M11\.9125 21\.4125[^`]+)`,fill:`currentColor`/);
|
|
626
|
-
const inactive = source.match(/d:`(M7\.38 16\.2207[^`]+)`,fill:`currentColor`/);
|
|
627
|
-
if (!active?.[1] || !inactive?.[1]) return;
|
|
628
|
-
modelSelectorNativeFastIcons = {
|
|
629
|
-
active: { viewBox: "0 0 24 24", markup: `<path d="${active[1]}" fill="currentColor" />` },
|
|
630
|
-
inactive: {
|
|
631
|
-
viewBox: "0 0 20 20",
|
|
632
|
-
markup: `<g transform="translate(2.43 1.609)"><path d="${inactive[1]}" fill="currentColor" /></g>`,
|
|
633
|
-
},
|
|
634
|
-
};
|
|
635
|
-
document.querySelectorAll("[data-codex-model-slider-fast]").forEach((button) => {
|
|
636
|
-
const icon = createNativeFastIcon(button.getAttribute("aria-pressed") === "true");
|
|
637
|
-
const content = button.querySelector("[data-codex-model-slider-fast-content]");
|
|
638
|
-
if (icon && content) content.replaceChildren(icon);
|
|
639
|
-
});
|
|
640
|
-
queueEnsure();
|
|
641
|
-
} catch {
|
|
642
|
-
// Leave the native control untouched when this private Codex asset moves.
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
621
|
function nativeCssModuleClass(tokenName, root = document) {
|
|
647
622
|
const pattern = new RegExp(`^_${tokenName}_[A-Za-z0-9_-]+$`);
|
|
648
623
|
const live = Array.from(root.querySelectorAll(`[class*="_${tokenName}_"]`))
|
|
@@ -674,11 +649,33 @@ export function createInjectionSource(features, {
|
|
|
674
649
|
return null;
|
|
675
650
|
}
|
|
676
651
|
|
|
652
|
+
function captureNativeFastIcon() {
|
|
653
|
+
const capture = (state, pathPrefix) => {
|
|
654
|
+
if (modelSelectorNativeFastIcons?.[state]) return;
|
|
655
|
+
const path = Array.from(document.querySelectorAll("svg path")).find(
|
|
656
|
+
(candidate) => candidate.getAttribute("d")?.startsWith(pathPrefix),
|
|
657
|
+
);
|
|
658
|
+
const svg = path?.closest("svg");
|
|
659
|
+
if (!svg) return;
|
|
660
|
+
const template = svg.cloneNode(true);
|
|
661
|
+
template.removeAttribute("id");
|
|
662
|
+
template.removeAttribute("width");
|
|
663
|
+
template.removeAttribute("height");
|
|
664
|
+
if (!modelSelectorNativeFastIcons) modelSelectorNativeFastIcons = {};
|
|
665
|
+
modelSelectorNativeFastIcons[state] = template;
|
|
666
|
+
};
|
|
667
|
+
capture("active", "M11.9125 21.4125");
|
|
668
|
+
capture("inactive", "M7.38 16.2207");
|
|
669
|
+
return modelSelectorNativeFastIcons;
|
|
670
|
+
}
|
|
671
|
+
|
|
677
672
|
function createNativeFastIcon(active) {
|
|
678
|
-
const
|
|
679
|
-
|
|
673
|
+
const template = captureNativeFastIcon()?.[active ? "active" : "inactive"];
|
|
674
|
+
if (!template) return null;
|
|
675
|
+
const icon = template.cloneNode(true);
|
|
676
|
+
icon.setAttribute("aria-hidden", "true");
|
|
680
677
|
const iconClass = nativeCssModuleClass("FastModeIcon");
|
|
681
|
-
if (
|
|
678
|
+
if (iconClass && !icon.classList.contains(iconClass)) icon.classList.add(iconClass);
|
|
682
679
|
return icon;
|
|
683
680
|
}
|
|
684
681
|
|
|
@@ -852,7 +849,20 @@ export function createInjectionSource(features, {
|
|
|
852
849
|
const aliases = (selector.models ?? []).filter((model) => (
|
|
853
850
|
comparableModelLabel(model.slug) === target || comparableModelLabel(model.displayName) === target
|
|
854
851
|
));
|
|
855
|
-
return aliases
|
|
852
|
+
return resolveModelAlias(aliases, identity);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function resolveModelAlias(aliases, identity) {
|
|
856
|
+
if (aliases.length === 1) return aliases[0];
|
|
857
|
+
if (aliases.length < 2 || identity.provider) return null;
|
|
858
|
+
const native = aliases.find((model) => !model.slug.includes("/"));
|
|
859
|
+
const thirdParty = aliases.find((model) => model.slug.includes("/"));
|
|
860
|
+
if (!native || !thirdParty) return null;
|
|
861
|
+
if (!modelIdentity(native.slug).backend.toLowerCase().startsWith("gpt")) return null;
|
|
862
|
+
const nativeLabel = formatIdentifier(
|
|
863
|
+
modelIdentity(String(native.displayName ?? native.slug)).backend,
|
|
864
|
+
).toLowerCase();
|
|
865
|
+
return formatIdentifier(identity.backend).toLowerCase() === nativeLabel ? native : thirdParty;
|
|
856
866
|
}
|
|
857
867
|
|
|
858
868
|
function loadModelFavorites(selector) {
|
|
@@ -1128,6 +1138,29 @@ export function createInjectionSource(features, {
|
|
|
1128
1138
|
}, 260);
|
|
1129
1139
|
}
|
|
1130
1140
|
|
|
1141
|
+
function nativeGptSortRank(rawValue) {
|
|
1142
|
+
const known = new Map([
|
|
1143
|
+
["gpt-5.6-sol", 1], ["gpt-5.6-terra", 2], ["gpt-5.6-luna", 3],
|
|
1144
|
+
["gpt-5.5", 4], ["gpt-5.4", 5], ["gpt-5.4-mini", 6], ["gpt-5.3-codex-spark", 7],
|
|
1145
|
+
]);
|
|
1146
|
+
const raw = String(rawValue ?? "").trim();
|
|
1147
|
+
const knownRank = known.get(raw);
|
|
1148
|
+
if (knownRank != null) return { official: true, rank: knownRank };
|
|
1149
|
+
const identity = modelIdentity(raw);
|
|
1150
|
+
return identity.provider ? null : { official: true, rank: 100 };
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function compareEnhancedModelItems(a, b, favorites) {
|
|
1154
|
+
const favA = Number(favorites.has(a.dataset.codexModelSliderRaw));
|
|
1155
|
+
const favB = Number(favorites.has(b.dataset.codexModelSliderRaw));
|
|
1156
|
+
if (favA !== favB) return favB - favA;
|
|
1157
|
+
const rankA = nativeGptSortRank(a.dataset.codexModelSliderRaw);
|
|
1158
|
+
const rankB = nativeGptSortRank(b.dataset.codexModelSliderRaw);
|
|
1159
|
+
if (Boolean(rankA) !== Boolean(rankB)) return rankA ? -1 : 1;
|
|
1160
|
+
if (rankA && rankB) return rankA.rank - rankB.rank;
|
|
1161
|
+
return 0;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1131
1164
|
function renderEnhancedModelMenu(menu, selector) {
|
|
1132
1165
|
const items = modelMenuItems(menu).filter((item) => item.dataset.codexModelSliderRaw);
|
|
1133
1166
|
if (!items.length) return;
|
|
@@ -1135,7 +1168,7 @@ export function createInjectionSource(features, {
|
|
|
1135
1168
|
items.forEach((item) => { item.style.display = ""; });
|
|
1136
1169
|
const starred = items.filter((item) => favorites.has(item.dataset.codexModelSliderRaw));
|
|
1137
1170
|
const ordinary = items.filter((item) => !favorites.has(item.dataset.codexModelSliderRaw));
|
|
1138
|
-
const desired =
|
|
1171
|
+
const desired = items.slice().sort((a, b) => compareEnhancedModelItems(a, b, favorites));
|
|
1139
1172
|
const parent = items[0].parentElement;
|
|
1140
1173
|
if (!parent || !desired.every((item) => item.parentElement === parent)) return;
|
|
1141
1174
|
const current = Array.from(parent.children).filter((child) => child.hasAttribute?.("data-codex-model-slider-item"));
|
|
@@ -1148,7 +1181,10 @@ export function createInjectionSource(features, {
|
|
|
1148
1181
|
divider.setAttribute("data-codex-model-slider-divider", "");
|
|
1149
1182
|
divider.setAttribute("role", "separator");
|
|
1150
1183
|
}
|
|
1151
|
-
|
|
1184
|
+
const firstOrdinary = desired.find((item) => !favorites.has(item.dataset.codexModelSliderRaw));
|
|
1185
|
+
if (firstOrdinary && divider.nextElementSibling !== firstOrdinary) {
|
|
1186
|
+
parent.insertBefore(divider, firstOrdinary);
|
|
1187
|
+
}
|
|
1152
1188
|
} else {
|
|
1153
1189
|
divider?.remove();
|
|
1154
1190
|
}
|
|
@@ -1204,6 +1240,7 @@ export function createInjectionSource(features, {
|
|
|
1204
1240
|
}
|
|
1205
1241
|
|
|
1206
1242
|
function enhanceModelItem(item, menu, selector, index) {
|
|
1243
|
+
if (item.querySelector("[data-codex-model-slider-row]")) return;
|
|
1207
1244
|
const observedRaw = modelItemRawValue(item);
|
|
1208
1245
|
if (!observedRaw) return;
|
|
1209
1246
|
if (!modelSelectorOriginalItems.has(item)) modelSelectorOriginalItems.set(item, item.innerHTML);
|
|
@@ -1215,7 +1252,6 @@ export function createInjectionSource(features, {
|
|
|
1215
1252
|
item.dataset.codexModelSliderItem = "";
|
|
1216
1253
|
item.dataset.codexModelSliderOrder = String(index);
|
|
1217
1254
|
item.dataset.codexModelSliderIdentitySource = catalogModel ? "catalog" : nativeDescriptor ? "native" : "visible";
|
|
1218
|
-
if (item.querySelector("[data-codex-model-slider-row]")) return;
|
|
1219
1255
|
|
|
1220
1256
|
const identity = modelIdentity(raw);
|
|
1221
1257
|
const display = modelDisplayLabel(identity, selector);
|
|
@@ -1808,8 +1844,14 @@ export function createInjectionSource(features, {
|
|
|
1808
1844
|
const model = modelDefinitionFor(identity, selector);
|
|
1809
1845
|
const efforts = model?.supportedReasoningLevels ?? [];
|
|
1810
1846
|
if (!model || efforts.length < 2) return false;
|
|
1811
|
-
|
|
1812
|
-
|
|
1847
|
+
// 控制器推导的任务内实际 effort 优先,避免子任务/主任务切换后触发器属性滞后;
|
|
1848
|
+
// 触发器属性仅在控制器无法解析时作为实时回退,静态目录默认值最后兜底。
|
|
1849
|
+
const nativeModel = nativeCatalogModelFor(identity);
|
|
1850
|
+
const selectedEffort = nativeModel?.defaultReasoningLevel
|
|
1851
|
+
?? document.querySelector("[data-codex-intelligence-trigger]")
|
|
1852
|
+
?.getAttribute("data-selected-reasoning-effort")
|
|
1853
|
+
?? model.defaultReasoningLevel
|
|
1854
|
+
?? efforts[0].effort;
|
|
1813
1855
|
const selectedServiceTier = nativeComposerModelController()?.selectedServiceTier;
|
|
1814
1856
|
const serviceTierKey = selectedServiceTier?.id ?? selectedServiceTier ?? "standard";
|
|
1815
1857
|
const key = `${identity.raw}:${selectedEffort}:${serviceTierKey}:${efforts.map((level) => level.effort).join(",")}`;
|
|
@@ -1818,7 +1860,6 @@ export function createInjectionSource(features, {
|
|
|
1818
1860
|
parentMenu.firstElementChild
|
|
1819
1861
|
?.querySelector("[data-model-picker-power-slider] [class*='_TickRail_'] > span"),
|
|
1820
1862
|
);
|
|
1821
|
-
const nativeModel = nativeCatalogModelFor(identity);
|
|
1822
1863
|
const templateWaitCount = Number(parentMenu.getAttribute("data-codex-model-slider-template-wait") ?? 0);
|
|
1823
1864
|
if (nativeModel?.slug.toLowerCase().startsWith("gpt-") && !nativeTemplateReady && templateWaitCount < 3) {
|
|
1824
1865
|
parentMenu.setAttribute("data-codex-model-slider-template-wait", String(templateWaitCount + 1));
|
|
@@ -1914,6 +1955,7 @@ export function createInjectionSource(features, {
|
|
|
1914
1955
|
function ensureModelSelector() {
|
|
1915
1956
|
const selector = modelSelectorFeature?.modelSelector;
|
|
1916
1957
|
if (!selector) return;
|
|
1958
|
+
captureNativeFastIcon();
|
|
1917
1959
|
pruneModelSubmenuPositionObservers();
|
|
1918
1960
|
if (!modelSelectorStyle.isConnected) document.head?.append(modelSelectorStyle);
|
|
1919
1961
|
enhanceComposerModelTrigger(selector);
|
|
@@ -2950,6 +2992,42 @@ export function createInjectionSource(features, {
|
|
|
2950
2992
|
return null;
|
|
2951
2993
|
}
|
|
2952
2994
|
|
|
2995
|
+
function nativeJsxRuntime(appModule) {
|
|
2996
|
+
const candidates = Object.values(appModule).filter((value) => {
|
|
2997
|
+
if (typeof value !== "function" || value.length !== 0) return false;
|
|
2998
|
+
let source;
|
|
2999
|
+
try {
|
|
3000
|
+
source = Function.prototype.toString.call(value);
|
|
3001
|
+
} catch {
|
|
3002
|
+
return false;
|
|
3003
|
+
}
|
|
3004
|
+
if (source.length > 1200) return false;
|
|
3005
|
+
if (
|
|
3006
|
+
source.includes("useState")
|
|
3007
|
+
|| source.includes("memo_cache_sentinel")
|
|
3008
|
+
|| source.includes("(0,")
|
|
3009
|
+
|| source.startsWith("class ")
|
|
3010
|
+
) return false;
|
|
3011
|
+
return source.includes("jsxs") && source.includes("Fragment");
|
|
3012
|
+
});
|
|
3013
|
+
for (const candidate of candidates) {
|
|
3014
|
+
let runtime;
|
|
3015
|
+
try {
|
|
3016
|
+
runtime = candidate();
|
|
3017
|
+
} catch {
|
|
3018
|
+
continue;
|
|
3019
|
+
}
|
|
3020
|
+
if (
|
|
3021
|
+
runtime
|
|
3022
|
+
&& typeof runtime === "object"
|
|
3023
|
+
&& typeof runtime.jsx === "function"
|
|
3024
|
+
&& typeof runtime.jsxs === "function"
|
|
3025
|
+
&& "Fragment" in runtime
|
|
3026
|
+
) return runtime;
|
|
3027
|
+
}
|
|
3028
|
+
return null;
|
|
3029
|
+
}
|
|
3030
|
+
|
|
2953
3031
|
async function loadNativeTabCapability() {
|
|
2954
3032
|
if (nativeTabCapability) return nativeTabCapability;
|
|
2955
3033
|
if (nativeTabCapabilityPromise) return nativeTabCapabilityPromise;
|
|
@@ -2958,28 +3036,24 @@ export function createInjectionSource(features, {
|
|
|
2958
3036
|
const moduleUrl = await waitForNativeTabModuleUrl();
|
|
2959
3037
|
if (!moduleUrl) throw new Error("Codex app-initial modulepreload was not found");
|
|
2960
3038
|
const appModule = await import(moduleUrl);
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
3039
|
+
const controller = Object.values(appModule).find((value) => (
|
|
3040
|
+
value
|
|
3041
|
+
&& typeof value === "object"
|
|
3042
|
+
&& typeof value.openTab === "function"
|
|
3043
|
+
&& typeof value.closeTab === "function"
|
|
3044
|
+
&& typeof value.activateTab === "function"
|
|
3045
|
+
&& value.panelId === "right"
|
|
3046
|
+
));
|
|
3047
|
+
if (!controller) throw new Error("Codex right-panel controller is unavailable");
|
|
3048
|
+
const jsx = nativeJsxRuntime(appModule);
|
|
2970
3049
|
if (
|
|
2971
|
-
|
|
2972
|
-
|| typeof controller?.activateTab !== "function"
|
|
2973
|
-
|| typeof controller?.closeTab !== "function"
|
|
2974
|
-
|| !controller?.tabById$
|
|
3050
|
+
!controller?.tabById$
|
|
2975
3051
|
|| typeof jsx?.jsx !== "function"
|
|
2976
|
-
|| typeof
|
|
2977
|
-
|| typeof React?.useRef !== "function"
|
|
3052
|
+
|| typeof jsx?.jsxs !== "function"
|
|
2978
3053
|
) throw new Error("Codex native tab capability is incomplete");
|
|
2979
3054
|
nativeTabCapability = {
|
|
2980
3055
|
controller,
|
|
2981
3056
|
jsx,
|
|
2982
|
-
React,
|
|
2983
3057
|
moduleAsset: moduleUrl.split("/").pop(),
|
|
2984
3058
|
};
|
|
2985
3059
|
nativeTabCapabilityError = null;
|
|
@@ -3016,38 +3090,10 @@ export function createInjectionSource(features, {
|
|
|
3016
3090
|
const detailKey = `${feature.id}:${detail.id}`;
|
|
3017
3091
|
let Component = nativeDetailComponents.get(detailKey);
|
|
3018
3092
|
if (Component) return Component;
|
|
3019
|
-
const { jsx
|
|
3093
|
+
const { jsx } = capability;
|
|
3094
|
+
const frameName = surfaceFrameName(feature.id);
|
|
3020
3095
|
Component = function CodexPersonalDetailTab() {
|
|
3021
|
-
const elementRef = React.useRef(null);
|
|
3022
|
-
const frameRef = React.useRef(null);
|
|
3023
|
-
const recordKeyRef = React.useRef(null);
|
|
3024
|
-
const frameNameRef = React.useRef(null);
|
|
3025
|
-
frameNameRef.current ??= surfaceFrameName(feature.id);
|
|
3026
|
-
React.useLayoutEffect(() => {
|
|
3027
|
-
const element = elementRef.current;
|
|
3028
|
-
const frame = frameRef.current;
|
|
3029
|
-
if (!element || !frame) return undefined;
|
|
3030
|
-
const instanceId = crypto.randomUUID?.()
|
|
3031
|
-
?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
3032
|
-
const recordKey = surfaceKey(feature.id, "native-detail", `${detail.id}:${instanceId}`);
|
|
3033
|
-
recordKeyRef.current = recordKey;
|
|
3034
|
-
registerSurface(recordKey, feature, "detail", detail.surfaceUrl, element, frame, detail.id);
|
|
3035
|
-
let keys = nativeDetailSurfaceKeys.get(detailKey);
|
|
3036
|
-
if (!keys) {
|
|
3037
|
-
keys = new Set();
|
|
3038
|
-
nativeDetailSurfaceKeys.set(detailKey, keys);
|
|
3039
|
-
}
|
|
3040
|
-
keys.add(recordKey);
|
|
3041
|
-
queueTheme();
|
|
3042
|
-
return () => {
|
|
3043
|
-
keys.delete(recordKey);
|
|
3044
|
-
if (keys.size === 0) nativeDetailSurfaceKeys.delete(detailKey);
|
|
3045
|
-
removeSurfaceRecord(recordKey);
|
|
3046
|
-
recordKeyRef.current = null;
|
|
3047
|
-
};
|
|
3048
|
-
}, []);
|
|
3049
3096
|
return jsx.jsx("div", {
|
|
3050
|
-
ref: elementRef,
|
|
3051
3097
|
"data-codex-personal-native-detail": detailKey,
|
|
3052
3098
|
style: {
|
|
3053
3099
|
height: "100%",
|
|
@@ -3056,8 +3102,7 @@ export function createInjectionSource(features, {
|
|
|
3056
3102
|
background: "var(--color-token-main-surface-primary)",
|
|
3057
3103
|
},
|
|
3058
3104
|
children: jsx.jsx("iframe", {
|
|
3059
|
-
|
|
3060
|
-
name: frameNameRef.current,
|
|
3105
|
+
name: frameName,
|
|
3061
3106
|
src: "about:blank",
|
|
3062
3107
|
title: detail.label,
|
|
3063
3108
|
allow: "clipboard-write",
|
|
@@ -3085,17 +3130,17 @@ export function createInjectionSource(features, {
|
|
|
3085
3130
|
return reloaded;
|
|
3086
3131
|
}
|
|
3087
3132
|
|
|
3088
|
-
async function
|
|
3133
|
+
async function waitForNativeDetailTabElement(detailKey) {
|
|
3089
3134
|
const deadline = performance.now() + 2_000;
|
|
3090
3135
|
do {
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
if (
|
|
3136
|
+
const element = document.querySelector(
|
|
3137
|
+
`[data-codex-personal-native-detail="${detailKey}"]`,
|
|
3138
|
+
);
|
|
3139
|
+
const frame = element?.querySelector("iframe");
|
|
3140
|
+
if (element && frame) return { element, frame };
|
|
3096
3141
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
3097
3142
|
} while (performance.now() < deadline);
|
|
3098
|
-
return
|
|
3143
|
+
return null;
|
|
3099
3144
|
}
|
|
3100
3145
|
|
|
3101
3146
|
async function openNativeDetailTab(feature, detail) {
|
|
@@ -3134,7 +3179,14 @@ export function createInjectionSource(features, {
|
|
|
3134
3179
|
icon: nativeDetailIcon(feature, detail, jsx),
|
|
3135
3180
|
isClosable: true,
|
|
3136
3181
|
props: {},
|
|
3137
|
-
onClose: () =>
|
|
3182
|
+
onClose: () => {
|
|
3183
|
+
nativeOpenTabSessions.delete(session);
|
|
3184
|
+
const closedKeys = nativeDetailSurfaceKeys.get(detailKey);
|
|
3185
|
+
if (closedKeys) {
|
|
3186
|
+
for (const key of closedKeys) removeSurfaceRecord(key);
|
|
3187
|
+
nativeDetailSurfaceKeys.delete(detailKey);
|
|
3188
|
+
}
|
|
3189
|
+
},
|
|
3138
3190
|
});
|
|
3139
3191
|
} catch (error) {
|
|
3140
3192
|
if (isNewSession) nativeOpenTabSessions.delete(session);
|
|
@@ -3142,15 +3194,29 @@ export function createInjectionSource(features, {
|
|
|
3142
3194
|
return null;
|
|
3143
3195
|
}
|
|
3144
3196
|
|
|
3145
|
-
if (!
|
|
3146
|
-
|
|
3147
|
-
if (!
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3197
|
+
if (!nativeDetailSurfaceKeys.has(detailKey)) {
|
|
3198
|
+
const mounted = await waitForNativeDetailTabElement(detailKey);
|
|
3199
|
+
if (!mounted) {
|
|
3200
|
+
if (isNewSession) nativeOpenTabSessions.delete(session);
|
|
3201
|
+
if (!focusedExisting) {
|
|
3202
|
+
try {
|
|
3203
|
+
controller.closeTab(scope, tabId);
|
|
3204
|
+
} catch {}
|
|
3205
|
+
}
|
|
3206
|
+
nativeTabCapabilityError = new Error("Codex native right-panel Tab did not mount");
|
|
3207
|
+
return null;
|
|
3151
3208
|
}
|
|
3152
|
-
|
|
3153
|
-
|
|
3209
|
+
const instanceId = crypto.randomUUID?.()
|
|
3210
|
+
?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
3211
|
+
const recordKey = surfaceKey(feature.id, "native-detail", `${detail.id}:${instanceId}`);
|
|
3212
|
+
registerSurface(recordKey, feature, "detail", detail.surfaceUrl, mounted.element, mounted.frame, detail.id);
|
|
3213
|
+
let keys = nativeDetailSurfaceKeys.get(detailKey);
|
|
3214
|
+
if (!keys) {
|
|
3215
|
+
keys = new Set();
|
|
3216
|
+
nativeDetailSurfaceKeys.set(detailKey, keys);
|
|
3217
|
+
}
|
|
3218
|
+
keys.add(recordKey);
|
|
3219
|
+
queueTheme();
|
|
3154
3220
|
}
|
|
3155
3221
|
|
|
3156
3222
|
nativeTabCapabilityError = null;
|
|
@@ -3168,6 +3234,9 @@ export function createInjectionSource(features, {
|
|
|
3168
3234
|
}
|
|
3169
3235
|
|
|
3170
3236
|
function closeNativeDetailTabs() {
|
|
3237
|
+
for (const keys of Array.from(nativeDetailSurfaceKeys.values())) {
|
|
3238
|
+
for (const key of keys) removeSurfaceRecord(key);
|
|
3239
|
+
}
|
|
3171
3240
|
for (const session of Array.from(nativeOpenTabSessions)) {
|
|
3172
3241
|
try {
|
|
3173
3242
|
session.controller.closeTab(session.scope, session.tabId);
|
|
@@ -3884,7 +3953,7 @@ export class CodexRuntime {
|
|
|
3884
3953
|
cdpPort = 9231,
|
|
3885
3954
|
logger = console,
|
|
3886
3955
|
fetchImpl = globalThis.fetch,
|
|
3887
|
-
|
|
3956
|
+
launchApplication = null,
|
|
3888
3957
|
connectClient = connect,
|
|
3889
3958
|
sleep = (timeoutMs) => new Promise((resolve) => setTimeout(resolve, timeoutMs)),
|
|
3890
3959
|
availabilityTimeoutMs = 20_000,
|
|
@@ -3903,7 +3972,7 @@ export class CodexRuntime {
|
|
|
3903
3972
|
this.cdpPort = cdpPort;
|
|
3904
3973
|
this.logger = logger;
|
|
3905
3974
|
this.fetchImpl = fetchImpl;
|
|
3906
|
-
this.
|
|
3975
|
+
this.launchApplication = launchApplication;
|
|
3907
3976
|
this.connectClient = connectClient;
|
|
3908
3977
|
this.sleep = sleep;
|
|
3909
3978
|
this.availabilityTimeoutMs = availabilityTimeoutMs;
|
|
@@ -4040,17 +4109,19 @@ export class CodexRuntime {
|
|
|
4040
4109
|
}
|
|
4041
4110
|
|
|
4042
4111
|
launchManagedCodex() {
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4112
|
+
if (typeof this.launchApplication !== "function") {
|
|
4113
|
+
throw new Error("CodexRuntime requires an explicit application launcher for isolated tests.");
|
|
4114
|
+
}
|
|
4115
|
+
const child = this.launchApplication({
|
|
4116
|
+
appPath: this.appPath,
|
|
4117
|
+
args: [
|
|
4046
4118
|
`--user-data-dir=${this.profileDir}`,
|
|
4047
4119
|
"--remote-debugging-address=127.0.0.1",
|
|
4048
4120
|
`--remote-debugging-port=${this.cdpPort}`,
|
|
4049
4121
|
`--remote-allow-origins=http://127.0.0.1:${this.cdpPort}`,
|
|
4050
4122
|
"--no-first-run",
|
|
4051
4123
|
],
|
|
4052
|
-
|
|
4053
|
-
);
|
|
4124
|
+
});
|
|
4054
4125
|
this.managedCodexChild = child;
|
|
4055
4126
|
this.appPid = child.pid ?? null;
|
|
4056
4127
|
this.notifyManagedCodexPidChange(this.appPid);
|
|
@@ -17,7 +17,6 @@ const featuresRoot = process.env.CODEX_FEATURES_ROOT ?? path.dirname(runtimeRoot
|
|
|
17
17
|
const legacyDataDir = process.env.CODEX_IMAGE_HOST_DATA_DIR ?? path.join(os.homedir(), ".codex-image-host");
|
|
18
18
|
const profileDir = process.env.CODEX_RUNTIME_PROFILE_DIR ?? path.join(legacyDataDir, "codex-profile");
|
|
19
19
|
const cdpPort = Number(process.env.CODEX_RUNTIME_CDP_PORT ?? 9231);
|
|
20
|
-
const launchCodex = process.env.MINECODEX_LAUNCH_CODEX === "1";
|
|
21
20
|
|
|
22
21
|
const discoveredFeatures = await discoverFeatures(featuresRoot);
|
|
23
22
|
if (!discoveredFeatures.length) throw new Error(`No codex-feature.json files found under ${featuresRoot}`);
|
|
@@ -66,13 +65,12 @@ runtime = new CodexRuntime({
|
|
|
66
65
|
featureInstanceId,
|
|
67
66
|
profileDir,
|
|
68
67
|
cdpPort,
|
|
69
|
-
appPath: process.env.MINECODEX_CODEX_EXECUTABLE || undefined,
|
|
70
68
|
onManagedCodexPidChange: updateCodexPidFile,
|
|
71
69
|
onStatusChange: requestReadyFileRefresh,
|
|
72
70
|
});
|
|
73
71
|
|
|
74
72
|
try {
|
|
75
|
-
await runtime.start(
|
|
73
|
+
await runtime.start();
|
|
76
74
|
runtimeReady = true;
|
|
77
75
|
await writeReadyFile();
|
|
78
76
|
} catch (error) {
|