minecodex 0.1.19 → 0.2.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/features/images/codex-feature.json +5 -0
- package/features/images/src/chatgpt-image-source-page.mjs +207 -0
- package/features/images/src/chatgpt-image-source.mjs +42 -0
- package/features/images/src/http-server.mjs +92 -3
- package/features/images/src/image-library.mjs +171 -4
- package/features/images/web/app.js +35 -13
- package/features/model-slider/THIRD_PARTY_NOTICES.md +7 -0
- package/features/model-slider/assets/providers/muse.svg +1 -0
- package/features/model-slider/codex-feature.json +8 -2
- package/package.json +1 -1
- package/packages/cli/src/chatgpt-launch-coordinator.mjs +27 -1
- package/packages/cli/src/platform.mjs +0 -12
- package/packages/cli/src/runtime-manager.mjs +25 -28
- package/packages/runtime-host/src/codex-runtime.mjs +397 -135
- package/packages/runtime-host/src/feature-registry.mjs +56 -10
|
@@ -37,7 +37,7 @@ export function responsiveSummaryVisibleSurface({ displayMode, isPinned, isPopov
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export function responsiveSummaryContentShift({ displayMode, isPinned }, layout = RESPONSIVE_SUMMARY_LAYOUT) {
|
|
40
|
-
return displayMode
|
|
40
|
+
return displayMode !== "overlay" && isPinned
|
|
41
41
|
? -(layout.panelWidth + layout.panelInset) / 2
|
|
42
42
|
: 0;
|
|
43
43
|
}
|
|
@@ -78,6 +78,18 @@ function requireDeclaredLoopbackSurface(feature, value) {
|
|
|
78
78
|
return url;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
export function pageScriptServiceOrigin(feature) {
|
|
82
|
+
if (!feature.pageScript || !feature.surfaceUrl || !feature.healthUrl) return null;
|
|
83
|
+
const surfaceUrl = new URL(feature.surfaceUrl);
|
|
84
|
+
const healthUrl = new URL(feature.healthUrl);
|
|
85
|
+
if (
|
|
86
|
+
surfaceUrl.origin !== healthUrl.origin
|
|
87
|
+
|| surfaceUrl.protocol !== "http:"
|
|
88
|
+
|| !LOOPBACK_SURFACE_HOSTS.has(surfaceUrl.hostname)
|
|
89
|
+
) return null;
|
|
90
|
+
return surfaceUrl.origin;
|
|
91
|
+
}
|
|
92
|
+
|
|
81
93
|
function findFrameByName(frameTree, frameName) {
|
|
82
94
|
if (frameTree.frame?.name === frameName) return frameTree.frame;
|
|
83
95
|
for (const child of frameTree.childFrames ?? []) {
|
|
@@ -127,9 +139,6 @@ export function createInjectionSource(features, {
|
|
|
127
139
|
const pinnedSurfaceMarker = "data-codex-personal-pinned-summary";
|
|
128
140
|
const modalSurfaceMarker = "data-codex-personal-modal";
|
|
129
141
|
const promptPreviewMarker = "data-codex-personal-prompt-preview";
|
|
130
|
-
const threadShiftStyle = document.createElement("style");
|
|
131
|
-
threadShiftStyle.setAttribute("data-codex-personal-summary-shift-style", "");
|
|
132
|
-
threadShiftStyle.textContent = "[data-codex-personal-summary-shift] { transform: none !important; --thread-wide-block-inline-shift: 0px !important; }";
|
|
133
142
|
const nativeLabels = {
|
|
134
143
|
sites: ["sites", "站点"],
|
|
135
144
|
scheduled: ["scheduled", "计划任务"],
|
|
@@ -254,7 +263,7 @@ export function createInjectionSource(features, {
|
|
|
254
263
|
const modelSelectorFeature = features.find(
|
|
255
264
|
(feature) => feature.entry?.kind === "composer-model-selector",
|
|
256
265
|
) ?? null;
|
|
257
|
-
const pageScriptFeatures = features.filter((feature) => feature.
|
|
266
|
+
const pageScriptFeatures = features.filter((feature) => feature.pageScript);
|
|
258
267
|
const pageSurfaces = new Map();
|
|
259
268
|
const pinnedSurfaces = new Map();
|
|
260
269
|
const surfaceRecords = new Map();
|
|
@@ -273,9 +282,9 @@ export function createInjectionSource(features, {
|
|
|
273
282
|
let savedComposerThread = null;
|
|
274
283
|
let currentThread = null;
|
|
275
284
|
let currentSummaryDisplayMode = null;
|
|
276
|
-
let
|
|
277
|
-
let shiftedThreadOriginal = null;
|
|
285
|
+
let shiftedConversationOwners = [];
|
|
278
286
|
let mainContentObserver = null;
|
|
287
|
+
let toolbarReadiness = null;
|
|
279
288
|
const domObservers = [];
|
|
280
289
|
const pageScriptCleanups = [];
|
|
281
290
|
const pendingHostActions = new Map();
|
|
@@ -300,6 +309,11 @@ export function createInjectionSource(features, {
|
|
|
300
309
|
let modelSelectorNativeFastIcons = null;
|
|
301
310
|
// Fast 图标来源标记:bundle 静态提取 vs DOM 克隆,DOM 克隆优先以保持像素一致。
|
|
302
311
|
let modelSelectorNativeFastIconSources = null;
|
|
312
|
+
// DOM 克隆可见性门控:仅在 Fast 控件从无到有时扫描一次,常驻会话不逐帧扫描。
|
|
313
|
+
let modelSelectorFastControlVisible = false;
|
|
314
|
+
// 持久化脏标记:仅在图标实际变化时写一次 localStorage。
|
|
315
|
+
let modelSelectorNativeFastIconsDirty = false;
|
|
316
|
+
const modelSelectorFastIconStorageKey = "codex-model-slider:fast-icons:v1";
|
|
303
317
|
const modelSelectorStyle = document.createElement("style");
|
|
304
318
|
modelSelectorStyle.setAttribute("data-codex-model-slider-style", "");
|
|
305
319
|
modelSelectorStyle.textContent = `
|
|
@@ -380,7 +394,7 @@ export function createInjectionSource(features, {
|
|
|
380
394
|
document.documentElement.append(modelSelectorStyle);
|
|
381
395
|
// 启动阶段就解析 Fast 图标:先读持久化的原生克隆,再从 app bundle 静态提取兜底,
|
|
382
396
|
// 避免首次会话必须等用户点开弹窗后 DOM 里才出现图标。
|
|
383
|
-
const persistedIcons = loadPersistedNativeFastIcons();
|
|
397
|
+
const persistedIcons = loadPersistedNativeFastIcons(modelSelectorFastIconStorageKey);
|
|
384
398
|
if (persistedIcons) {
|
|
385
399
|
modelSelectorNativeFastIcons = persistedIcons;
|
|
386
400
|
modelSelectorNativeFastIconSources = {};
|
|
@@ -400,9 +414,16 @@ export function createInjectionSource(features, {
|
|
|
400
414
|
"window",
|
|
401
415
|
"document",
|
|
402
416
|
"MutationObserver",
|
|
417
|
+
"config",
|
|
403
418
|
`"use strict";\n${source}`,
|
|
404
419
|
);
|
|
405
|
-
const cleanup = installer(
|
|
420
|
+
const cleanup = installer(
|
|
421
|
+
lifetime.signal,
|
|
422
|
+
window,
|
|
423
|
+
document,
|
|
424
|
+
MutationObserver,
|
|
425
|
+
feature.pageScript.config ?? {},
|
|
426
|
+
);
|
|
406
427
|
if (typeof cleanup === "function") pageScriptCleanups.push(cleanup);
|
|
407
428
|
} catch (error) {
|
|
408
429
|
console.warn(`[codex-personal] page-script feature failed: ${feature.id}`, error);
|
|
@@ -467,6 +488,57 @@ export function createInjectionSource(features, {
|
|
|
467
488
|
}) ?? null;
|
|
468
489
|
}
|
|
469
490
|
|
|
491
|
+
function structuralToolbarAnchor() {
|
|
492
|
+
const buttons = Array.from(document.querySelectorAll("button"));
|
|
493
|
+
return buttons.find((button) => /temporary chat/i.test(button.getAttribute("aria-label") ?? ""))
|
|
494
|
+
?? buttons.find((button) => new Set(["Toggle summary", "Toggle pinned summary"])
|
|
495
|
+
.has(button.getAttribute("aria-label")))
|
|
496
|
+
?? buttons.find((button) => button.getAttribute("aria-label") === "Toggle bottom panel")
|
|
497
|
+
?? buttons.find((button) => button.getAttribute("aria-label") === "Toggle side panel")
|
|
498
|
+
?? null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function stopToolbarReadiness() {
|
|
502
|
+
if (!toolbarReadiness) return;
|
|
503
|
+
toolbarReadiness.observer.disconnect();
|
|
504
|
+
if (toolbarReadiness.retryTimer != null) clearTimeout(toolbarReadiness.retryTimer);
|
|
505
|
+
if (toolbarReadiness.deadlineTimer != null) clearTimeout(toolbarReadiness.deadlineTimer);
|
|
506
|
+
toolbarReadiness = null;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function ensureToolbarReadiness(candidate) {
|
|
510
|
+
const thread = threadIdentity();
|
|
511
|
+
if (toolbarReadiness?.candidate === candidate && toolbarReadiness.thread === thread) return;
|
|
512
|
+
stopToolbarReadiness();
|
|
513
|
+
const record = {
|
|
514
|
+
candidate,
|
|
515
|
+
thread,
|
|
516
|
+
attempts: 0,
|
|
517
|
+
retryTimer: null,
|
|
518
|
+
deadlineTimer: null,
|
|
519
|
+
observer: null,
|
|
520
|
+
};
|
|
521
|
+
const wake = () => {
|
|
522
|
+
if (toolbarReadiness !== record) return;
|
|
523
|
+
queueEnsure();
|
|
524
|
+
};
|
|
525
|
+
record.observer = new ResizeObserver(wake);
|
|
526
|
+
record.observer.observe(candidate);
|
|
527
|
+
const header = candidate.closest("header");
|
|
528
|
+
if (header) record.observer.observe(header);
|
|
529
|
+
const retry = () => {
|
|
530
|
+
if (toolbarReadiness !== record || record.attempts >= 8) return;
|
|
531
|
+
record.attempts += 1;
|
|
532
|
+
wake();
|
|
533
|
+
record.retryTimer = setTimeout(retry, 75);
|
|
534
|
+
};
|
|
535
|
+
record.retryTimer = setTimeout(retry, 75);
|
|
536
|
+
record.deadlineTimer = setTimeout(() => {
|
|
537
|
+
if (toolbarReadiness === record) stopToolbarReadiness();
|
|
538
|
+
}, 1_500);
|
|
539
|
+
toolbarReadiness = record;
|
|
540
|
+
}
|
|
541
|
+
|
|
470
542
|
function toolbarControlRoot(button) {
|
|
471
543
|
return button?.parentElement?.parentElement ?? button?.parentElement ?? button ?? null;
|
|
472
544
|
}
|
|
@@ -521,8 +593,10 @@ export function createInjectionSource(features, {
|
|
|
521
593
|
}
|
|
522
594
|
|
|
523
595
|
function mainContentViewport() {
|
|
524
|
-
|
|
596
|
+
const composer = currentComposer();
|
|
597
|
+
return composer?.closest("main[data-app-shell-main-surface]")
|
|
525
598
|
?? document.querySelector("main[data-app-shell-main-surface]")
|
|
599
|
+
?? document.querySelector("[data-app-shell-main-content-layout]")
|
|
526
600
|
?? document.querySelector("main");
|
|
527
601
|
}
|
|
528
602
|
|
|
@@ -662,37 +736,43 @@ export function createInjectionSource(features, {
|
|
|
662
736
|
return null;
|
|
663
737
|
}
|
|
664
738
|
|
|
665
|
-
const modelSelectorFastIconStorageKey = "codex-model-slider:fast-icons:v1";
|
|
666
|
-
|
|
667
739
|
function persistNativeFastIcons() {
|
|
668
|
-
if (!modelSelectorNativeFastIcons) return;
|
|
740
|
+
if (!modelSelectorNativeFastIcons || !modelSelectorNativeFastIconsDirty) return;
|
|
669
741
|
try {
|
|
670
742
|
const payload = {};
|
|
671
743
|
for (const [state, template] of Object.entries(modelSelectorNativeFastIcons)) {
|
|
672
744
|
payload[state] = template.outerHTML;
|
|
673
745
|
}
|
|
674
746
|
localStorage.setItem(modelSelectorFastIconStorageKey, JSON.stringify(payload));
|
|
747
|
+
modelSelectorNativeFastIconsDirty = false;
|
|
675
748
|
} catch {
|
|
676
749
|
// localStorage 不可用时静默降级,仅本次会话内生效。
|
|
677
750
|
}
|
|
678
751
|
}
|
|
679
752
|
|
|
680
|
-
function loadPersistedNativeFastIcons() {
|
|
753
|
+
function loadPersistedNativeFastIcons(storageKey) {
|
|
754
|
+
let stored;
|
|
681
755
|
try {
|
|
682
|
-
|
|
683
|
-
if (!payload || typeof payload !== "object") return null;
|
|
684
|
-
const icons = {};
|
|
685
|
-
for (const [state, markup] of Object.entries(payload)) {
|
|
686
|
-
if (typeof markup !== "string") continue;
|
|
687
|
-
const container = document.createElement("template");
|
|
688
|
-
container.innerHTML = markup;
|
|
689
|
-
const svg = container.content.querySelector("svg");
|
|
690
|
-
if (svg) icons[state] = svg;
|
|
691
|
-
}
|
|
692
|
-
return Object.keys(icons).length ? icons : null;
|
|
756
|
+
stored = localStorage.getItem(storageKey);
|
|
693
757
|
} catch {
|
|
694
758
|
return null;
|
|
695
759
|
}
|
|
760
|
+
let payload;
|
|
761
|
+
try {
|
|
762
|
+
payload = JSON.parse(stored ?? "null");
|
|
763
|
+
} catch {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
if (!payload || typeof payload !== "object") return null;
|
|
767
|
+
const icons = {};
|
|
768
|
+
for (const [state, markup] of Object.entries(payload)) {
|
|
769
|
+
if (typeof markup !== "string") continue;
|
|
770
|
+
const container = document.createElement("template");
|
|
771
|
+
container.innerHTML = markup;
|
|
772
|
+
const svg = container.content.querySelector("svg");
|
|
773
|
+
if (svg) icons[state] = svg;
|
|
774
|
+
}
|
|
775
|
+
return Object.keys(icons).length ? icons : null;
|
|
696
776
|
}
|
|
697
777
|
|
|
698
778
|
function extractFastIconPath(source, prefix) {
|
|
@@ -701,6 +781,8 @@ export function createInjectionSource(features, {
|
|
|
701
781
|
}
|
|
702
782
|
|
|
703
783
|
async function loadNativeFastIconFromBundle() {
|
|
784
|
+
// 两个图标均已就绪(持久化或 DOM 克隆)时不再拉取整包 app-initial 资源。
|
|
785
|
+
if (modelSelectorNativeFastIcons?.active && modelSelectorNativeFastIcons?.inactive) return;
|
|
704
786
|
const sourceLink = document.querySelector('link[href*="/assets/app-initial-"][href$=".js"]');
|
|
705
787
|
if (!sourceLink?.href) return;
|
|
706
788
|
try {
|
|
@@ -728,6 +810,7 @@ export function createInjectionSource(features, {
|
|
|
728
810
|
define("active", "M11\\.9125 21\\.4125", { viewBox: "0 0 24 24" });
|
|
729
811
|
define("inactive", "M7\\.38 16\\.2207", { viewBox: "0 0 20 20", transform: "translate(2.43 1.609)" });
|
|
730
812
|
if (gained) {
|
|
813
|
+
modelSelectorNativeFastIconsDirty = true;
|
|
731
814
|
persistNativeFastIcons();
|
|
732
815
|
applyNativeFastIconsToShells();
|
|
733
816
|
queueEnsure();
|
|
@@ -747,32 +830,36 @@ export function createInjectionSource(features, {
|
|
|
747
830
|
|
|
748
831
|
function captureNativeFastIcon() {
|
|
749
832
|
// Fast 图标只在 Fast 控件渲染后才出现在 DOM(打开菜单或 GPT 模型);
|
|
750
|
-
//
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
(
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
833
|
+
// 只在控件可见性从无到有时做一次全页扫描(升级为 DOM 克隆),
|
|
834
|
+
// 常驻会话不逐帧扫描整个文档;缺图标时由 bundle/持久化兜底。
|
|
835
|
+
const visible = hasVisibleFastControl();
|
|
836
|
+
const justBecameVisible = visible && !modelSelectorFastControlVisible;
|
|
837
|
+
modelSelectorFastControlVisible = visible;
|
|
838
|
+
if (visible && justBecameVisible) {
|
|
839
|
+
const capture = (state, pathPrefix) => {
|
|
840
|
+
const current = modelSelectorNativeFastIcons?.[state];
|
|
841
|
+
const currentSource = modelSelectorNativeFastIconSources?.[state];
|
|
842
|
+
if (current && currentSource === "dom") return;
|
|
843
|
+
const path = Array.from(document.querySelectorAll("svg path")).find(
|
|
844
|
+
(candidate) => candidate.getAttribute("d")?.startsWith(pathPrefix),
|
|
845
|
+
);
|
|
846
|
+
const svg = path?.closest("svg");
|
|
847
|
+
if (!svg) return;
|
|
848
|
+
const template = svg.cloneNode(true);
|
|
849
|
+
template.removeAttribute("id");
|
|
850
|
+
template.removeAttribute("width");
|
|
851
|
+
template.removeAttribute("height");
|
|
852
|
+
if (!modelSelectorNativeFastIcons) {
|
|
853
|
+
modelSelectorNativeFastIcons = {};
|
|
854
|
+
modelSelectorNativeFastIconSources = {};
|
|
855
|
+
}
|
|
856
|
+
modelSelectorNativeFastIcons[state] = template;
|
|
857
|
+
modelSelectorNativeFastIconSources[state] = "dom";
|
|
858
|
+
modelSelectorNativeFastIconsDirty = true;
|
|
859
|
+
};
|
|
860
|
+
capture("active", "M11.9125 21.4125");
|
|
861
|
+
capture("inactive", "M7.38 16.2207");
|
|
862
|
+
}
|
|
776
863
|
const ready = Boolean(
|
|
777
864
|
modelSelectorNativeFastIcons?.active && modelSelectorNativeFastIcons?.inactive,
|
|
778
865
|
);
|
|
@@ -785,14 +872,18 @@ export function createInjectionSource(features, {
|
|
|
785
872
|
|
|
786
873
|
function applyNativeFastIconsToShells() {
|
|
787
874
|
document.querySelectorAll("[data-codex-model-slider-fast]").forEach((button) => {
|
|
788
|
-
const icon = createNativeFastIcon(button.getAttribute("aria-pressed") === "true");
|
|
789
875
|
const content = button.querySelector("[data-codex-model-slider-fast-content]");
|
|
790
|
-
|
|
876
|
+
// 幂等补装:只给还没有图标的按钮补上,避免每次 ensure 都重建节点。
|
|
877
|
+
if (!content || content.querySelector("svg")) return;
|
|
878
|
+
const icon = createNativeFastIcon(button.getAttribute("aria-pressed") === "true");
|
|
879
|
+
if (icon) content.replaceChildren(icon);
|
|
791
880
|
});
|
|
792
881
|
}
|
|
793
882
|
|
|
794
883
|
function createNativeFastIcon(active) {
|
|
795
|
-
|
|
884
|
+
// 直接读取已就绪的图标存储(启动时由 bundle/持久化填充,运行期由
|
|
885
|
+
// captureNativeFastIcon 升级为 DOM 克隆),避免再次触发捕获造成递归。
|
|
886
|
+
const template = modelSelectorNativeFastIcons?.[active ? "active" : "inactive"];
|
|
796
887
|
if (!template) return null;
|
|
797
888
|
const icon = template.cloneNode(true);
|
|
798
889
|
icon.setAttribute("aria-hidden", "true");
|
|
@@ -895,14 +986,16 @@ export function createInjectionSource(features, {
|
|
|
895
986
|
(option) => String(option.thinkingEffort ?? "") === String(selected.thinkingEffort ?? ""),
|
|
896
987
|
);
|
|
897
988
|
const selectedEffort = selectedOption ? effortFor(selectedOption) : (efforts[0] ?? "instant");
|
|
989
|
+
const defaultEffort = String(version.defaultReasoningEffort ?? efforts[0] ?? "instant").toLowerCase();
|
|
898
990
|
const models = [{
|
|
899
991
|
model: String(selected.slug ?? ""),
|
|
900
992
|
displayName: String(version?.label ?? selected.slug ?? ""),
|
|
901
|
-
defaultReasoningEffort:
|
|
993
|
+
defaultReasoningEffort: defaultEffort,
|
|
902
994
|
supportedReasoningEfforts: efforts.map((effort) => ({ reasoningEffort: effort })),
|
|
903
995
|
}];
|
|
904
996
|
return {
|
|
905
997
|
models,
|
|
998
|
+
reasoningEffort: selectedEffort,
|
|
906
999
|
onSelectReasoningEffort(effort) {
|
|
907
1000
|
const target = options.find((option) => effortFor(option) === String(effort).toLowerCase());
|
|
908
1001
|
if (!target) return;
|
|
@@ -914,8 +1007,9 @@ export function createInjectionSource(features, {
|
|
|
914
1007
|
};
|
|
915
1008
|
}
|
|
916
1009
|
|
|
917
|
-
function nativeCatalogModelFor(identity) {
|
|
918
|
-
const
|
|
1010
|
+
function nativeCatalogModelFor(identity, selector = modelSelectorFeature?.modelSelector) {
|
|
1011
|
+
const controller = nativeComposerModelController();
|
|
1012
|
+
const models = controller?.models ?? [];
|
|
919
1013
|
const exact = models.find((model) => String(model.model ?? "").toLowerCase() === identity.raw.toLowerCase());
|
|
920
1014
|
const target = comparableModelLabel(identity.backend);
|
|
921
1015
|
const aliases = exact ? [] : models.filter((model) => (
|
|
@@ -923,13 +1017,25 @@ export function createInjectionSource(features, {
|
|
|
923
1017
|
));
|
|
924
1018
|
const model = exact ?? (aliases.length === 1 ? aliases[0] : null);
|
|
925
1019
|
if (!model) return null;
|
|
1020
|
+
const nativeReasoningLevels = (model.supportedReasoningEfforts ?? []).map((level) => ({
|
|
1021
|
+
effort: String(level?.reasoningEffort ?? level?.effort ?? level ?? "").toLowerCase(),
|
|
1022
|
+
})).filter((level) => level.effort);
|
|
1023
|
+
const allowed = selector?.reasoningEffortOverrides?.[identity.raw.toLowerCase()];
|
|
1024
|
+
const supportedReasoningLevels = allowed
|
|
1025
|
+
? nativeReasoningLevels.filter((level) => allowed.includes(level.effort))
|
|
1026
|
+
: nativeReasoningLevels;
|
|
1027
|
+
const currentReasoningLevel = String(controller?.reasoningEffort ?? "").toLowerCase();
|
|
926
1028
|
return {
|
|
927
1029
|
slug: String(model.model ?? ""),
|
|
928
1030
|
displayName: String(model.displayName ?? model.model ?? ""),
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1031
|
+
currentReasoningLevel: nativeReasoningLevels.some(
|
|
1032
|
+
(level) => level.effort === currentReasoningLevel,
|
|
1033
|
+
) ? currentReasoningLevel : null,
|
|
1034
|
+
currentReasoningLevelVisible: supportedReasoningLevels.some(
|
|
1035
|
+
(level) => level.effort === currentReasoningLevel,
|
|
1036
|
+
),
|
|
1037
|
+
defaultReasoningLevel: String(model.defaultReasoningEffort ?? "").toLowerCase(),
|
|
1038
|
+
supportedReasoningLevels,
|
|
933
1039
|
};
|
|
934
1040
|
}
|
|
935
1041
|
|
|
@@ -1362,18 +1468,19 @@ export function createInjectionSource(features, {
|
|
|
1362
1468
|
}
|
|
1363
1469
|
|
|
1364
1470
|
function enhanceModelItem(item, menu, selector, index) {
|
|
1365
|
-
if (item.querySelector("[data-codex-model-slider-row]")) return;
|
|
1366
1471
|
const observedRaw = modelItemRawValue(item);
|
|
1367
1472
|
if (!observedRaw) return;
|
|
1368
|
-
if (!modelSelectorOriginalItems.has(item)) modelSelectorOriginalItems.set(item, item.innerHTML);
|
|
1369
1473
|
const observedIdentity = modelIdentity(observedRaw);
|
|
1370
1474
|
const nativeDescriptor = nativeModelDescriptorForItem(item, observedRaw);
|
|
1371
1475
|
const catalogModel = catalogModelFor(observedIdentity, selector);
|
|
1372
1476
|
const raw = catalogModel?.slug ?? nativeDescriptor?.slug ?? observedRaw;
|
|
1477
|
+
// 先刷新 dataset:React 原地复用菜单节点时,raw/排序/收藏键不能基于过期值。
|
|
1373
1478
|
item.dataset.codexModelSliderRaw = raw;
|
|
1374
1479
|
item.dataset.codexModelSliderItem = "";
|
|
1375
1480
|
item.dataset.codexModelSliderOrder = String(index);
|
|
1376
1481
|
item.dataset.codexModelSliderIdentitySource = catalogModel ? "catalog" : nativeDescriptor ? "native" : "visible";
|
|
1482
|
+
if (item.querySelector("[data-codex-model-slider-row]")) return;
|
|
1483
|
+
if (!modelSelectorOriginalItems.has(item)) modelSelectorOriginalItems.set(item, item.innerHTML);
|
|
1377
1484
|
|
|
1378
1485
|
const identity = modelIdentity(raw);
|
|
1379
1486
|
const display = modelDisplayLabel(identity, selector);
|
|
@@ -1574,6 +1681,9 @@ export function createInjectionSource(features, {
|
|
|
1574
1681
|
}
|
|
1575
1682
|
|
|
1576
1683
|
function selectNativeEffort(parentMenu, effort) {
|
|
1684
|
+
const identity = modelIdentity(nativeModelValue(parentMenu));
|
|
1685
|
+
const model = modelDefinitionFor(identity, modelSelectorFeature?.modelSelector);
|
|
1686
|
+
if (!model?.supportedReasoningLevels?.some((level) => level.effort === String(effort).toLowerCase())) return;
|
|
1577
1687
|
const controller = nativeComposerModelController();
|
|
1578
1688
|
if (typeof controller?.onSelectReasoningEffort === "function") {
|
|
1579
1689
|
controller.onSelectReasoningEffort(effort);
|
|
@@ -1649,6 +1759,24 @@ export function createInjectionSource(features, {
|
|
|
1649
1759
|
rail.setAttribute("aria-valuenow", String(index));
|
|
1650
1760
|
}
|
|
1651
1761
|
|
|
1762
|
+
function neutralizeStaleSliderVisual(rail) {
|
|
1763
|
+
for (const selector of [
|
|
1764
|
+
"[data-codex-model-slider-native-range]",
|
|
1765
|
+
"[data-codex-model-slider-native-thumb]",
|
|
1766
|
+
"[data-codex-model-slider-range]",
|
|
1767
|
+
"[data-codex-model-slider-thumb]",
|
|
1768
|
+
]) {
|
|
1769
|
+
rail.querySelectorAll(selector).forEach((element) => {
|
|
1770
|
+
element.style.setProperty("visibility", "hidden");
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
rail.querySelectorAll("[data-codex-model-slider-tick]").forEach((tick) => {
|
|
1774
|
+
tick.removeAttribute("data-selected");
|
|
1775
|
+
});
|
|
1776
|
+
rail.removeAttribute("aria-valuenow");
|
|
1777
|
+
rail.style.removeProperty("--codex-model-slider-progress");
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1652
1780
|
function nativeParticleSeed(index, salt) {
|
|
1653
1781
|
const value = Math.sin((index + 1) * 12.9898 + salt * 78.233) * 43758.5453;
|
|
1654
1782
|
return value - Math.floor(value);
|
|
@@ -1775,7 +1903,8 @@ export function createInjectionSource(features, {
|
|
|
1775
1903
|
|
|
1776
1904
|
function createGenericModelSlider(parentMenu, selector, identity, model, selectedEffort) {
|
|
1777
1905
|
const efforts = model.supportedReasoningLevels.map((level) => level.effort);
|
|
1778
|
-
const selectedIndex =
|
|
1906
|
+
const selectedIndex = efforts.indexOf(selectedEffort);
|
|
1907
|
+
const selectedEffortVisible = selectedIndex >= 0;
|
|
1779
1908
|
const classes = Object.fromEntries([
|
|
1780
1909
|
"Menu",
|
|
1781
1910
|
"ViewToggle",
|
|
@@ -1873,7 +2002,20 @@ export function createInjectionSource(features, {
|
|
|
1873
2002
|
rail.setAttribute("aria-label", locale === "zh-CN" ? "推理强度" : "Reasoning effort");
|
|
1874
2003
|
rail.setAttribute("aria-valuemin", "0");
|
|
1875
2004
|
rail.setAttribute("aria-valuemax", String(efforts.length - 1));
|
|
1876
|
-
rail.setAttribute("aria-valuetext",
|
|
2005
|
+
rail.setAttribute("aria-valuetext", selectedEffortVisible
|
|
2006
|
+
? effortLabel(selectedEffort)
|
|
2007
|
+
: (locale === "zh-CN" ? "当前档位不可用" : "Current effort unavailable"));
|
|
2008
|
+
rail.toggleAttribute("data-stale-reasoning-effort", !selectedEffortVisible);
|
|
2009
|
+
if (!selectedEffortVisible) {
|
|
2010
|
+
rail.setAttribute("aria-disabled", "true");
|
|
2011
|
+
rail.removeAttribute("tabindex");
|
|
2012
|
+
neutralizeStaleSliderVisual(rail);
|
|
2013
|
+
const staleView = document.createElement("div");
|
|
2014
|
+
staleView.className = classes.SimpleView;
|
|
2015
|
+
staleView.append(nativeSlider ?? rail);
|
|
2016
|
+
shell.append(viewControls, staleView);
|
|
2017
|
+
return shell;
|
|
2018
|
+
}
|
|
1877
2019
|
if (!nativeSlider) {
|
|
1878
2020
|
const track = document.createElement("div");
|
|
1879
2021
|
track.setAttribute("data-codex-model-slider-track", "");
|
|
@@ -1969,7 +2111,7 @@ export function createInjectionSource(features, {
|
|
|
1969
2111
|
// 控制器推导的任务内实际 effort 优先,避免子任务/主任务切换后触发器属性滞后;
|
|
1970
2112
|
// 触发器属性仅在控制器无法解析时作为实时回退,静态目录默认值最后兜底。
|
|
1971
2113
|
const nativeModel = nativeCatalogModelFor(identity);
|
|
1972
|
-
const selectedEffort = nativeModel?.
|
|
2114
|
+
const selectedEffort = nativeModel?.currentReasoningLevel
|
|
1973
2115
|
?? document.querySelector("[data-codex-intelligence-trigger]")
|
|
1974
2116
|
?.getAttribute("data-selected-reasoning-effort")
|
|
1975
2117
|
?? model.defaultReasoningLevel
|
|
@@ -2077,7 +2219,6 @@ export function createInjectionSource(features, {
|
|
|
2077
2219
|
function ensureModelSelector() {
|
|
2078
2220
|
const selector = modelSelectorFeature?.modelSelector;
|
|
2079
2221
|
if (!selector) return;
|
|
2080
|
-
captureNativeFastIcon();
|
|
2081
2222
|
pruneModelSubmenuPositionObservers();
|
|
2082
2223
|
if (!modelSelectorStyle.isConnected) document.head?.append(modelSelectorStyle);
|
|
2083
2224
|
enhanceComposerModelTrigger(selector);
|
|
@@ -2089,11 +2230,17 @@ export function createInjectionSource(features, {
|
|
|
2089
2230
|
returnToNativeModelSlider();
|
|
2090
2231
|
}, 80);
|
|
2091
2232
|
}
|
|
2092
|
-
|
|
2233
|
+
} else {
|
|
2234
|
+
enhanceNativeModelPicker(parentMenu, selector);
|
|
2235
|
+
const submenu = nativeModelSubmenu(parentMenu);
|
|
2236
|
+
if (submenu) enhanceNativeModelSubmenu(submenu, selector);
|
|
2237
|
+
}
|
|
2238
|
+
try {
|
|
2239
|
+
// 图标工作与菜单增强解耦:放在最后并隔离异常,任何失败都不影响标签与菜单。
|
|
2240
|
+
captureNativeFastIcon();
|
|
2241
|
+
} catch (error) {
|
|
2242
|
+
console.warn("[codex-personal] fast icon capture failed", error);
|
|
2093
2243
|
}
|
|
2094
|
-
enhanceNativeModelPicker(parentMenu, selector);
|
|
2095
|
-
const submenu = nativeModelSubmenu(parentMenu);
|
|
2096
|
-
if (submenu) enhanceNativeModelSubmenu(submenu, selector);
|
|
2097
2244
|
}
|
|
2098
2245
|
|
|
2099
2246
|
function restoreModelSelectorEnhancements() {
|
|
@@ -2335,9 +2482,11 @@ export function createInjectionSource(features, {
|
|
|
2335
2482
|
}
|
|
2336
2483
|
if (next === currentThread) return;
|
|
2337
2484
|
currentThread = next;
|
|
2485
|
+
stopToolbarReadiness();
|
|
2338
2486
|
savedComposerRange = null;
|
|
2339
2487
|
savedComposerThread = null;
|
|
2340
2488
|
hidePinnedSurfaces();
|
|
2489
|
+
queueEnsure();
|
|
2341
2490
|
}
|
|
2342
2491
|
|
|
2343
2492
|
function currentComposer() {
|
|
@@ -2399,16 +2548,23 @@ export function createInjectionSource(features, {
|
|
|
2399
2548
|
]);
|
|
2400
2549
|
for (const element of selected) {
|
|
2401
2550
|
if (element.hasAttribute(entryMarker) || alreadySuppressed.has(element)) continue;
|
|
2402
|
-
|
|
2551
|
+
const selection = {
|
|
2403
2552
|
element,
|
|
2404
2553
|
ariaCurrent: element.getAttribute("aria-current"),
|
|
2405
2554
|
threadSelected: element.getAttribute("data-app-action-sidebar-thread-selected"),
|
|
2555
|
+
hadPrimaryGhost: element.classList.contains("bg-primary-ghost-hover"),
|
|
2556
|
+
hadHoverPrimaryGhost: element.classList.contains("hover:bg-primary-ghost-hover"),
|
|
2406
2557
|
hadActiveClass: element.classList.contains("bg-token-list-hover-background"),
|
|
2407
2558
|
hadHoverClass: element.classList.contains("hover:bg-token-list-hover-background"),
|
|
2408
|
-
}
|
|
2559
|
+
};
|
|
2560
|
+
suppressedSelections.push(selection);
|
|
2409
2561
|
element.removeAttribute("aria-current");
|
|
2410
2562
|
element.removeAttribute("data-app-action-sidebar-thread-selected");
|
|
2563
|
+
// 原生选中态(灰底)现在由 bg-primary-ghost-hover 承载,需一并移除;
|
|
2564
|
+
// 旧 token 类仍保留处理,以便兼容旧主题。
|
|
2565
|
+
element.classList.remove("bg-primary-ghost-hover");
|
|
2411
2566
|
element.classList.remove("bg-token-list-hover-background");
|
|
2567
|
+
if (!selection.hadHoverPrimaryGhost) element.classList.add("hover:bg-primary-ghost-hover");
|
|
2412
2568
|
element.classList.add("hover:bg-token-list-hover-background");
|
|
2413
2569
|
}
|
|
2414
2570
|
}
|
|
@@ -2421,6 +2577,8 @@ export function createInjectionSource(features, {
|
|
|
2421
2577
|
if (selection.threadSelected !== null) {
|
|
2422
2578
|
element.setAttribute("data-app-action-sidebar-thread-selected", selection.threadSelected);
|
|
2423
2579
|
}
|
|
2580
|
+
element.classList.toggle("bg-primary-ghost-hover", Boolean(selection.hadPrimaryGhost));
|
|
2581
|
+
element.classList.toggle("hover:bg-primary-ghost-hover", Boolean(selection.hadHoverPrimaryGhost));
|
|
2424
2582
|
element.classList.toggle("bg-token-list-hover-background", selection.hadActiveClass);
|
|
2425
2583
|
element.classList.toggle("hover:bg-token-list-hover-background", selection.hadHoverClass);
|
|
2426
2584
|
}
|
|
@@ -2432,6 +2590,7 @@ export function createInjectionSource(features, {
|
|
|
2432
2590
|
const entry = findEntry(feature.id);
|
|
2433
2591
|
if (!entry) continue;
|
|
2434
2592
|
const selected = feature.id === activePageFeatureId;
|
|
2593
|
+
entry.classList.toggle("bg-primary-ghost-hover", selected);
|
|
2435
2594
|
entry.classList.toggle("bg-token-list-hover-background", selected);
|
|
2436
2595
|
entry.classList.toggle("hover:bg-token-list-hover-background", !selected);
|
|
2437
2596
|
if (selected) entry.setAttribute("aria-current", "page");
|
|
@@ -2528,24 +2687,23 @@ export function createInjectionSource(features, {
|
|
|
2528
2687
|
}
|
|
2529
2688
|
}
|
|
2530
2689
|
|
|
2531
|
-
function threadScrollContent() {
|
|
2532
|
-
const scrollContainer = mainContentViewport()?.querySelector(".thread-scroll-container");
|
|
2533
|
-
return scrollContainer?.firstElementChild instanceof HTMLElement
|
|
2534
|
-
? scrollContainer.firstElementChild
|
|
2535
|
-
: null;
|
|
2536
|
-
}
|
|
2537
|
-
|
|
2538
2690
|
function restoreThreadContentShift() {
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2691
|
+
for (const { element, shift, translate } of shiftedConversationOwners) {
|
|
2692
|
+
if (!element.isConnected) continue;
|
|
2693
|
+
if (shift.value) {
|
|
2694
|
+
element.style.setProperty(
|
|
2695
|
+
"--thread-wide-block-inline-shift",
|
|
2696
|
+
shift.value,
|
|
2697
|
+
shift.priority,
|
|
2698
|
+
);
|
|
2699
|
+
} else {
|
|
2700
|
+
element.style.removeProperty("--thread-wide-block-inline-shift");
|
|
2701
|
+
}
|
|
2702
|
+
if (translate.value) element.style.setProperty("translate", translate.value, translate.priority);
|
|
2703
|
+
else element.style.removeProperty("translate");
|
|
2545
2704
|
element.removeAttribute("data-codex-personal-summary-shift");
|
|
2546
2705
|
}
|
|
2547
|
-
|
|
2548
|
-
shiftedThreadOriginal = null;
|
|
2706
|
+
shiftedConversationOwners = [];
|
|
2549
2707
|
}
|
|
2550
2708
|
|
|
2551
2709
|
function applyThreadContentShift(value) {
|
|
@@ -2553,45 +2711,40 @@ export function createInjectionSource(features, {
|
|
|
2553
2711
|
restoreThreadContentShift();
|
|
2554
2712
|
return;
|
|
2555
2713
|
}
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
if (!
|
|
2714
|
+
const viewport = mainContentViewport();
|
|
2715
|
+
const owners = viewport
|
|
2716
|
+
? Array.from(viewport.querySelectorAll('[style*="--thread-wide-block-inline-shift"]'))
|
|
2717
|
+
: [];
|
|
2718
|
+
if (!owners.length) {
|
|
2561
2719
|
restoreThreadContentShift();
|
|
2562
2720
|
return;
|
|
2563
2721
|
}
|
|
2564
|
-
if (
|
|
2722
|
+
if (
|
|
2723
|
+
shiftedConversationOwners.length !== owners.length
|
|
2724
|
+
|| owners.some((owner, index) => shiftedConversationOwners[index]?.element !== owner)
|
|
2725
|
+
) {
|
|
2565
2726
|
restoreThreadContentShift();
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
priority: content.style.getPropertyPriority("position"),
|
|
2572
|
-
},
|
|
2573
|
-
left: {
|
|
2574
|
-
value: content.style.getPropertyValue("left"),
|
|
2575
|
-
priority: content.style.getPropertyPriority("left"),
|
|
2727
|
+
shiftedConversationOwners = owners.map((owner) => ({
|
|
2728
|
+
element: owner,
|
|
2729
|
+
shift: {
|
|
2730
|
+
value: owner.style.getPropertyValue("--thread-wide-block-inline-shift"),
|
|
2731
|
+
priority: owner.style.getPropertyPriority("--thread-wide-block-inline-shift"),
|
|
2576
2732
|
},
|
|
2577
|
-
|
|
2578
|
-
value:
|
|
2579
|
-
priority:
|
|
2733
|
+
translate: {
|
|
2734
|
+
value: owner.style.getPropertyValue("translate"),
|
|
2735
|
+
priority: owner.style.getPropertyPriority("translate"),
|
|
2580
2736
|
},
|
|
2581
|
-
};
|
|
2737
|
+
}));
|
|
2738
|
+
}
|
|
2739
|
+
for (const owner of owners) {
|
|
2740
|
+
owner.style.setProperty(
|
|
2741
|
+
"--thread-wide-block-inline-shift",
|
|
2742
|
+
`${Number(value)}px`,
|
|
2743
|
+
"important",
|
|
2744
|
+
);
|
|
2745
|
+
owner.style.setProperty("translate", `${Number(value)}px 0`, "important");
|
|
2746
|
+
owner.setAttribute("data-codex-personal-summary-shift", String(value));
|
|
2582
2747
|
}
|
|
2583
|
-
const duration = reducedMotion() ? 0 : 300;
|
|
2584
|
-
content.style.setProperty("transition", duration
|
|
2585
|
-
? "left 300ms cubic-bezier(.2,.8,.2,1)"
|
|
2586
|
-
: "none", "important");
|
|
2587
|
-
content.style.setProperty("position", "relative", "important");
|
|
2588
|
-
content.style.setProperty("left", `${value}px`, "important");
|
|
2589
|
-
content.setAttribute("data-codex-personal-summary-shift", String(value));
|
|
2590
|
-
if (duration) window.setTimeout(() => {
|
|
2591
|
-
if (content.getAttribute("data-codex-personal-summary-shift") !== String(value)) return;
|
|
2592
|
-
content.style.setProperty("transition", "none", "important");
|
|
2593
|
-
content.style.setProperty("left", `${value}px`, "important");
|
|
2594
|
-
}, duration + 50);
|
|
2595
2748
|
}
|
|
2596
2749
|
|
|
2597
2750
|
function setSummarySurfaceVisible(surface, visible, mode) {
|
|
@@ -2696,7 +2849,7 @@ export function createInjectionSource(features, {
|
|
|
2696
2849
|
surface.setAttribute("data-codex-personal-summary-presentation", displayMode);
|
|
2697
2850
|
setSummarySurfaceVisible(surface, true, displayMode);
|
|
2698
2851
|
|
|
2699
|
-
const shift = displayMode
|
|
2852
|
+
const shift = displayMode !== "overlay"
|
|
2700
2853
|
? -(layout.panelWidth + layout.panelInset) / 2
|
|
2701
2854
|
: 0;
|
|
2702
2855
|
applyThreadContentShift(shift);
|
|
@@ -3341,8 +3494,12 @@ export function createInjectionSource(features, {
|
|
|
3341
3494
|
const detail = feature?.detailTabs?.find((candidate) => candidate.id === detailId);
|
|
3342
3495
|
if (!feature || !detail) throw new Error("The requested detail Tab is not registered");
|
|
3343
3496
|
const result = await openNativeDetailTab(feature, detail);
|
|
3344
|
-
if (!result)
|
|
3345
|
-
|
|
3497
|
+
if (!result) {
|
|
3498
|
+
const error = nativeTabCapabilityError
|
|
3499
|
+
?? new Error("Codex native right-panel tabs are unavailable");
|
|
3500
|
+
error.code = "NATIVE_TAB_UNAVAILABLE";
|
|
3501
|
+
throw error;
|
|
3502
|
+
}
|
|
3346
3503
|
return result;
|
|
3347
3504
|
}
|
|
3348
3505
|
|
|
@@ -3457,13 +3614,20 @@ export function createInjectionSource(features, {
|
|
|
3457
3614
|
}
|
|
3458
3615
|
|
|
3459
3616
|
function ensureToolbarEntries() {
|
|
3460
|
-
if (!toolbarFeatures.length)
|
|
3617
|
+
if (!toolbarFeatures.length) {
|
|
3618
|
+
stopToolbarReadiness();
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3461
3621
|
const temporaryChatButton = nativeTemporaryChatButton();
|
|
3462
3622
|
const summaryButton = nativeSummaryButton();
|
|
3463
3623
|
const bottomPanelButton = nativeBottomPanelButton();
|
|
3464
3624
|
const sidePanelButton = nativeSidePanelButton();
|
|
3465
3625
|
const anchorButton = temporaryChatButton ?? summaryButton ?? bottomPanelButton ?? sidePanelButton;
|
|
3466
|
-
if (!anchorButton)
|
|
3626
|
+
if (!anchorButton) {
|
|
3627
|
+
const candidate = structuralToolbarAnchor();
|
|
3628
|
+
if (candidate) ensureToolbarReadiness(candidate);
|
|
3629
|
+
return;
|
|
3630
|
+
}
|
|
3467
3631
|
const anchorRoot = toolbarControlRoot(anchorButton);
|
|
3468
3632
|
// The bottom/side-panel toggles sit in narrow fixed containers that
|
|
3469
3633
|
// travel with the native panels. Entries anchored to them must live in
|
|
@@ -3474,7 +3638,10 @@ export function createInjectionSource(features, {
|
|
|
3474
3638
|
const useFallbackAnchor = anchorButton === sidePanelButton || anchorButton === bottomPanelButton;
|
|
3475
3639
|
const summaryGroup = useFallbackAnchor ? null : nativeSummaryToolbarGroup(anchorButton);
|
|
3476
3640
|
let targetGroup = summaryGroup ?? ensureFallbackSummaryToolbarGroup(anchorButton);
|
|
3477
|
-
if (!targetGroup)
|
|
3641
|
+
if (!targetGroup) {
|
|
3642
|
+
ensureToolbarReadiness(anchorButton);
|
|
3643
|
+
return;
|
|
3644
|
+
}
|
|
3478
3645
|
let cursor = useFallbackAnchor ? null : anchorRoot;
|
|
3479
3646
|
for (const feature of toolbarFeatures) {
|
|
3480
3647
|
const entries = Array.from(document.querySelectorAll(`[${entryMarker}="${feature.id}"]`));
|
|
@@ -3510,6 +3677,11 @@ export function createInjectionSource(features, {
|
|
|
3510
3677
|
if (!used) group.remove();
|
|
3511
3678
|
});
|
|
3512
3679
|
}
|
|
3680
|
+
if (toolbarFeatures.every((feature) => findEntry(feature.id)?.isConnected)) {
|
|
3681
|
+
stopToolbarReadiness();
|
|
3682
|
+
} else {
|
|
3683
|
+
ensureToolbarReadiness(anchorButton);
|
|
3684
|
+
}
|
|
3513
3685
|
}
|
|
3514
3686
|
|
|
3515
3687
|
function ensureEntries() {
|
|
@@ -3568,6 +3740,13 @@ export function createInjectionSource(features, {
|
|
|
3568
3740
|
}, record.origin);
|
|
3569
3741
|
}
|
|
3570
3742
|
|
|
3743
|
+
function hostActionFailure(error) {
|
|
3744
|
+
return {
|
|
3745
|
+
code: typeof error?.code === "string" ? error.code : "HOST_ACTION_FAILED",
|
|
3746
|
+
message: error?.message ?? "Host action failed",
|
|
3747
|
+
};
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3571
3750
|
async function handleSurfaceHostAction(record, message) {
|
|
3572
3751
|
const feature = featureById.get(record.featureId);
|
|
3573
3752
|
const { requestId, action, payload = {} } = message;
|
|
@@ -3631,7 +3810,7 @@ export function createInjectionSource(features, {
|
|
|
3631
3810
|
} catch (error) {
|
|
3632
3811
|
respondToSurface(record, requestId, {
|
|
3633
3812
|
ok: false,
|
|
3634
|
-
error:
|
|
3813
|
+
error: hostActionFailure(error),
|
|
3635
3814
|
});
|
|
3636
3815
|
}
|
|
3637
3816
|
}
|
|
@@ -3799,9 +3978,9 @@ export function createInjectionSource(features, {
|
|
|
3799
3978
|
if (ensureFrame != null) cancelAnimationFrame(ensureFrame);
|
|
3800
3979
|
if (ensureTimer != null) clearTimeout(ensureTimer);
|
|
3801
3980
|
for (const observer of domObservers) observer.disconnect();
|
|
3981
|
+
stopToolbarReadiness();
|
|
3802
3982
|
mainContentObserver?.observer.disconnect();
|
|
3803
3983
|
restoreThreadContentShift();
|
|
3804
|
-
threadShiftStyle.remove();
|
|
3805
3984
|
for (const root of document.querySelectorAll("[data-codex-personal-toolbar-entry]")) root.remove();
|
|
3806
3985
|
for (const group of document.querySelectorAll("[data-codex-personal-summary-toolbar-group]")) group.remove();
|
|
3807
3986
|
for (const surface of pageSurfaces.values()) surface.remove();
|
|
@@ -3832,7 +4011,15 @@ export function createInjectionSource(features, {
|
|
|
3832
4011
|
detailTabs: feature.detailTabs ?? [],
|
|
3833
4012
|
hostActions: feature.hostActions ?? [],
|
|
3834
4013
|
modelSelector: feature.modelSelector ?? null,
|
|
3835
|
-
pageScript: feature.pageScript
|
|
4014
|
+
pageScript: feature.pageScript ? {
|
|
4015
|
+
...feature.pageScript,
|
|
4016
|
+
config: {
|
|
4017
|
+
serviceOrigin: pageScriptServiceOrigin(feature),
|
|
4018
|
+
bindingName,
|
|
4019
|
+
bindingToken,
|
|
4020
|
+
featureId: feature.id,
|
|
4021
|
+
},
|
|
4022
|
+
} : null,
|
|
3836
4023
|
}));
|
|
3837
4024
|
return `(${install.toString()})(${JSON.stringify({
|
|
3838
4025
|
version: RUNTIME_VERSION,
|
|
@@ -3854,7 +4041,9 @@ export function createRuntimeReadyContract({
|
|
|
3854
4041
|
runtimeSessionId,
|
|
3855
4042
|
}) {
|
|
3856
4043
|
const discovered = Boolean(renderer.discovered);
|
|
4044
|
+
const discoveredTargets = Math.max(0, Number(renderer.discoveredTargets) || 0);
|
|
3857
4045
|
const injectedTargets = Math.max(0, Number(renderer.injectedTargets) || 0);
|
|
4046
|
+
const targetFailures = Array.isArray(renderer.targetFailures) ? renderer.targetFailures : [];
|
|
3858
4047
|
const active = Boolean(renderer.active) && discovered && injectedTargets > 0;
|
|
3859
4048
|
return {
|
|
3860
4049
|
pid,
|
|
@@ -3872,8 +4061,11 @@ export function createRuntimeReadyContract({
|
|
|
3872
4061
|
})),
|
|
3873
4062
|
renderer: {
|
|
3874
4063
|
discovered,
|
|
4064
|
+
discoveredTargets,
|
|
3875
4065
|
injectedTargets,
|
|
3876
4066
|
active,
|
|
4067
|
+
healthy: active && targetFailures.length === 0 && injectedTargets === discoveredTargets,
|
|
4068
|
+
targetFailures,
|
|
3877
4069
|
runtimeSessionId: renderer.runtimeSessionId ?? runtimeSessionId,
|
|
3878
4070
|
},
|
|
3879
4071
|
};
|
|
@@ -4074,6 +4266,8 @@ export class CodexRuntime {
|
|
|
4074
4266
|
this.onStatusChange = onStatusChange;
|
|
4075
4267
|
this.onManagedCodexPidChange = onManagedCodexPidChange;
|
|
4076
4268
|
this.injectedTargets = new Set();
|
|
4269
|
+
this.rendererTargetIds = new Set();
|
|
4270
|
+
this.targetFailures = new Map();
|
|
4077
4271
|
this.clients = new Map();
|
|
4078
4272
|
this.scriptIds = new Map();
|
|
4079
4273
|
this.bindingTokens = new Map();
|
|
@@ -4127,10 +4321,21 @@ export class CodexRuntime {
|
|
|
4127
4321
|
|
|
4128
4322
|
getRendererStatus() {
|
|
4129
4323
|
const injectedTargets = this.injectedTargets.size;
|
|
4324
|
+
const discoveredTargets = this.rendererTargetIds.size;
|
|
4325
|
+
const targetFailures = Array.from(this.targetFailures, ([targetId, failure]) => ({
|
|
4326
|
+
targetId,
|
|
4327
|
+
attempts: failure.attempts,
|
|
4328
|
+
state: "retrying",
|
|
4329
|
+
lastFailure: failure.lastFailure,
|
|
4330
|
+
})).sort((left, right) => left.targetId.localeCompare(right.targetId));
|
|
4331
|
+
const active = !this.stopping && this.rendererDiscovered && injectedTargets > 0;
|
|
4130
4332
|
return {
|
|
4131
4333
|
discovered: this.rendererDiscovered,
|
|
4334
|
+
discoveredTargets,
|
|
4132
4335
|
injectedTargets,
|
|
4133
|
-
active
|
|
4336
|
+
active,
|
|
4337
|
+
healthy: active && targetFailures.length === 0 && injectedTargets === discoveredTargets,
|
|
4338
|
+
targetFailures,
|
|
4134
4339
|
runtimeSessionId: this.runtimeSessionId,
|
|
4135
4340
|
};
|
|
4136
4341
|
}
|
|
@@ -4141,8 +4346,11 @@ export class CodexRuntime {
|
|
|
4141
4346
|
if (
|
|
4142
4347
|
previous
|
|
4143
4348
|
&& previous.discovered === status.discovered
|
|
4349
|
+
&& previous.discoveredTargets === status.discoveredTargets
|
|
4144
4350
|
&& previous.injectedTargets === status.injectedTargets
|
|
4145
4351
|
&& previous.active === status.active
|
|
4352
|
+
&& previous.healthy === status.healthy
|
|
4353
|
+
&& JSON.stringify(previous.targetFailures) === JSON.stringify(status.targetFailures)
|
|
4146
4354
|
&& previous.runtimeSessionId === status.runtimeSessionId
|
|
4147
4355
|
) return;
|
|
4148
4356
|
this.lastRendererStatus = status;
|
|
@@ -4186,6 +4394,8 @@ export class CodexRuntime {
|
|
|
4186
4394
|
this.bindingTokens.clear();
|
|
4187
4395
|
this.hostActionChains.clear();
|
|
4188
4396
|
this.injectedTargets.clear();
|
|
4397
|
+
this.rendererTargetIds.clear();
|
|
4398
|
+
this.targetFailures.clear();
|
|
4189
4399
|
this.rendererDiscovered = false;
|
|
4190
4400
|
this.browserClient?.socket.close();
|
|
4191
4401
|
this.browserClient = null;
|
|
@@ -4549,6 +4759,10 @@ export class CodexRuntime {
|
|
|
4549
4759
|
});
|
|
4550
4760
|
return;
|
|
4551
4761
|
}
|
|
4762
|
+
if (request.action === "import-generated-image") {
|
|
4763
|
+
await this.importGeneratedImage(feature, request.payload);
|
|
4764
|
+
return;
|
|
4765
|
+
}
|
|
4552
4766
|
try {
|
|
4553
4767
|
if (request.action !== "attach-file") throw new Error("Unsupported native Host action");
|
|
4554
4768
|
const result = await this.attachFileToComposer(client, request.payload?.path, request.requestId);
|
|
@@ -4561,6 +4775,22 @@ export class CodexRuntime {
|
|
|
4561
4775
|
}
|
|
4562
4776
|
}
|
|
4563
4777
|
|
|
4778
|
+
async importGeneratedImage(feature, payload) {
|
|
4779
|
+
const origin = pageScriptServiceOrigin(feature);
|
|
4780
|
+
if (!origin) throw new Error("Generated image import service is unavailable");
|
|
4781
|
+
const response = await this.fetchImpl(new URL("/api/import-generated-image", origin), {
|
|
4782
|
+
method: "POST",
|
|
4783
|
+
headers: {
|
|
4784
|
+
"Content-Type": "application/json",
|
|
4785
|
+
Origin: CODEX_APP_ORIGIN,
|
|
4786
|
+
},
|
|
4787
|
+
body: JSON.stringify(payload),
|
|
4788
|
+
});
|
|
4789
|
+
if (!response?.ok) {
|
|
4790
|
+
throw new Error(`Generated image import returned HTTP ${response?.status ?? "error"}`);
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4564
4794
|
async refreshTargets() {
|
|
4565
4795
|
let response;
|
|
4566
4796
|
try {
|
|
@@ -4587,8 +4817,14 @@ export class CodexRuntime {
|
|
|
4587
4817
|
.filter((target) => target.type === "page" && target.url === "app://-/index.html")
|
|
4588
4818
|
.map((target) => target.id),
|
|
4589
4819
|
);
|
|
4820
|
+
this.rendererTargetIds = liveRendererIds;
|
|
4590
4821
|
let removedTarget = false;
|
|
4591
|
-
|
|
4822
|
+
const trackedTargetIds = new Set([
|
|
4823
|
+
...this.injectedTargets,
|
|
4824
|
+
...this.clients.keys(),
|
|
4825
|
+
...this.targetFailures.keys(),
|
|
4826
|
+
]);
|
|
4827
|
+
for (const id of trackedTargetIds) {
|
|
4592
4828
|
if (liveRendererIds.has(id)) continue;
|
|
4593
4829
|
removedTarget = true;
|
|
4594
4830
|
this.clients.get(id)?.socket.close();
|
|
@@ -4597,6 +4833,7 @@ export class CodexRuntime {
|
|
|
4597
4833
|
this.bindingTokens.delete(id);
|
|
4598
4834
|
this.hostActionChains.delete(id);
|
|
4599
4835
|
this.injectedTargets.delete(id);
|
|
4836
|
+
this.targetFailures.delete(id);
|
|
4600
4837
|
}
|
|
4601
4838
|
const rendererDiscovered = liveRendererIds.size > 0;
|
|
4602
4839
|
const rendererChanged = rendererDiscovered !== this.rendererDiscovered;
|
|
@@ -4607,8 +4844,12 @@ export class CodexRuntime {
|
|
|
4607
4844
|
if (target.type !== "page" || target.url !== "app://-/index.html" || this.injectedTargets.has(target.id)) {
|
|
4608
4845
|
continue;
|
|
4609
4846
|
}
|
|
4610
|
-
const
|
|
4847
|
+
const previousFailure = this.targetFailures.get(target.id);
|
|
4848
|
+
if (previousFailure && Date.now() < previousFailure.nextRetryAt) continue;
|
|
4849
|
+
let client;
|
|
4850
|
+
let scriptIdentifier = null;
|
|
4611
4851
|
try {
|
|
4852
|
+
client = await this.connectClient(target.webSocketDebuggerUrl);
|
|
4612
4853
|
await Promise.all([
|
|
4613
4854
|
client.send("Page.enable"),
|
|
4614
4855
|
client.send("Runtime.enable"),
|
|
@@ -4630,6 +4871,7 @@ export class CodexRuntime {
|
|
|
4630
4871
|
const script = await client.send("Page.addScriptToEvaluateOnNewDocument", {
|
|
4631
4872
|
source: createDocumentBootstrapSource(source),
|
|
4632
4873
|
});
|
|
4874
|
+
scriptIdentifier = script.identifier;
|
|
4633
4875
|
await waitForExpression(
|
|
4634
4876
|
client,
|
|
4635
4877
|
`document.readyState === "interactive" || document.readyState === "complete"`,
|
|
@@ -4642,15 +4884,33 @@ export class CodexRuntime {
|
|
|
4642
4884
|
+ ` && window.__codexPersonalRuntime?.sessionId === ${JSON.stringify(this.runtimeSessionId)}`,
|
|
4643
4885
|
);
|
|
4644
4886
|
this.clients.set(target.id, client);
|
|
4645
|
-
this.scriptIds.set(target.id,
|
|
4887
|
+
this.scriptIds.set(target.id, scriptIdentifier);
|
|
4646
4888
|
this.injectedTargets.add(target.id);
|
|
4889
|
+
this.targetFailures.delete(target.id);
|
|
4647
4890
|
this.notifyStatusChange();
|
|
4648
4891
|
} catch (error) {
|
|
4649
4892
|
this.bindingTokens.delete(target.id);
|
|
4650
|
-
client
|
|
4651
|
-
|
|
4893
|
+
if (client) {
|
|
4894
|
+
if (scriptIdentifier) {
|
|
4895
|
+
await client.send("Page.removeScriptToEvaluateOnNewDocument", {
|
|
4896
|
+
identifier: scriptIdentifier,
|
|
4897
|
+
}).catch(() => {});
|
|
4898
|
+
}
|
|
4899
|
+
await client.send("Runtime.removeBinding", { name: HOST_BINDING_NAME }).catch(() => {});
|
|
4900
|
+
client.socket.close();
|
|
4901
|
+
}
|
|
4902
|
+
const attempts = (previousFailure?.attempts ?? 0) + 1;
|
|
4903
|
+
const retryDelay = Math.min(5_000, 250 * (2 ** Math.min(attempts - 1, 5)));
|
|
4904
|
+
this.targetFailures.set(target.id, {
|
|
4905
|
+
attempts,
|
|
4906
|
+
lastFailure: error.message,
|
|
4907
|
+
nextRetryAt: Date.now() + retryDelay,
|
|
4908
|
+
});
|
|
4909
|
+
this.logger.warn?.(`Renderer injection failed for ${target.id}; retrying`, error.message);
|
|
4910
|
+
this.notifyStatusChange();
|
|
4652
4911
|
}
|
|
4653
4912
|
}
|
|
4913
|
+
this.notifyStatusChange();
|
|
4654
4914
|
}
|
|
4655
4915
|
|
|
4656
4916
|
async stop() {
|
|
@@ -4680,6 +4940,8 @@ export class CodexRuntime {
|
|
|
4680
4940
|
this.bindingTokens.clear();
|
|
4681
4941
|
this.hostActionChains.clear();
|
|
4682
4942
|
this.injectedTargets.clear();
|
|
4943
|
+
this.rendererTargetIds.clear();
|
|
4944
|
+
this.targetFailures.clear();
|
|
4683
4945
|
this.rendererDiscovered = false;
|
|
4684
4946
|
this.notifyStatusChange();
|
|
4685
4947
|
this.monitorPromise = null;
|