minecodex 2.0.51 → 2.0.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/features/composer-expand/src/injection-source.mjs +3 -3
  2. package/features/composer-expand/src/native.mjs +23 -1
  3. package/features/file-navigation/src/adapters/codex/modules.mjs +23 -2
  4. package/features/file-navigation/src/injection-source.mjs +3 -3
  5. package/features/images/src/adapters/codex/native-image-modules.mjs +11 -4
  6. package/features/images/src/adapters/codex/rspack-modules.mjs +148 -0
  7. package/features/images/src/library-prompt-source.mjs +3 -2
  8. package/features/markdown-reading/src/adapters/codex/links.mjs +10 -1
  9. package/features/markdown-reading/src/adapters/codex/rich-text.mjs +12 -0
  10. package/features/markdown-reading/src/injection-source.mjs +6 -4
  11. package/features/mermaid-diagrams/src/adapters/codex/native.mjs +15 -0
  12. package/features/mermaid-diagrams/src/injection-source.mjs +6 -5
  13. package/features/model-slider/src/adapters/codex/controller.mjs +57 -2
  14. package/features/model-slider/src/frontend/install.mjs +49 -43
  15. package/features/quick-chat/src/adapters/codex/rspack.mjs +50 -0
  16. package/features/quick-chat/src/adapters/codex/shortcut.mjs +8 -1
  17. package/features/quick-chat/src/adapters/codex/sidebar.mjs +5 -1
  18. package/features/quick-chat/src/injection-source.mjs +3 -2
  19. package/features/session-status/src/session-status.mjs +9 -3
  20. package/features/sites/src/adapters/codex/renderer.mjs +2 -2
  21. package/package.json +1 -1
  22. package/packages/cli/src/control-server.mjs +4 -5
  23. package/packages/cli/src/npm-adapter.mjs +4 -4
  24. package/packages/codex-adapter/src/markdown/block-actions.mjs +4 -1
  25. package/packages/codex-adapter/src/markdown/editor-api.mjs +26 -1
  26. package/packages/codex-adapter/src/markdown/rspack.mjs +74 -0
  27. package/packages/codex-adapter/src/markdown/toc-native.mjs +4 -1
  28. package/packages/codex-adapter/src/renderer/modules.mjs +79 -0
  29. package/packages/plugin/assets/app.js +2 -2
  30. package/packages/plugin/assets/prompts.js +1 -1
@@ -16,6 +16,11 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
16
16
  const modelSelectorOriginalToggleLabels = new Map();
17
17
  const modelSelectorGenericContainers = new Map();
18
18
  const modelSelectorNativeToggleHandlers = new Map();
19
+ // 原生 CSS 模块类名:26.1029 起改用不带下划线的 Name-hash 形式。
20
+ function hasNativeClass(element, name) {
21
+ const tokens = String(element?.className ?? "").split(/\s+/);
22
+ return tokens.some((token) => token.startsWith(`_${name}_`) || token.startsWith(`${name}-`));
23
+ }
19
24
  const modelSelectorPositionObservers = new Map();
20
25
  const modelSelectorMenuSizeFrames = new Map();
21
26
  const modelSelectorOpeningFrames = new Map();
@@ -63,15 +68,15 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
63
68
  max-height: var(--codex-model-slider-menu-max-height) !important;
64
69
  overflow-y: auto;
65
70
  }
66
- [data-codex-model-slider-trigger] [class*="_ModelPickerTriggerModelLabel_"] {
71
+ [data-codex-model-slider-trigger] [class*="ModelPickerTriggerModelLabel"] {
67
72
  min-width: 0; max-width: 110px; flex-shrink: 1; overflow: hidden; text-overflow: ellipsis;
68
73
  }
69
- [data-codex-model-slider-trigger] [class*="_ModelPickerTriggerModelText_"] {
74
+ [data-codex-model-slider-trigger] [class*="ModelPickerTriggerModelText"] {
70
75
  min-width: 0; max-width: 110px; overflow: hidden; text-overflow: ellipsis;
71
76
  }
72
77
  html[data-codex-model-slider-catalog-ready]
73
78
  [data-codex-model-slider-trigger]
74
- [class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label]):has(+ [data-codex-model-slider-trigger-label]) {
79
+ [class*="ModelPickerTriggerModelText"]:not([data-codex-model-slider-trigger-label]):has(+ [data-codex-model-slider-trigger-label]) {
75
80
  display: none !important;
76
81
  }
77
82
  [data-codex-model-slider-trigger-label] {
@@ -83,26 +88,26 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
83
88
  [data-codex-model-slider-trigger]:not([data-codex-model-slider-trigger-placeholder]) [data-codex-model-slider-trigger-label] {
84
89
  max-width: none; overflow: visible; text-overflow: clip; flex-shrink: 0;
85
90
  }
86
- [data-codex-model-slider-trigger]:not([data-codex-model-slider-trigger-placeholder]) [class*="_ModelPickerTriggerModelLabel_"] {
91
+ [data-codex-model-slider-trigger]:not([data-codex-model-slider-trigger-placeholder]) [class*="ModelPickerTriggerModelLabel"] {
87
92
  max-width: none; overflow: visible; flex-shrink: 0;
88
93
  }
89
- [data-codex-model-slider-trigger]:not([data-codex-model-slider-trigger-placeholder]) [class*="_ModelPickerTriggerContent_"] {
94
+ [data-codex-model-slider-trigger]:not([data-codex-model-slider-trigger-placeholder]) [class*="ModelPickerTriggerContent"] {
90
95
  max-width: none !important;
91
96
  }
92
- [data-codex-model-slider-trigger][data-codex-model-slider-trigger-placeholder] [class*="_ModelPickerTriggerContent_"] {
97
+ [data-codex-model-slider-trigger][data-codex-model-slider-trigger-placeholder] [class*="ModelPickerTriggerContent"] {
93
98
  width: 160px; min-width: 160px !important; max-width: 160px;
94
99
  }
95
100
  /* 打开时先完成占位态,避免原生 crossfade 的宽度动画带动弹层。 */
96
- [data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] [class*="_ModelPickerTriggerContent_"] > span:has(> span > [class*="_ModelPickerTriggerPlaceholder_"]) {
101
+ [data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] [class*="ModelPickerTriggerContent"] > span:has(> span > [class*="ModelPickerTriggerPlaceholder"]) {
97
102
  width: 160px !important;
98
103
  }
99
- [data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] span:has(> [class*="_ModelPickerTriggerPlaceholder_"]) {
104
+ [data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] span:has(> [class*="ModelPickerTriggerPlaceholder"]) {
100
105
  opacity: 1 !important;
101
106
  }
102
- [data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] [class*="_ModelPickerTriggerLabel_"] {
107
+ [data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] [class*="ModelPickerTriggerLabel"] {
103
108
  visibility: hidden;
104
109
  }
105
- [data-codex-model-slider-trigger] [class*="_ModelPickerTriggerEffortLabel_"] {
110
+ [data-codex-model-slider-trigger] [class*="ModelPickerTriggerEffortLabel"] {
106
111
  display: inline-flex; align-items: center; align-self: center;
107
112
  }
108
113
  html[data-codex-model-slider-selecting-effort] [role="menu"][data-state="open"]:not(:has([data-reasoning-slider])) {
@@ -185,12 +190,13 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
185
190
  }
186
191
 
187
192
  function nativeCssModuleClass(tokenName, root = document) {
188
- const pattern = new RegExp(`^_${tokenName}_[A-Za-z0-9_-]+$`);
189
- const live = Array.from(root.querySelectorAll(`[class*="_${tokenName}_"]`))
193
+ // 26.1029 起原生类名改为 Name-hash,旧版带下划线包裹。
194
+ const pattern = new RegExp(`^(?:_${tokenName}_|${tokenName}-)[A-Za-z0-9_-]+$`);
195
+ const live = Array.from(root.querySelectorAll(`[class*="_${tokenName}_"], [class*="${tokenName}-"]`))
190
196
  .flatMap((element) => Array.from(element.classList))
191
197
  .find((name) => pattern.test(name));
192
198
  if (live) return live;
193
- const selectorPattern = new RegExp(`\\.(_${tokenName}_[A-Za-z0-9_-]+)`);
199
+ const selectorPattern = new RegExp(`\\.((?:_${tokenName}_|${tokenName}-)[A-Za-z0-9_-]+)`);
194
200
  const find = (rules) => {
195
201
  for (const rule of rules ?? []) {
196
202
  const match = String(rule.selectorText ?? "").match(selectorPattern);
@@ -296,9 +302,9 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
296
302
 
297
303
  function captureNativeFastIcon() {
298
304
  // 原生控件每次只渲染当前状态;状态切换后按控件本身更新缓存。
299
- const nativeContent = Array.from(document.querySelectorAll('[class*="_FastModeToggleContent_"]'))
305
+ const nativeContent = Array.from(document.querySelectorAll(':is([class*="_FastModeToggleContent_"], [class*="FastModeToggleContent-"])'))
300
306
  .find((content) => !content.closest("[data-codex-model-slider-generic]"));
301
- const toggle = nativeContent?.closest('[class*="_FastModeToggle_"]');
307
+ const toggle = nativeContent?.closest(':is([class*="_FastModeToggle_"], [class*="FastModeToggle-"])');
302
308
  const state = toggle?.getAttribute("data-fast-mode-enabled") === "true" ? "active" : "inactive";
303
309
  const svg = nativeContent?.querySelector("svg");
304
310
  const current = modelSelectorNativeFastIcons?.[state];
@@ -341,11 +347,11 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
341
347
  // 直接读取已就绪的图标存储(启动时由 bundle/持久化填充,运行期由
342
348
  // captureNativeFastIcon 升级为 DOM 克隆),避免再次触发捕获造成递归。
343
349
  // 优先克隆当前面板的图标;切换瞬间使用缓存时,也沿用原生图标尺寸。
344
- const nativeContents = Array.from(document.querySelectorAll('[class*="_FastModeToggleContent_"]'))
350
+ const nativeContents = Array.from(document.querySelectorAll(':is([class*="_FastModeToggleContent_"], [class*="FastModeToggleContent-"])'))
345
351
  .filter((content) => !content.closest("[data-codex-model-slider-generic]"));
346
352
  const nativeContent = nativeContents.find((content) => (
347
353
  (content.getAttribute("data-fast-mode-enabled")
348
- ?? content.closest('[class*="_FastModeToggle_"]')?.getAttribute("data-fast-mode-enabled")) === String(active)
354
+ ?? content.closest(':is([class*="_FastModeToggle_"], [class*="FastModeToggle-"])')?.getAttribute("data-fast-mode-enabled")) === String(active)
349
355
  ));
350
356
  const nativeSvg = nativeContent?.querySelector("svg") ?? nativeContents[0]?.querySelector("svg");
351
357
  const template = nativeContent?.querySelector("svg")
@@ -458,7 +464,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
458
464
 
459
465
  function nativeModelValue(menu) {
460
466
  const toggle = menu?.querySelector("[data-model-picker-view-toggle]");
461
- const toggleModelLabel = toggle?.querySelector('[class*="_ViewToggleModelLabel_"]');
467
+ const toggleModelLabel = toggle?.querySelector(':is([class*="_ViewToggleModelLabel_"], [class*="ViewToggleModelLabel-"])');
462
468
  const toggleValues = String(
463
469
  toggle?.hasAttribute("data-model-picker-view-toggle")
464
470
  ? (toggleModelLabel?.textContent ?? toggle.innerText)
@@ -1113,7 +1119,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1113
1119
  if (!record) return false;
1114
1120
  if (record.closing && !immediate) return true;
1115
1121
  record.reveal?.cancel();
1116
- const placeholder = record.trigger?.querySelector('[class*="_ModelPickerTriggerPlaceholder_"]');
1122
+ const placeholder = record.trigger?.querySelector('[class*="ModelPickerTriggerPlaceholder"]');
1117
1123
  const placeholderNode = placeholder && firstVisibleTextNode(placeholder);
1118
1124
  if (placeholderNode && record.placeholderText != null) {
1119
1125
  placeholderNode.nodeValue = record.placeholderText;
@@ -1297,7 +1303,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1297
1303
  renderEnhancedModelMenu(menu, selector);
1298
1304
 
1299
1305
  const trigger = nativeComposerTrigger();
1300
- const placeholder = trigger?.querySelector('[class*="_ModelPickerTriggerPlaceholder_"]');
1306
+ const placeholder = trigger?.querySelector('[class*="ModelPickerTriggerPlaceholder"]');
1301
1307
  const placeholderNode = placeholder && firstVisibleTextNode(placeholder);
1302
1308
  const placeholderText = placeholderNode?.nodeValue ?? null;
1303
1309
  // 保留产品行为:模型列表打开时 chip 显示 Select model。只能原地更新
@@ -1446,7 +1452,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1446
1452
  const edgeCorrection = correction * Math.max(0, edgeScale);
1447
1453
  nativeRange.style.transform = `translateX(calc(${progress - 100}% + ${edgeCorrection}px))`;
1448
1454
  nativeThumb.style.left = `calc(${progress}% + ${correction}px)`;
1449
- const particleClip = rail.querySelector('[class*="_FastParticleClip_"]');
1455
+ const particleClip = rail.querySelector(':is([class*="_FastParticleClip_"], [class*="FastParticleClip-"])');
1450
1456
  if (particleClip) particleClip.style.clipPath = `inset(0 calc(${100 - progress}% - ${edgeCorrection}px) 0 0)`;
1451
1457
  }
1452
1458
  rail.style.setProperty("--codex-model-slider-progress", `${progress}%`);
@@ -1462,7 +1468,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1462
1468
  rail?.setAttribute("data-max", value);
1463
1469
  effortText?.setAttribute("data-maximum", value);
1464
1470
  fastButton?.setAttribute("data-max-power-selection", value);
1465
- const maxEffects = rail?.querySelector('[class*="_MaxEffects_"]');
1471
+ const maxEffects = rail?.querySelector(':is([class*="_MaxEffects_"], [class*="MaxEffects-"])');
1466
1472
  if (maxEffects) {
1467
1473
  maxEffects.style.opacity = maximum ? "1" : "0";
1468
1474
  if (maximum) maxEffects.style.transform = "translateX(0)";
@@ -1493,9 +1499,9 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1493
1499
  }
1494
1500
 
1495
1501
  function syncNativeFastParticles(rail, enabled, entering = false) {
1496
- const track = Array.from(rail.children).find((element) => String(element.className).includes("_Track_"));
1502
+ const track = Array.from(rail.children).find((element) => hasNativeClass(element, "Track"));
1497
1503
  if (!track) return;
1498
- const staticParticles = rail.querySelector('[class*="_ParticleTransition_"]');
1504
+ const staticParticles = rail.querySelector(':is([class*="_ParticleTransition_"], [class*="ParticleTransition-"])');
1499
1505
  if (staticParticles && !matchMedia("(prefers-reduced-motion: reduce)").matches) {
1500
1506
  staticParticles.__codexStaticParticleFade?.cancel();
1501
1507
  staticParticles.style.opacity = enabled ? "0" : "1";
@@ -1507,7 +1513,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1507
1513
  );
1508
1514
  }
1509
1515
  }
1510
- track.querySelectorAll('[class*="_FastParticleClip_"]').forEach((element) => element.remove());
1516
+ track.querySelectorAll(':is([class*="_FastParticleClip_"], [class*="FastParticleClip-"])').forEach((element) => element.remove());
1511
1517
  if (!enabled || matchMedia("(prefers-reduced-motion: reduce)").matches) return;
1512
1518
  const clipClass = nativeCssModuleClass("FastParticleClip");
1513
1519
  const particlesClass = nativeCssModuleClass("FastTrackParticles");
@@ -1540,7 +1546,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1540
1546
  return path;
1541
1547
  }));
1542
1548
  clip.append(particles);
1543
- const tickRail = Array.from(track.children).find((element) => String(element.className).includes("_TickRail_"));
1549
+ const tickRail = Array.from(track.children).find((element) => hasNativeClass(element, "TickRail"));
1544
1550
  track.insertBefore(clip, tickRail ?? null);
1545
1551
  // 重建粒子层后按当前档位恢复裁剪,避免重开面板时覆盖整条滑轨。
1546
1552
  setGenericSliderVisual(rail, valueNow, valueMax + 1);
@@ -1615,13 +1621,13 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1615
1621
  root.setAttribute("data-max", "false");
1616
1622
  root.setAttribute("data-endpoint-labels-visible", "false");
1617
1623
  const range = Array.from(root.querySelectorAll("span")).find((element) => (
1618
- String(element.className).includes("_Range_")
1624
+ hasNativeClass(element, "Range")
1619
1625
  ));
1620
1626
  const thumbScale = Array.from(root.querySelectorAll("span")).find((element) => (
1621
- String(element.className).includes("_ThumbScale_")
1627
+ hasNativeClass(element, "ThumbScale")
1622
1628
  ));
1623
1629
  const tickRail = Array.from(root.querySelectorAll("div")).find((element) => (
1624
- String(element.className).includes("_TickRail_")
1630
+ hasNativeClass(element, "TickRail")
1625
1631
  ));
1626
1632
  const tickTemplate = tickRail?.querySelector("span");
1627
1633
  if (!range || !thumbScale || !tickRail || !tickTemplate) return null;
@@ -1680,29 +1686,29 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1680
1686
  copyCanvas();
1681
1687
  canvasTimer = setInterval(copyCanvas, 80);
1682
1688
  }
1683
- const sourceEffects = source.querySelector('[class*="_MaxEffects_"]');
1684
- const cloneEffects = clone.querySelector('[class*="_MaxEffects_"]');
1689
+ const sourceEffects = source.querySelector(':is([class*="_MaxEffects_"], [class*="MaxEffects-"])');
1690
+ const cloneEffects = clone.querySelector(':is([class*="_MaxEffects_"], [class*="MaxEffects-"])');
1685
1691
  if (!sourceEffects || !cloneEffects) return { disconnect };
1686
1692
  const sync = () => {
1687
1693
  if (!source.isConnected || !shell.isConnected) {
1688
1694
  disconnect();
1689
1695
  return;
1690
1696
  }
1691
- const sourceTransition = sourceEffects.querySelector('[class*="_ParticleTransition_"]');
1692
- let cloneTransition = cloneEffects.querySelector('[class*="_ParticleTransition_"]');
1697
+ const sourceTransition = sourceEffects.querySelector(':is([class*="_ParticleTransition_"], [class*="ParticleTransition-"])');
1698
+ let cloneTransition = cloneEffects.querySelector(':is([class*="_ParticleTransition_"], [class*="ParticleTransition-"])');
1693
1699
  if (!sourceTransition) {
1694
1700
  if (clone.querySelector("[data-fast-mode]")?.getAttribute("data-fast-mode") !== "true") {
1695
1701
  cloneTransition?.remove();
1696
1702
  }
1697
1703
  return;
1698
1704
  }
1699
- const sourceParticles = sourceTransition.querySelectorAll('[class*="_TrackParticle_"]');
1700
- let cloneParticles = cloneTransition?.querySelectorAll('[class*="_TrackParticle_"]');
1705
+ const sourceParticles = sourceTransition.querySelectorAll(':is([class*="_TrackParticle_"], [class*="TrackParticle-"])');
1706
+ let cloneParticles = cloneTransition?.querySelectorAll(':is([class*="_TrackParticle_"], [class*="TrackParticle-"])');
1701
1707
  if (!cloneTransition || sourceParticles.length !== cloneParticles.length) {
1702
1708
  cloneTransition?.remove();
1703
1709
  cloneTransition = sourceTransition.cloneNode(true);
1704
1710
  cloneEffects.append(cloneTransition);
1705
- cloneParticles = cloneTransition.querySelectorAll('[class*="_TrackParticle_"]');
1711
+ cloneParticles = cloneTransition.querySelectorAll(':is([class*="_TrackParticle_"], [class*="TrackParticle-"])');
1706
1712
  }
1707
1713
  cloneTransition.setAttribute("style", sourceTransition.getAttribute("style") ?? "");
1708
1714
  sourceParticles.forEach((particle, index) => {
@@ -1862,7 +1868,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1862
1868
  viewControls.append(modelButton);
1863
1869
  }
1864
1870
 
1865
- const nativeReset = parentMenu.firstElementChild?.querySelector('[class*="_ResetToDefault_"]');
1871
+ const nativeReset = parentMenu.firstElementChild?.querySelector(':is([class*="_ResetToDefault_"], [class*="ResetToDefault-"])');
1866
1872
  if (nativeReset) {
1867
1873
  const reset = nativeReset.cloneNode(true);
1868
1874
  reset.setAttribute("role", "button");
@@ -1870,7 +1876,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1870
1876
  reset.setAttribute("data-codex-model-slider-reset", "");
1871
1877
  reset.removeAttribute("data-radix-collection-item");
1872
1878
  reset.addEventListener("click", () => {
1873
- const currentReset = parentMenu.firstElementChild?.querySelector('[class*="_ResetToDefault_"]');
1879
+ const currentReset = parentMenu.firstElementChild?.querySelector(':is([class*="_ResetToDefault_"], [class*="ResetToDefault-"])');
1874
1880
  invokeNativeMenuItemSelect(currentReset);
1875
1881
  queueEnsure();
1876
1882
  });
@@ -2092,7 +2098,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2092
2098
  const existing = parentMenu.querySelector(":scope > [data-codex-model-slider-generic]");
2093
2099
  const nativeTemplateReady = Boolean(
2094
2100
  parentMenu.firstElementChild
2095
- ?.querySelector("[data-model-picker-power-slider] [class*='_TickRail_'] > span"),
2101
+ ?.querySelector("[data-model-picker-power-slider] :is([class*='_TickRail_'], [class*='TickRail-']) > span"),
2096
2102
  );
2097
2103
  const templateWaitCount = Number(parentMenu.getAttribute("data-codex-model-slider-template-wait") ?? 0);
2098
2104
  if (nativeModel?.slug.toLowerCase().startsWith("gpt-") && !nativeTemplateReady && templateWaitCount < 3) {
@@ -2212,8 +2218,8 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2212
2218
  if (trigger.querySelector("[data-codex-model-slider-trigger-label]")) return;
2213
2219
  const raw = trigger.getAttribute("data-codex-model-slider-trigger-raw");
2214
2220
  if (!raw) return;
2215
- const anchor = trigger.querySelector('[class*="_ModelPickerTriggerLabel_"]')
2216
- ?? trigger.querySelector('[class*="_ModelPickerTriggerContent_"]');
2221
+ const anchor = trigger.querySelector('[class*="ModelPickerTriggerLabel"]')
2222
+ ?? trigger.querySelector('[class*="ModelPickerTriggerContent"]');
2217
2223
  if (!anchor) return;
2218
2224
  const displayLabel = document.createElement("span");
2219
2225
  displayLabel.setAttribute("data-codex-model-slider-trigger-label", "");
@@ -2235,14 +2241,14 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2235
2241
  modelSelectorTriggerObservers.set(trigger, observer);
2236
2242
  }
2237
2243
  const openPlaceholder = trigger.getAttribute("data-state") === "open"
2238
- && trigger.querySelector('[class*="_ModelPickerTriggerPlaceholder_"]');
2244
+ && trigger.querySelector('[class*="ModelPickerTriggerPlaceholder"]');
2239
2245
  if (openPlaceholder && modelSelectorControllerMenu?.trigger === trigger && !modelSelectorControllerMenu.closing) {
2240
2246
  const textNode = firstVisibleTextNode(openPlaceholder);
2241
2247
  const label = locale === "zh-CN" ? "选择模型" : "Select model";
2242
2248
  if (textNode && textNode.nodeValue !== label) textNode.nodeValue = label;
2243
2249
  }
2244
2250
  const nativeLabels = Array.from(trigger.querySelectorAll(
2245
- '[class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label])',
2251
+ '[class*="ModelPickerTriggerModelText"]:not([data-codex-model-slider-trigger-label])',
2246
2252
  ));
2247
2253
  if (!nativeLabels.length) {
2248
2254
  trigger.setAttribute("data-codex-model-slider-trigger", "");
@@ -0,0 +1,50 @@
1
+ // 从已注册的原生模块与导出表解析角色,不执行模块工厂或依赖压缩名称。
2
+ export function loadRspackQuickChatBindings(registry, commandCatalog) {
3
+ const catalog=registry.module(source=>/\{id:[`"']temporaryChat[`"'],titleIntlId:/.test(source),'Quick Chat command catalog');
4
+ const native=commandCatalog(catalog.source);
5
+ if (native) return {native};
6
+ const entry=registry.module(source=>/has-opened-quick-chat-v1/.test(source)&&/chatgpt\.quick-chat/.test(source),'Quick Chat lifecycle');
7
+ const exported=local=>{
8
+ if (!local) throw new Error('quick-chat-lifecycle-contract-unavailable');
9
+ const escaped=local.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
10
+ const key=entry.source.match(new RegExp(`([\\w$]+):\\(\\)=>${escaped}(?=[,}])`))?.[1];
11
+ const value=key && registry.exports(entry)[key];
12
+ if (value==null) throw new Error('quick-chat-lifecycle-export-unavailable');
13
+ return value;
14
+ };
15
+ const open=registry.value(entry,(value,source)=>typeof value==='function'&&/chatgpt\.quick-chat/.test(source)&&/\.get\(([\w$]+)\)\|\|/.test(source),'Quick Chat open');
16
+ const source=String(open);
17
+ const create=exported(source.match(/null==[\w$]+\?([\w$]+)\([\w$]+,[\w$]+\):/)?.[1]);
18
+ const state=exported(source.match(/\.get\(([\w$]+)\)\|\|/)?.[1]);
19
+ const bindings={native,open,create,isOpen:scope=>scope.get(state)};
20
+ if (typeof create!=='function') throw new Error('quick-chat-lifecycle-contract-unavailable');
21
+ try {
22
+ const discard=registry.value(entry,(value,source)=>typeof value==='function'&&/draftKey:/.test(source)&&/content:null/.test(source)&&/conversationId,""/.test(source),'Quick Chat discard');
23
+ const actions=String(discard).match(/let ([\w$]+)=([\w$]+)\.get\(([\w$]+)\);([\w$]+)\(\2\),\2\.set\(\3,null\),null!=\1&&\(\(0,([\w$]+\.[\w$]+)\)\(\2,\{draftKey:\1\.conversationId,content:null\}\),\(0,([\w$]+\.[\w$]+)\)\(\2,\1\.conversationId,""\),\2\.set\(([\w$]+\.[\w$]+),\1\.conversationId,\[\]\)/);
24
+ if (!actions) throw new Error('quick-chat-close-contract-unavailable');
25
+ const session=exported(actions[3]),close=exported(actions[4]);
26
+ const draft=registry.binding(entry,actions[5]),text=registry.binding(entry,actions[6]),attachments=registry.binding(entry,actions[7]);
27
+ if ([close,draft,text].some(value=>typeof value!=='function')) throw new Error('quick-chat-close-contract-unavailable');
28
+ bindings.close=(scope,id)=>{
29
+ if (scope.get(session)?.conversationId!==id) return false;
30
+ draft(scope,{draftKey:id,content:null});text(scope,id,'');scope.set(attachments,id,[]);close(scope);return true;
31
+ };
32
+ } catch { /* 关闭协议独立失效时仍保留打开能力。 */ }
33
+ try {
34
+ const modeEntry=registry.module(source=>/conversationDetailMode/.test(source)&&/"work":"codex"/.test(source),'Quick Chat product mode');
35
+ const modes=Object.values(registry.exports(modeEntry));
36
+ if (modes.length===1) bindings.mode=scope=>scope.get(modes[0]);
37
+ } catch { /* 无模式 selector 时只使用可见模式选择器。 */ }
38
+ return bindings;
39
+ }
40
+
41
+ export function loadRspackQuickChatIcon(registry) {
42
+ const entry=registry.module(source=>/"data-floating-chat-surface":/.test(source)&&/minimizedIcon:/.test(source),'Quick Chat floating surface');
43
+ const local=entry.source.match(/minimizedIcon:([\w$]+)/)?.[1];
44
+ const escaped=local?.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
45
+ const reference=escaped&&entry.source.match(new RegExp(`children:${escaped}\\?\\?\\(0,[\\w$]+\\.jsx\\)\\(([\\w$]+\\.[\\w$]+),\\{\\}\\)`))?.[1];
46
+ if (!reference) throw new Error('quick-chat-icon-contract-unavailable');
47
+ const icon=registry.binding(entry,reference);
48
+ if (typeof icon?.createElement!=='function') throw new Error('quick-chat-icon-contract-unavailable');
49
+ return icon;
50
+ }
@@ -1,4 +1,5 @@
1
- import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
+ import {loadRspackQuickChatBindings} from "./rspack.mjs";
2
+ import { findCodexModule, codexModuleRegistry } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
2
3
  import {quickChatLifecycleActions, quickChatScope} from "./lifecycle.mjs";
3
4
  // 通过原生能力检查与已有会话分支定位打开动作,不依赖构建哈希或压缩名称。
4
5
  export function quickChatOpenExport(source) {
@@ -99,6 +100,12 @@ export function installQuickChatShortcut(signal, codex, discover = null) {
99
100
  }
100
101
  async function load() {
101
102
  codex.report('quick-chat-shortcut', false, 'quick-chat-module-pending');
103
+ const registry=await codexModuleRegistry({signal:loading.signal,waitForEntry:true,read:async url=>(await fetch(url,{signal:loading.signal})).text()});
104
+ if (registry) {
105
+ const bindings=loadRspackQuickChatBindings(registry,quickChatNativeCommand);
106
+ if (!bindings.native && !bindings.close) codex.report('quick-chat-close',false,'quick-chat-close-contract-unavailable');
107
+ return bindings;
108
+ }
102
109
  const url=await findCodexModule('app-initial',{signal:loading.signal,waitForEntry:true});
103
110
  if (!url) throw new Error('quick-chat-module-unavailable');
104
111
  const source = await (await fetch(url, {signal:loading.signal})).text();
@@ -1,5 +1,9 @@
1
- import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
+ import {loadRspackQuickChatIcon} from "./rspack.mjs";
2
+ import { findCodexModule, codexModuleRegistry } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
2
3
  export async function loadQuickChatSidebarIcon(signal) {
4
+ signal.throwIfAborted();
5
+ const registry=await codexModuleRegistry({signal,waitForEntry:true,read:async url=>(await fetch(url,{signal})).text()});
6
+ if (registry) return loadRspackQuickChatIcon(registry);
3
7
  const initialUrl = await findCodexModule('app-initial',{signal});
4
8
  if (!initialUrl) throw new Error('quick-chat-icon-module-unavailable');
5
9
  const initial = await (await fetch(initialUrl,{signal})).text();
@@ -1,4 +1,5 @@
1
- import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
1
+ import {loadRspackQuickChatBindings,loadRspackQuickChatIcon} from "./adapters/codex/rspack.mjs";
2
+ import { findCodexModule, codexModuleRegistry } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
2
3
  import {quickChatLifecycleActions, quickChatScope} from "./adapters/codex/lifecycle.mjs";
3
4
  import {quickChatHistoryVisible} from "./adapters/codex/history.mjs";
4
5
  import { readFile } from "node:fs/promises";
@@ -10,7 +11,7 @@ import { installQuickChatSidebar } from "./frontend/sidebar.mjs";
10
11
  import { createHistoryTracker } from "./domain/history.mjs";
11
12
  export async function createPageScriptSource() {
12
13
  const source = await readFile(new URL("./frontend/entry.mjs", import.meta.url), "utf8");
13
- const functions = [findCodexModule, quickChatHistoryVisible, loadQuickChatSidebarIcon, createQuickChatSidebarAdapter, installQuickChatSidebar,
14
+ const functions = [findCodexModule, codexModuleRegistry, loadRspackQuickChatBindings, loadRspackQuickChatIcon, quickChatHistoryVisible, loadQuickChatSidebarIcon, createQuickChatSidebarAdapter, installQuickChatSidebar,
14
15
  quickChatLifecycleActions, quickChatScope, quickChatOpenExport, quickChatLifecycleExports, quickChatNativeCommand, installQuickChatShortcut, createQuickChatAdapter,
15
16
  createSubmissionTracker, createHistoryTracker];
16
17
  return functions.map(fn => `const ${fn.name}=${fn.toString()};`).join('\n') + '\n' + source;
@@ -269,6 +269,7 @@ collapse.innerHTML = '<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" st
269
269
  collapse.addEventListener("click", () => { setGlobalExpanded(false); trigger.focus(); hideTooltip(); }, { signal });
270
270
  root.append(collapse);
271
271
  let snapshot = null;
272
+ let logSnapshot = null;
272
273
  // 统计刷新会重建浮窗,复用图标节点以保留 tooltip 的悬浮锚点。
273
274
  const tpsInfo = document.createElement("button");
274
275
  tpsInfo.type = "button";
@@ -499,7 +500,8 @@ function ensurePosition() {
499
500
  activeThreadId = threadId;
500
501
  nativeContext = emptyNativeContext();
501
502
  // 会话建立后先展示占位,不让日志落盘速度决定状态栏是否出现。
502
- snapshot = threadId ? { threadId } : null;
503
+ logSnapshot = threadId ? { threadId } : null;
504
+ snapshot = logSnapshot;
503
505
  closePanel();
504
506
  render();
505
507
  requestRefresh();
@@ -532,8 +534,12 @@ function ensurePosition() {
532
534
  }
533
535
  syncVisibility(composer);
534
536
  }
537
+ // 原生上下文不可用时保留日志口径,避免环退化为占位。
538
+ function withNativeContext(current, native) {
539
+ return !current || native.contextPercent == null ? current : { ...current, ...native };
540
+ }
535
541
  function render() {
536
- if (snapshot) snapshot = { ...snapshot, ...nativeContext };
542
+ snapshot = withNativeContext(logSnapshot, nativeContext);
537
543
  syncVisibility(findComposer());
538
544
  if (root.hidden) return;
539
545
  trigger.querySelector("[data-context-progress]").setAttribute("stroke-dashoffset", String(100 - (snapshot.contextPercent ?? 0)));
@@ -587,7 +593,7 @@ const receiver = {
587
593
  if (revision <= lastRevision) return;
588
594
  lastRevision = revision;
589
595
  }
590
- snapshot = value;
596
+ logSnapshot = value;
591
597
  render();
592
598
  scheduleRefresh();
593
599
  },
@@ -31,8 +31,8 @@ function pageInfo() {
31
31
  if (!visible(content)) return null;
32
32
  const native = nativeProps(list || content.querySelector("button") || content.querySelector("p") || content.lastElementChild?.lastElementChild, p => "viewMode" in p && "shared" in p);
33
33
  const grid = native ? native.viewMode === "grid" : !!page.querySelector('button[aria-label="Grid view"][aria-pressed="true"],button[aria-label="网格视图"][aria-pressed="true"]');
34
- const ownershipTabs=[...page.querySelectorAll('[role="tablist"]')].find(node=>!node.closest(`[${owned}]`)&&nativeProps(node,p=>p.tabs?.some(tab=>tab.key==="owned")&&p.tabs?.some(tab=>tab.key==="shared")));
35
- const ownership=ownershipTabs&&nativeProps(ownershipTabs,p=>p.tabs?.some(tab=>tab.key==="owned")&&p.tabs?.some(tab=>tab.key==="shared"));
34
+ const ownershipTabs=[...page.querySelectorAll('[role="tablist"]')].find(node=>!node.closest(`[${owned}]`)&&nativeProps(node,p=>p.tabs?.some(tab=>tab.key==="owned")&&p.tabs?.some(tab=>["shared","pinned"].includes(tab.key))));
35
+ const ownership=ownershipTabs&&nativeProps(ownershipTabs,p=>p.tabs?.some(tab=>tab.key==="owned")&&p.tabs?.some(tab=>["shared","pinned"].includes(tab.key)));
36
36
  const shared = ownership ? ownership.selectedKey === "shared" : native ? !!native.shared : [...page.querySelectorAll("button[aria-pressed=true]")].some(b => /Shared with you|与你共享|與你共用/.test(b.textContent));
37
37
  const create = [...(plus ? page : page.firstElementChild).querySelectorAll("button")].find(b => visible(b) && (plus ? /^(Create Site|创建站点|创建新站点|建立網站)$/ : /^(New|新建|新增)$/).test(b.textContent.trim()) && !b.hasAttribute(owned));
38
38
  codex.report("sites-page",true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecodex",
3
- "version": "2.0.51",
3
+ "version": "2.0.53",
4
4
  "description": "Lightweight, local-first plugins for the Codex desktop app.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url";
8
8
  import { BrowserExchangeStore } from "./browser-exchange.mjs";
9
9
  import { FEATURE_IDS, readConfig, updateFeature } from "./config.mjs";
10
10
  import { normalizeRuntimeReady } from "./runtime-manager.mjs";
11
+ import { createNpmAdapter } from "./npm-adapter.mjs";
11
12
 
12
13
  import { createDataTransfer, MAX_TRANSFER_BYTES } from "./data-transfer/service.mjs";
13
14
 
@@ -327,11 +328,9 @@ export async function createControlServer({
327
328
  return;
328
329
  }
329
330
  if (request.method === "GET" && url.pathname === "/api/update") {
330
- const registryResponse = await fetch("https://registry.npmjs.org/minecodex/latest", {
331
- signal: AbortSignal.timeout(4_000),
332
- });
333
- if (!registryResponse.ok) throw Object.assign(new Error("Update check failed"), { statusCode: 502 });
334
- const latest = String((await registryResponse.json()).version ?? "");
331
+ // 与 mcx update 共用 npm 配置,保留用户的 registry、代理和证书设置。
332
+ const latest = await createNpmAdapter().viewLatestVersion();
333
+ if (!latest) throw Object.assign(new Error("Update check failed"), { statusCode: 502 });
335
334
  json(response, 200, {
336
335
  current: packageMetadata.version,
337
336
  latest,
@@ -7,17 +7,17 @@ import { promisify } from "node:util";
7
7
  const defaultExecFile = promisify(execFileCallback);
8
8
 
9
9
  export function createNpmAdapter({ execFile = defaultExecFile, tempRoot = os.tmpdir(), platform = process.platform } = {}) {
10
- function runNpm(args) {
11
- if (platform !== "win32") return execFile("npm", args);
10
+ function runNpm(args, options = {}) {
11
+ if (platform !== "win32") return execFile("npm", args, options);
12
12
  // npm.cmd 需要命令解释器;字面量参数经编码传递,避免路径被当成脚本。
13
13
  const quote = (value) => "'" + String(value).replaceAll("'", "''") + "'";
14
14
  const script = "[Console]::OutputEncoding=[Text.Encoding]::UTF8; $ErrorActionPreference='Stop'; & npm.cmd "
15
15
  + args.map(quote).join(" ") + "; exit $LASTEXITCODE";
16
- return execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")]);
16
+ return execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], options);
17
17
  }
18
18
  return {
19
19
  async viewLatestVersion() {
20
- const { stdout } = await runNpm(["view", "minecodex", "version"]);
20
+ const { stdout } = await runNpm(["view", "minecodex", "version", "--fetch-retries=0", "--fetch-timeout=8000"], { timeout: 12_000 });
21
21
  return stdout.trim();
22
22
  },
23
23
 
@@ -1,4 +1,5 @@
1
- import { findCodexModule } from '../renderer/modules.mjs';
1
+ import {loadRspackMarkdownTableActions} from './rspack.mjs';
2
+ import { findCodexModule, codexModuleRegistry } from '../renderer/modules.mjs';
2
3
  import { loadNativeMarkdownRenderer } from './markdown-native.mjs';
3
4
 
4
5
  export function markdownExportAlias(source, name) {
@@ -14,6 +15,8 @@ export function markdownExportAlias(source, name) {
14
15
 
15
16
  // 从 Chat Markdown 的公开组件发现悬浮操作栏和控件,不绑定构建 hash。
16
17
  export async function loadNativeMarkdownTableActions(native) {
18
+ const registry=await codexModuleRegistry();
19
+ if(registry)return loadRspackMarkdownTableActions(registry,native);
17
20
  const url = await findCodexModule('app-initial');
18
21
  if (!url) throw new Error('native-table-actions-module-unavailable');
19
22
  const response = await fetch(url);
@@ -1,6 +1,8 @@
1
- import { findCodexModule } from '../renderer/modules.mjs';
1
+ import { findCodexModule, codexModuleRegistry } from '../renderer/modules.mjs';
2
2
  // 使用当前编辑器所属的原生 CodeMirror 实例,避免引入第二套编辑器依赖。
3
3
  export async function loadMarkdownListAPI(view) {
4
+ const registry=await codexModuleRegistry();
5
+ if (registry) return loadRspackMarkdownListAPI(registry,view);
4
6
  const urls=await findCodexModule('dist',{all:true});
5
7
  for (const url of urls) {
6
8
  const exports = Object.values(await import(url));
@@ -24,3 +26,26 @@ export async function loadMarkdownListAPI(view) {
24
26
  }
25
27
  throw new Error('native-markdown-list-api-unavailable');
26
28
  }
29
+
30
+
31
+ export function loadRspackMarkdownListAPI(registry,view) {
32
+ const values=[...new Set(registry.entries.flatMap(entry=>{
33
+ let exports;
34
+ try {exports=Object.values(registry.exports(entry));} catch {return [];}
35
+ return exports.includes(view.constructor)||exports.includes(view.state.constructor)?exports:[];
36
+ }))];
37
+ const requireRole=(predicate,role)=>{
38
+ const matches=values.filter(predicate);
39
+ if(matches.length!==1)throw new Error(`native-codemirror-${role}-unavailable`);
40
+ return matches[0];
41
+ };
42
+ return {
43
+ Decoration:requireRole(value=>value?.line&&value?.replace&&value?.mark,'decoration'),
44
+ ViewPlugin:requireRole(value=>value?.fromClass&&value?.define,'view-plugin'),
45
+ StateEffect:requireRole(value=>value?.appendConfig&&value?.reconfigure,'state-effect'),
46
+ Compartment:requireRole(value=>value?.prototype?.reconfigure&&value?.prototype?.of&&value?.prototype?.get,'compartment'),
47
+ WidgetType:requireRole(value=>value?.prototype?.eq&&value?.prototype?.ignoreEvent&&value?.prototype?.updateDOM,'widget'),
48
+ StateField:requireRole(value=>value?.define&&value?.prototype?.slot&&value?.prototype?.init&&value?.prototype?.create,'state-field'),
49
+ Prec:requireRole(value=>value?.highest&&value?.lowest&&value?.high,'precedence'),
50
+ };
51
+ }