minecodex 2.0.48 → 2.0.49

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 (28) hide show
  1. package/features/composer-expand/src/injection-source.mjs +2 -1
  2. package/features/composer-expand/src/native.mjs +6 -18
  3. package/features/file-navigation/src/adapters/codex/inline-modules.mjs +54 -0
  4. package/features/file-navigation/src/adapters/codex/modules.mjs +16 -4
  5. package/features/file-navigation/src/injection-source.mjs +3 -1
  6. package/features/images/src/adapters/codex/library-api.mjs +8 -7
  7. package/features/images/src/adapters/codex/native-image-modules.mjs +27 -15
  8. package/features/images/src/backend/cloud-library.mjs +3 -1
  9. package/features/images/src/library-prompt-source.mjs +3 -2
  10. package/features/markdown-reading/src/adapters/codex/links.mjs +2 -3
  11. package/features/markdown-reading/src/adapters/codex/rich-text.mjs +5 -4
  12. package/features/markdown-reading/src/injection-source.mjs +2 -1
  13. package/features/mermaid-diagrams/src/adapters/codex/native.mjs +3 -2
  14. package/features/mermaid-diagrams/src/injection-source.mjs +2 -1
  15. package/features/model-slider/src/adapters/codex/controller.mjs +4 -4
  16. package/features/model-slider/src/injection-source.mjs +2 -0
  17. package/features/quick-chat/src/adapters/codex/shortcut.mjs +12 -19
  18. package/features/quick-chat/src/adapters/codex/sidebar.mjs +5 -5
  19. package/features/quick-chat/src/injection-source.mjs +2 -1
  20. package/features/session-status/src/adapters/codex/context-usage.mjs +4 -15
  21. package/features/session-status/src/injection-source.mjs +2 -1
  22. package/package.json +2 -1
  23. package/packages/codex-adapter/src/markdown/block-actions.mjs +3 -3
  24. package/packages/codex-adapter/src/markdown/editor-api.mjs +4 -4
  25. package/packages/codex-adapter/src/markdown/toc-native.mjs +17 -13
  26. package/packages/codex-adapter/src/renderer/environment.mjs +4 -1
  27. package/packages/codex-adapter/src/renderer/modules.mjs +66 -0
  28. package/packages/runtime-host/src/codex-injection.mjs +7 -17
@@ -1,7 +1,8 @@
1
+ import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  import {readFile} from 'node:fs/promises';
2
3
  import {loadComposerExpandControls,createComposerExpandAdapter} from './native.mjs';
3
4
  import {composerCanExpand,installComposerExpand} from './install.mjs';
4
5
  export async function createPageScriptSource(){
5
- return [loadComposerExpandControls,createComposerExpandAdapter,composerCanExpand,installComposerExpand]
6
+ return [findCodexModule, loadComposerExpandControls,createComposerExpandAdapter,composerCanExpand,installComposerExpand]
6
7
  .map(fn=>`const ${fn.name}=${fn.toString()};`).join('\n')+'\n'+await readFile(new URL('./entry.mjs',import.meta.url),'utf8');
7
8
  }
@@ -1,26 +1,14 @@
1
+ import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  export async function loadComposerExpandControls(signal) {
2
3
  signal.throwIfAborted();
3
- function modules() {
4
- const urls = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(node => node.href || node.src);
5
- const initialUrl = urls.find(url => /\/app-initial-[^/]+\.js/.test(url));
6
- const mainUrl = urls.find(url => /\/app-main-[^/]+\.js/.test(url));
7
- return initialUrl && mainUrl ? {initialUrl,mainUrl} : null;
8
- }
9
- // 冷启动时入口标签可能晚于 DOMContentLoaded 挂载。
10
- const {initialUrl,mainUrl} = modules() ?? await new Promise((resolve,reject) => {
11
- const observer = new MutationObserver(check);
12
- function cleanup() { observer.disconnect();signal.removeEventListener('abort',abort); }
13
- function abort() { cleanup();reject(signal.reason); }
14
- function check() { const found=modules();if(found){cleanup();resolve(found);} }
15
- observer.observe(document.documentElement,{subtree:true,childList:true,attributes:true,attributeFilter:['src','href','rel']});
16
- signal.addEventListener('abort',abort,{once:true});
17
- check();
18
- });
4
+ const initialUrl=await findCodexModule('app-initial',{signal,waitForEntry:true});
5
+ const mainUrl=await findCodexModule('app-main',{signal,waitForEntry:true});
6
+ if (!initialUrl || !mainUrl) throw new Error('composer-expand-modules-unavailable');
19
7
  const read = async url => { const response=await fetch(url,{signal}); if(!response.ok) throw new Error('composer-expand-source-unavailable'); return response.text(); };
20
8
  const [initialSource,mainSource] = await Promise.all([read(initialUrl),read(mainUrl)]);
21
9
  const file = initialSource.match(/["'`]\.\/(app-primary-[\w-]+\.js)["'`]/)?.[1];
22
- if (!file) throw new Error('composer-expand-primary-unavailable');
23
- const primaryUrl = new URL(file,initialUrl).href;
10
+ const primaryUrl = file ? new URL(file,initialUrl).href : await findCodexModule('app-primary',{read,signal});
11
+ if (!primaryUrl) throw new Error('composer-expand-primary-unavailable');
24
12
  const source = await read(primaryUrl);
25
13
  async function binding(source,url,local) {
26
14
  if (!local) throw new Error('composer-expand-binding-unavailable');
@@ -0,0 +1,54 @@
1
+ // 新版将文件树和目录读取合入入口;仍按调用角色绑定,不执行无关初始化器。
2
+ export async function loadInlineFileNavigationModules(initialUrl, mainUrl, read, load) {
3
+ const [source,main]=await Promise.all([read(initialUrl),read(mainUrl)]);
4
+ const aliases=text=>new Map([...text.slice(text.lastIndexOf('export{')+7).split('}')[0].split(',')]
5
+ .map(entry=>{const [local,alias=local]=entry.trim().split(/\s+as\s+/);return [local,alias];}));
6
+ const imports=(text,url)=>new Map([...text.matchAll(/import\{([^}]+)\}from["']([^"']+)["']/g)]
7
+ .flatMap(match=>match[1].split(',').map(entry=>{const parts=entry.trim().split(/\s+as\s+/);return [parts.at(-1),{name:parts[0],url:new URL(match[2],url).href}];})));
8
+ const exported=aliases(source),dependencies=imports(source,initialUrl),module=await load(initialUrl);
9
+ const binding=async local=>{
10
+ if (exported.has(local)) return module[exported.get(local)];
11
+ const value=dependencies.get(local);
12
+ if(!value)throw new Error('file-navigation-inline-dependency-unavailable');
13
+ return (await load(value.url))[value.name];
14
+ };
15
+ const functions=[...source.matchAll(/function ([\w$]+)\(/g)];
16
+ const bodies=functions.map((match,index)=>({name:match[1],body:source.slice(match.index,functions[index+1]?.index??source.length)}));
17
+ const trees=bodies.filter(({name,body})=>exported.has(name)&&/contextMenuEnabled:/.test(body)&&/initialExpandedPaths:/.test(body)&&/onSelectionChange:/.test(body));
18
+ if(trees.length!==1)throw new Error('file-navigation-inline-tree-unavailable');
19
+ const tree=trees[0],cache=tree.body.match(/\(0,([\w$]+)\.c\)/)?.[1];
20
+ if(cache) {
21
+ const assignment=source.indexOf(`${cache}=`),before=source.slice(source.lastIndexOf('function ',assignment),assignment);
22
+ const initializer=before.match(/^function ([\w$]+)\(\)\{return\(\1=/)?.[1];
23
+ if(!initializer || typeof await binding(initializer)!=='function')throw new Error('file-navigation-inline-initializer-unavailable');
24
+ (await binding(initializer))();
25
+ }
26
+ const Tree=await binding(tree.name);
27
+ const reactOwner=tree.body.match(/\(0,([\w$]+)\.useState\)/)?.[1];
28
+ const reactFactory=reactOwner && source.match(new RegExp(`${reactOwner.replace(/\$/g,'\\$')}=([\\w$]+)\\(\\)`))?.[1];
29
+ const rootOwner=main.match(/\(0,([\w$]+)\.createRoot\)/)?.[1];
30
+ const rootFactory=rootOwner && main.match(new RegExp(`${rootOwner.replace(/\$/g,'\\$')}=([\\w$]+)\\(\\)`))?.[1];
31
+ const rootImport=imports(main,mainUrl).get(rootFactory);
32
+ if(!rootImport || !reactFactory)throw new Error('file-navigation-inline-react-unavailable');
33
+ const React=(await binding(reactFactory))(),ReactDOM=(await load(rootImport.url))[rootImport.name]();
34
+ const requests=bodies.filter(({body})=>body.includes('.workspaceFiles')&&body.includes('.listDirectory(')&&body.includes('.throwIfAborted('));
35
+ if(requests.length!==1)throw new Error('file-navigation-inline-directory-unavailable');
36
+ const requestBody=requests[0].body;
37
+ const host=requestBody.match(/let [\w$]+=([\w$]+)\.workspaceFiles/)?.[1];
38
+ const cancellable=requestBody.match(/return ([\w$]+)\([\w$]+\.listDirectory\(/)?.[1];
39
+ const bridge=await binding(host),withSignal=await binding(cancellable);
40
+ if(typeof bridge?.workspaceFiles?.listDirectory!=='function'||typeof withSignal!=='function')throw new Error('file-navigation-inline-directory-unavailable');
41
+ const request=(_method,{params,signal})=>{signal?.throwIfAborted();return withSignal(bridge.workspaceFiles.listDirectory(params),signal);};
42
+ const sharedUrl=[...dependencies.values()].find(value=>/\/app-shared-/.test(value.url))?.url;
43
+ if(!sharedUrl)throw new Error('file-navigation-inline-icons-unavailable');
44
+ const sharedSource=await read(sharedUrl),shared=await load(sharedUrl),sharedExports=aliases(sharedSource);
45
+ const folderAt=sharedSource.indexOf('name:`folder-light-16`'),prefix=sharedSource.slice(sharedSource.lastIndexOf('function ',folderAt),folderAt);
46
+ const folderInit=prefix.match(/^function ([\w$]+)\(\)\{return\(\1=/)?.[1];
47
+ if(folderAt<0||typeof shared[sharedExports.get(folderInit)]!=='function')throw new Error('file-navigation-folder-icon-unavailable');
48
+ shared[sharedExports.get(folderInit)]();
49
+ const folder=Object.values(shared).find(value=>value?.name==='folder-light-16');
50
+ if(typeof Tree!=='function')throw new Error('file-navigation-inline-tree-unavailable');
51
+ if(!React?.createElement||!ReactDOM?.createRoot)throw new Error('file-navigation-inline-react-unavailable');
52
+ if(!folder?.canvas?.viewBox||typeof folder.body!=='string')throw new Error('file-navigation-folder-icon-unavailable');
53
+ return {Tree,React,ReactDOM,request,folder};
54
+ }
@@ -1,9 +1,18 @@
1
+ import { loadInlineFileNavigationModules } from './inline-modules.mjs';
2
+ import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
3
  // 依照原生 breadcrumb 的依赖角色查找公开模块,不绑定产物 hash 或短变量名。
2
4
  export async function loadFileNavigationModules({read = async url => { const response = await fetch(url); if (!response.ok) throw new Error('file-navigation-source-unavailable'); return response.text(); }, load = url => import(url)} = {}) {
3
- const urls = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(n => n.href || n.src);
4
- const breadcrumbUrl = urls.find(url => /\/review-file-source-breadcrumb-[^/]+\.js$/.test(url));
5
- const mainUrl = urls.find(url => /\/app-main-[^/]+\.js$/.test(url));
6
- if (!breadcrumbUrl || !mainUrl) throw new Error('file-navigation-modules-unavailable');
5
+ const [breadcrumbUrl,mainUrl]=await Promise.all([
6
+ findCodexModule('review-file-source-breadcrumb',{read}),findCodexModule('app-main',{read}),
7
+ ]);
8
+ if (!mainUrl) throw new Error('file-navigation-modules-unavailable');
9
+ let resolved;
10
+ if (!breadcrumbUrl) {
11
+ const initialUrl=await findCodexModule('app-initial',{read});
12
+ if (!initialUrl) throw new Error('file-navigation-modules-unavailable');
13
+ resolved=await loadInlineFileNavigationModules(initialUrl,mainUrl,read,load);
14
+ }
15
+ if (!resolved) {
7
16
  const imports = (source, url) => {
8
17
  const result = new Map();
9
18
  for (const match of source.matchAll(/import\{([^}]+)\}from["']([^"']+)["']/g))
@@ -51,6 +60,9 @@ export async function loadFileNavigationModules({read = async url => { const res
51
60
  if (!folder?.canvas?.viewBox || typeof folder.body !== 'string')
52
61
  throw new Error('file-navigation-folder-icon-unavailable');
53
62
  // 分列导航没有纵向展开状态;通过原生样式入口使用固定的原生文件夹图标。
63
+ resolved={Tree,React,ReactDOM,request,folder};
64
+ }
65
+ const {Tree,React,ReactDOM,request,folder}=resolved;
54
66
  const folderImage = `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${folder.canvas.viewBox}">${folder.body}</svg>`)}")`;
55
67
  const columnStyles = `
56
68
  /* 原生树的逻辑焦点也响应鼠标;描边仅保留给键盘可见焦点。 */
@@ -1,9 +1,11 @@
1
+ import { loadInlineFileNavigationModules } from './adapters/codex/inline-modules.mjs';
2
+ import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
1
3
  import { readFile } from 'node:fs/promises';
2
4
  import { fileNavigationPath, selectFileNavigationDirectory } from './domain/navigation.mjs';
3
5
  import { loadFileNavigationModules } from './adapters/codex/modules.mjs';
4
6
  import { createFileNavigationAdapter } from './adapters/codex/navigation.mjs';
5
7
  import { installFileNavigation } from './frontend/navigation.mjs';
6
8
  export async function createPageScriptSource() {
7
- const bindings = [fileNavigationPath, selectFileNavigationDirectory, loadFileNavigationModules, createFileNavigationAdapter, installFileNavigation];
9
+ const bindings = [loadInlineFileNavigationModules,findCodexModule, fileNavigationPath, selectFileNavigationDirectory, loadFileNavigationModules, createFileNavigationAdapter, installFileNavigation];
8
10
  return bindings.map(fn => `const ${fn.name} = ${fn.toString()};`).join('\n') + '\n' + await readFile(new URL('./entry.mjs', import.meta.url), 'utf8');
9
11
  }
@@ -1,18 +1,19 @@
1
+ import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  // 唯一的 Library 构建产物协议;调用方只消费命名操作。
2
3
  export function createLibraryApi({read:readSource=async url=>(await fetch(url)).text(),load:loadModule=url=>import(url)} = {}) {
3
4
  let capability=null, downloadCapability=null;
4
5
  async function connect() {
5
6
  if (!capability) capability=(async()=>{
6
- const initialUrl = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')]
7
- .map(element => element.href || element.src).find(url => /\/app-initial-[^/]+\.js/.test(url));
7
+ const initialUrl = await findCodexModule('app-initial',{read:readSource});
8
8
  if (!initialUrl) throw new Error("Cloud Library is unavailable in this Codex version");
9
9
  const initialSource = await readSource(initialUrl);
10
- const asset = name => {
10
+ const asset = async name => {
11
11
  const file = initialSource.match(new RegExp(`${name}-[\\w-]+\\.js`))?.[0];
12
- if (!file) throw new Error(`Cloud Library capability ${name} is unavailable`);
13
- return new URL(file, initialUrl).href;
12
+ const url=file ? new URL(file, initialUrl).href : await findCodexModule(name,{read:readSource,via:['library-page']});
13
+ if (!url) throw new Error(`Cloud Library capability ${name} is unavailable`);
14
+ return url;
14
15
  };
15
- const metadataUrl = asset("library-file-metadata");
16
+ const metadataUrl = await asset("library-file-metadata");
16
17
  const metadataSource = await readSource(metadataUrl);
17
18
  const apiLocal = metadataSource.match(/([\w$]+)\.safePost\(`\/files\/library`/)?.[1];
18
19
  const importBlock = [...metadataSource.matchAll(/import\{([^}]+)\}from["']([^"']+)["']/g)].find(match => match[1].split(",").some(binding=>binding.trim().split(/\s+as\s+/).at(-1)===apiLocal));
@@ -33,7 +34,7 @@ export function createLibraryApi({read:readSource=async url=>(await fetch(url)).
33
34
  async function download(file,signal) {
34
35
  if (!downloadCapability) downloadCapability=(async()=>{
35
36
  const {asset}=await connect();
36
- const downloadUrl = asset("library-file-download");
37
+ const downloadUrl = await asset("library-file-download");
37
38
  const downloadSource = await readSource(downloadUrl);
38
39
  const exports = downloadSource.match(/export\{([^}]+)\}/)?.[1].split(",").map(s => s.trim().split(/\s+as\s+/)) ?? [];
39
40
  const responseLocal = downloadSource.match(/async function ([\w$]+)\([^)]*\)\{return [\w$]+\(await [\w$]+\([^;]+showInline:!0/)?.[1];
@@ -1,3 +1,4 @@
1
+ import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  // 以调用位置和参数结构识别角色,不绑定压缩后的局部变量名。
2
3
  export function discoverImageActionBinding(source, role) {
3
4
  const patterns = {
@@ -53,6 +54,18 @@ export function discoverFilePreviewComposerBindings(source) {
53
54
  return roles;
54
55
  }
55
56
 
57
+ export function discoverImageCanvasComponents(canvas,layout) {
58
+ const select=(exports,predicate,name)=>{
59
+ const matches=Object.values(exports).filter(value=>typeof value==='function'&&predicate(String(value)));
60
+ if(matches.length!==1)throw new Error(`Native Images canvas role unavailable: ${name}`);
61
+ return matches[0];
62
+ };
63
+ return {
64
+ Canvas:select(canvas,source=>/attachmentSrc:/.test(source)&&/onImageEditSubmit:/.test(source)&&/downloadSrc:/.test(source),'image preview'),
65
+ CanvasLayout:select(layout,source=>/reserveImageComposerSpace:/.test(source)&&/contentOwnsHeader:/.test(source),'preview layout'),
66
+ };
67
+ }
68
+
56
69
  // 构建产物发现只返回当前能力;原生组件呈现不参与引用解析。
57
70
  export function createNativeImageModules({read = async url => (await fetch(url)).text(), load = url => import(url)} = {}) {
58
71
  let starting, connecting, canvasConnecting;
@@ -68,29 +81,27 @@ export function createNativeImageModules({read = async url => (await fetch(url))
68
81
 
69
82
  async function bootstrap() {
70
83
  if (!starting) starting = (async () => {
71
- const urls = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(node => node.href || node.src);
72
- const initialUrl = urls.find(url => /\/app-initial-[^/]+\.js/.test(url));
73
- const mainUrl = urls.find(url => /\/app-main-[^/]+\.js/.test(url));
84
+ const [initialUrl,mainUrl]=await Promise.all([
85
+ findCodexModule('app-initial',{read}),findCodexModule('app-main',{read}),
86
+ ]);
74
87
  if (!initialUrl || !mainUrl) throw new Error("Native Images modules are unavailable");
75
88
  const [initialSource, mainSource] = await Promise.all([read(initialUrl), read(mainUrl)]);
76
89
  const initial = await load(initialUrl);
77
- const asset = (name, references = initialSource) => {
90
+ const asset = async (name, references = initialSource) => {
78
91
  const loaded = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(node => node.href || node.src)
79
92
  .find(url => new RegExp(`/${name}-[\\w-]+\\.js$`).test(url));
80
93
  if (loaded) return loaded;
81
94
  const file = references.match(new RegExp(`(?:["'./])(${name}-[\\w-]+\\.js)`))?.[1];
82
- if (!file) throw new Error(`Native Images capability is unavailable: ${name}`);
83
- return new URL(file, initialUrl).href;
95
+ const url=file ? new URL(file, initialUrl).href : await findCodexModule(name,{read,via:['library-page','library-trash-banner','library-item-menu','pinned-pages','recent-pages','space-trash-items','tab','cloud-file-preview.electron','library-marquee-selection','preview-tab','preview']});
96
+ return url;
84
97
  };
85
98
  const module = async (name,references) => {
86
- // 26.1013 将图库排布与卡片合入入口,其他能力仍按独立模块发现。
87
- const loaded = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].some(node =>
88
- new RegExp(`/${name}-[\\w-]+\\.js$`).test(node.href || node.src));
89
- if (!loaded && ["library-masonry", "library-item-layout"].includes(name)
90
- && !new RegExp(`(?:["'./])(${name}-[\\w-]+\\.js)`).test(references ?? initialSource)) {
91
- return {url:initialUrl, source:initialSource, exports:initial};
99
+ const url = await asset(name,references);
100
+ // 原生曾将这两个角色合入入口;先按实际资源引用查找,再使用已验证的内联分支。
101
+ if (!url && ['library-masonry','library-item-layout'].includes(name)) {
102
+ return {url:initialUrl,source:initialSource,exports:initial};
92
103
  }
93
- const url = asset(name,references);
104
+ if (!url) throw new Error(`Native Images capability is unavailable: ${name}`);
94
105
  const [source, exports] = await Promise.all([read(url), load(url)]);
95
106
  // 调用产物公开的惰性初始化器,不扫描或执行任意导出函数。
96
107
  for (const value of Object.values(exports)) {
@@ -233,14 +244,15 @@ export function createNativeImageModules({read = async url => (await fetch(url))
233
244
  // 文件画布使用原生 Home Composer 的控制器、附件 scope 和输入组件。
234
245
  const composerLocals=Object.values(discoverFilePreviewComposerBindings(filePreview.source));
235
246
  const [useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope,FileComposer,fileComposerAvailability,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute] = await Promise.all(composerLocals.map(local=>binding(filePreview.source,filePreview.url,local)));
236
- if (!canvas.exports.t || !canvasLayout.exports.r || !Composer || !ComposerSurface || !ScopeProvider
247
+ const {Canvas,CanvasLayout}=discoverImageCanvasComponents(canvas.exports,canvasLayout.exports);
248
+ if (!Canvas || !CanvasLayout || !Composer || !ComposerSurface || !ScopeProvider
237
249
  || !composerScope || !composerRouteScope?.parent
238
250
  || !FileComposer || !fileComposerAvailability
239
251
  || [createPortal,useComposerController,useHomeComposer,createComposerValue,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute].some(value=>typeof value!=="function")) {
240
252
  throw new Error("Native Images canvas contract changed");
241
253
  }
242
254
  return {
243
- createPortal,Canvas:canvas.exports.t,CanvasLayout:canvasLayout.exports.r,
255
+ createPortal,Canvas,CanvasLayout,
244
256
  useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope,
245
257
  FileComposer,fileComposerAvailability,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute,
246
258
  editImage:component(initial,source=>/async function/.test(source)&&/requireDispatchAcceptance:/.test(source)&&/sourceAttachment:/.test(source)&&/imageVariant/.test(source),"image edit submission"),
@@ -1,3 +1,4 @@
1
+ import { findCodexModule } from '../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  import { cloudImagesInRenderer } from "../adapters/codex/cloud-import.mjs";
2
3
  import { createLibraryApi } from "../adapters/codex/library-api.mjs";
3
4
  import { promptResolverSource } from "../adapters/codex/prompt-resolver-source.mjs";
@@ -6,5 +7,6 @@ export async function cloudImagesAction(evaluate, operation, payload = {}) {
6
7
  if (!["list", "import"].includes(operation)) throw new Error("Unknown cloud image operation");
7
8
  if (payload.cursor != null && (typeof payload.cursor !== "string" || payload.cursor.length > 4096)) throw new Error("Invalid Library cursor");
8
9
  if (operation === "import" && (typeof payload.libraryId !== "string" || !/^libfile_[\w-]+$/.test(payload.libraryId))) throw new Error("Invalid Library file ID");
9
- return evaluate(`(${cloudImagesInRenderer.toString()})(${JSON.stringify(operation)},${JSON.stringify(payload)},${promptResolverSource()},${createLibraryApi.toString()})`, { timeoutMs: 110_000 });
10
+ return evaluate(`(async()=>{const findCodexModule=${findCodexModule.toString()};
11
+ return (${cloudImagesInRenderer.toString()})(${JSON.stringify(operation)},${JSON.stringify(payload)},${promptResolverSource()},${createLibraryApi.toString()});})()`, { timeoutMs: 110_000 });
10
12
  }
@@ -1,7 +1,8 @@
1
+ import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  import { createCloudImageActions } from "./frontend/cloud-image-actions.mjs";
2
3
  import { createImageSourcesAdapter } from "./adapters/codex/image-sources.mjs";
3
4
  import { createNativeImageSource } from "./adapters/codex/native-image-source.mjs";
4
- import {createNativeImageModules, discoverImageActionBinding, discoverFilePreviewComposerBindings} from "./adapters/codex/native-image-modules.mjs";
5
+ import {createNativeImageModules, discoverImageCanvasComponents, discoverImageActionBinding, discoverFilePreviewComposerBindings} from "./adapters/codex/native-image-modules.mjs";
5
6
  import { installLocalImageSource } from "./frontend/local-image-source.mjs";
6
7
  import { createLibraryApi } from "./adapters/codex/library-api.mjs";
7
8
  import { createLibraryViewerAdapter } from "./adapters/codex/library-viewer.mjs";
@@ -11,7 +12,7 @@ import { promptResolverSource } from "./adapters/codex/prompt-resolver-source.mj
11
12
  export async function createPageScriptSource() {
12
13
  const source=await readFile(new URL("./library-prompt-page.mjs",import.meta.url),"utf8");
13
14
  const promptGlyphSource=await readFile(new URL("./assets/prompts-menu.svg",import.meta.url),"utf8");
14
- const functions={createCloudImageActions,createImageSourcesAdapter,discoverImageActionBinding,discoverFilePreviewComposerBindings,createNativeImageModules,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
15
+ const functions={findCodexModule,discoverImageCanvasComponents,createCloudImageActions,createImageSourcesAdapter,discoverImageActionBinding,discoverFilePreviewComposerBindings,createNativeImageModules,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
15
16
  const declarations=Object.entries(functions).map(([name,fn])=>`const ${name}=${fn.toString()};`).join("\n");
16
17
  return `${declarations}\nconst promptGlyphSource=${JSON.stringify(promptGlyphSource)};\nconst promptFromConversation=${promptResolverSource()};\n${source}`;
17
18
  }
@@ -1,8 +1,7 @@
1
+ import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  // 修补原生 Preview 的链接装饰;源码、语法树与编辑器实例继续归 Codex。
2
3
  export async function loadNativeMarkdownLinks() {
3
- const url = [...document.querySelectorAll('link[rel="modulepreload"]')]
4
- .map((n) => n.href)
5
- .find((u) => /\/text-file-editor-extensions-[^/]+\.js$/.test(u));
4
+ const url = await findCodexModule('text-file-editor-extensions',{via:['text-file-editor-tab-content.electron']});
6
5
  if (!url) throw new Error('native-markdown-link-module-unavailable');
7
6
  const source = await (await fetch(url)).text();
8
7
  const opener = source.match(/([\w$]+)\(\{event:[\w$]+,href:[\w$]+,initiator:`markdown_link_click`/)?.[1];
@@ -1,9 +1,9 @@
1
+ import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  import { collectMarkdownFootnotes } from '../../domain/footnotes.mjs';
2
3
  import { captureMarkdownProviders } from '../../../../../packages/codex-adapter/src/markdown/providers.mjs';
3
4
 
4
5
  // 复用消息流的代码组件与 Markdown lexer;文件内容及编辑器仍由 Codex 持有。
5
6
  export async function loadNativeMarkdownRichText(native) {
6
- const links = [...document.querySelectorAll('link[rel="modulepreload"]')].map((n) => n.href);
7
7
  const { sharedUrl } = native;
8
8
  const sharedSource = await (await fetch(sharedUrl)).text(),
9
9
  shared = await import(sharedUrl);
@@ -37,16 +37,17 @@ export async function loadNativeMarkdownRichText(native) {
37
37
  if (!codeInit || !sharedBinding(codeInit) || !sharedBinding(codeName))
38
38
  throw new Error('native-code-snippet-unavailable');
39
39
  sharedBinding(codeInit)();
40
- let mathUrl = links.find((u) => /\/katex-[^/]+\.js$/.test(u));
40
+ let mathUrl = await findCodexModule('katex');
41
41
  if (!mathUrl) {
42
- const editorUrl = links.find((u) => /\/text-file-editor-extensions-[^/]+\.js$/.test(u));
42
+ const editorUrl = await findCodexModule('text-file-editor-extensions',{via:['text-file-editor-tab-content.electron']});
43
43
  if (editorUrl) {
44
44
  const editorSource = await (await fetch(editorUrl)).text(),
45
45
  specifier = editorSource.match(/import\(`(\.\/katex-[^`]+\.js)`\)/)?.[1];
46
46
  if (specifier) mathUrl = new URL(specifier, editorUrl).href;
47
47
  }
48
48
  }
49
- const katex = mathUrl ? (await import(mathUrl)).default : null;
49
+ const mathModule=mathUrl ? await import(mathUrl) : null;
50
+ const katex=mathModule?.default ?? mathModule?.katex;
50
51
  if (!katex?.renderToString) throw new Error('native-katex-unavailable');
51
52
  return { ...native, CodeSnippet: sharedBinding(codeName), katex };
52
53
  }
@@ -1,3 +1,4 @@
1
+ import { findCodexModule, codexModuleStyles } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  import { loadMarkdownListAPI } from '../../../packages/codex-adapter/src/markdown/editor-api.mjs';
2
3
  import { markdownExportAlias, loadNativeMarkdownTableActions } from '../../../packages/codex-adapter/src/markdown/block-actions.mjs';
3
4
  import { loadNativeMarkdownRenderer } from '../../../packages/codex-adapter/src/markdown/markdown-native.mjs';
@@ -18,7 +19,7 @@ import { installMarkdownTables, markdownTableActionGeometry } from './adapters/c
18
19
  import { installMarkdownLists } from './adapters/codex/lists.mjs';
19
20
  import { createMarkdownPreviewAdapter } from '../../../packages/codex-adapter/src/markdown/preview.mjs';
20
21
  export async function createPageScriptSource() {
21
- const bindings = [
22
+ const bindings = [findCodexModule, codexModuleStyles,
22
23
  collectMarkdownFootnotes,
23
24
  captureMarkdownProviders,
24
25
  createMarkdownReadingRuntime,
@@ -1,9 +1,10 @@
1
+ import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  import { saveQvDiagram } from '../../mermaid/export.mjs';
2
3
 
3
4
  // 图表下载协议只由实验性功能加载,阅读增强不依赖此能力。
4
5
  export async function loadNativeMermaidActions(native, {read = async url => { const response = await fetch(url); if (!response.ok) throw new Error('native-diagram-source-unavailable'); return response.text(); }, load = url => import(url)} = {}) {
5
- const url = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')]
6
- .map(node => node.href || node.src).find(url => /\/app-initial-[^/]+\.js/.test(url));
6
+ const url = await findCodexModule('app-initial',{read});
7
+ if (!url) throw new Error('native-diagram-module-unavailable');
7
8
  const source = await read(url);
8
9
  const section = marker => {
9
10
  const at = source.indexOf(marker);
@@ -1,3 +1,4 @@
1
+ import { findCodexModule, codexModuleStyles } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  import { readFile } from 'node:fs/promises';
2
3
  import { homedir } from 'node:os';
3
4
  import { join } from 'node:path';
@@ -16,7 +17,7 @@ import { loadNativeMermaidActions } from './adapters/codex/native.mjs';
16
17
  import { createMermaidRuntime } from './adapters/codex/runtime.mjs';
17
18
 
18
19
  export async function createPageScriptSource() {
19
- const bindings = [createMarkdownPreviewAdapter, loadMarkdownListAPI, captureMarkdownProviders,
20
+ const bindings = [findCodexModule, codexModuleStyles, createMarkdownPreviewAdapter, loadMarkdownListAPI, captureMarkdownProviders,
20
21
  loadNativeMarkdownToc, loadNativeMarkdownRenderer, markdownExportAlias, loadNativeMarkdownTableActions,
21
22
  createQvResources, createQvFrame, readQvTheme, exportQvDiagram, saveQvDiagram, createQvLoading,
22
23
  createMarkdownMermaidControls, installMarkdownMermaid, loadNativeMermaidActions, createMermaidRuntime];
@@ -1,3 +1,4 @@
1
+ import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  export function createModelControllerAdapter(codex,comparableModelLabel) {
2
3
  function controllerView(props) {const view=Object.fromEntries(["models","model","reasoningEffort","powerSelections","selectedPowerSelection","fallbackPowerSelection","serviceTierOptions","selectedServiceTier","onSelectModel","onSelectReasoningEffort","onSelectPower","onCommitPower","onSelectServiceTier"].map(key=>[key,props[key]]));codex.report('model-controller',true);return view;}
3
4
  function nativeComposerTriggers() {
@@ -83,10 +84,9 @@ function invokeNativeMenuItemSelect(item) {
83
84
  }
84
85
 
85
86
  async function fastIcons(signal) {
86
- const sourceLink=document.querySelector('link[href*="/assets/app-primary-"][href$=".js"]')
87
- ?? document.querySelector('link[href*="/assets/app-initial-"][href$=".js"]');
88
- if (!sourceLink?.href) {codex.report("fast-icons",false,"bundle-unavailable");return null;}
89
- const source=await (await fetch(sourceLink.href,{signal})).text();
87
+ const sourceUrl=await findCodexModule('app-primary',{signal}) ?? await findCodexModule('app-initial',{signal});
88
+ if (!sourceUrl) {codex.report("fast-icons",false,"bundle-unavailable");return null;}
89
+ const source=await (await fetch(sourceUrl,{signal})).text();
90
90
  const active=extractFastIconPath(source,"M11\\.9125 21\\.4125"),inactive=extractFastIconPath(source,"M9\\.80999 17\\.8302");
91
91
  codex.report("fast-icons",Boolean(active && inactive),active && inactive ? null : "icon-resource-contract-changed");
92
92
  return {active:{path:active,viewBox:"0 0 24 24"},inactive:{path:inactive,viewBox:"0 0 20 20"}};
@@ -1,3 +1,4 @@
1
+ import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  import { createModelControllerAdapter } from "./adapters/codex/controller.mjs";
2
3
  import { createModelRules } from "./domain/model-rules.mjs";
3
4
  import { installModelSlider, replaceModelSliderShell } from "./frontend/install.mjs";
@@ -5,6 +6,7 @@ import { installModelSlider, replaceModelSliderShell } from "./frontend/install.
5
6
  // 两部分均显式序列化,Renderer 不捕获 Node imports。
6
7
  export function createModelSliderInstallerSource() {
7
8
  return `function installModelSliderFeature(signal, context) {
9
+ const findCodexModule = ${findCodexModule.toString()};
8
10
  const replaceModelSliderShell = ${replaceModelSliderShell.toString()};
9
11
  return (${installModelSlider.toString()})(signal, context, (${createModelRules.toString()})(), ${createModelControllerAdapter.toString()});
10
12
  }`;
@@ -1,7 +1,8 @@
1
+ import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  import {quickChatLifecycleActions, quickChatScope} from "./lifecycle.mjs";
2
3
  // 通过原生能力检查与已有会话分支定位打开动作,不依赖构建哈希或压缩名称。
3
4
  export function quickChatOpenExport(source) {
4
- const match = source.match(/function ([\w$]+)\(([\w$]+),[\w$]+=\{\}\)\{if\(!\2\.get\([\w$]+,\{name:[`"']chatgpt\.quick-chat[`"']\}\)\.isCapable\)return;let [\w$]+=\2\.get\([\w$]+\),[\w$]+=\2\.get\([\w$]+\);if\(/);
5
+ const match = source.match(/function ([\w$]+)\(([\w$]+),[\w$]+=\{\}\)\{if\(!\2\.get\([\w$]+,\{name:[`"']chatgpt\.quick-chat[`"']\}\)\.isCapable\)return;let [\w$]+=\2\.get\([\w$]+\),[\w$]+=\2\.get\([\w$]+\);(?:if\(|[\w$]+==null\?)/);
5
6
  if (!match) throw new Error('quick-chat-open-contract-unavailable');
6
7
  const escaped = match[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
7
8
  const name = source.slice(source.lastIndexOf('export{')).match(new RegExp(`(?:\\{|,)${escaped} as ([\\w$]+)(?:,|\\})`))?.[1];
@@ -17,7 +18,8 @@ export function quickChatLifecycleExports(source) {
17
18
  const local = exports.match(new RegExp(`(?:\\{|,)([\\w$]+) as ${escaped(open)}(?:,|\\})`))?.[1];
18
19
  const start = source.indexOf(`function ${local}(`);
19
20
  const body = source.slice(start, source.indexOf('function ', start + 9));
20
- const create = body.match(/if\([\w$]+==null\)\{([\w$]+)\([\w$]+,[\w$]+\);return\}/)?.[1];
21
+ const create = body.match(/if\([\w$]+==null\)\{([\w$]+)\([\w$]+,[\w$]+\);return\}/)?.[1]
22
+ ?? body.match(/[\w$]+==null\?([\w$]+)\([\w$]+,[\w$]+\):/)?.[1];
21
23
  const state = body.match(/[\w$]+\.get\(([\w$]+)\)\|\|/)?.[1];
22
24
  const exportOf = name => name && exports.match(new RegExp(`(?:\\{|,)${escaped(name)} as ([\\w$]+)(?:,|\\})`))?.[1];
23
25
  const createExport = exportOf(create), openState = exportOf(state);
@@ -39,7 +41,8 @@ export function quickChatNativeCommand(source) {
39
41
 
40
42
  export function installQuickChatShortcut(signal, codex, discover = null) {
41
43
  const windows = navigator.platform.startsWith('Win');
42
- let bindings = null, disposed = false, pendingFocus = null, stopWaiting = null;
44
+ let bindings = null, disposed = false, pendingFocus = null;
45
+ const loading=new AbortController();
43
46
  function activateExisting(minimize = false) {
44
47
  // 原生恢复按钮同时恢复 presentation、取消最小化并聚焦输入框。
45
48
  for (const button of document.querySelectorAll('button')) {
@@ -95,20 +98,10 @@ export function installQuickChatShortcut(signal, codex, discover = null) {
95
98
  return null;
96
99
  }
97
100
  async function load() {
98
- const findUrl = () => [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(e => e.href || e.src).find(url => /\/app-initial-[^/]+\.js(?:$|\?)/.test(url));
99
- // 宿主可早于原生模块入口注入;等待入口挂载,不能把正常启动顺序当作协议失效。
100
- const url = findUrl() ?? await new Promise((resolve, reject) => {
101
- codex.report('quick-chat-shortcut', false, 'quick-chat-module-pending');
102
- const cleanup = () => { observer.disconnect(); signal.removeEventListener('abort', cancel); stopWaiting=null; };
103
- const cancel = () => { cleanup(); reject(new Error('quick-chat-load-aborted')); };
104
- const check = () => { const found=findUrl(); if (found) { cleanup(); resolve(found); } };
105
- const observer = new MutationObserver(check);
106
- stopWaiting=cancel;
107
- observer.observe(document.documentElement, {childList:true,subtree:true,attributes:true,attributeFilter:['href','src']});
108
- signal.addEventListener('abort', cancel, {once:true});
109
- if (disposed || signal.aborted) cancel(); else check();
110
- });
111
- const source = await (await fetch(url, {signal})).text();
101
+ codex.report('quick-chat-shortcut', false, 'quick-chat-module-pending');
102
+ const url=await findCodexModule('app-initial',{signal:loading.signal,waitForEntry:true});
103
+ if (!url) throw new Error('quick-chat-module-unavailable');
104
+ const source = await (await fetch(url, {signal:loading.signal})).text();
112
105
  const module = await import(url);
113
106
  let native;
114
107
  try { native=quickChatNativeCommand(source); }
@@ -117,7 +110,7 @@ export function installQuickChatShortcut(signal, codex, discover = null) {
117
110
  // 新版命令目录移入共享模块;打开动作仍由入口模块导出。
118
111
  const file=source.match(/["'`]\.\/(app-shared-[\w-]+\.js)["'`]/)?.[1];
119
112
  if (!file) throw error;
120
- const response=await fetch(new URL(file,url).href,{signal});
113
+ const response=await fetch(new URL(file,url).href,{signal:loading.signal});
121
114
  if (!response.ok) throw error;
122
115
  native=quickChatNativeCommand(await response.text());
123
116
  }
@@ -178,7 +171,7 @@ export function installQuickChatShortcut(signal, codex, discover = null) {
178
171
  event.stopPropagation();
179
172
  }
180
173
  document.addEventListener('keydown', keydown, {signal});
181
- function dispose() { disposed = true; stopWaiting?.(); document.removeEventListener('keydown', keydown); bindings = null; pendingFocus=null; }
174
+ function dispose() { disposed = true; loading.abort(); document.removeEventListener('keydown', keydown); bindings = null; pendingFocus=null; }
182
175
  signal.addEventListener('abort', dispose, {once:true});
183
176
  return {ready, dispose, sync:focusNew, activate, close, canClose:() => Boolean(bindings?.close && !disposed && !signal.aborted), available:() => Boolean(bindings && !bindings.native && !disposed && !signal.aborted)};
184
177
  }
@@ -1,11 +1,11 @@
1
+ import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  export async function loadQuickChatSidebarIcon(signal) {
2
- const urls = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(node => node.href || node.src);
3
- const initialUrl = urls.find(url => /\/app-initial-[^/]+\.js/.test(url));
3
+ const initialUrl = await findCodexModule('app-initial',{signal});
4
4
  if (!initialUrl) throw new Error('quick-chat-icon-module-unavailable');
5
5
  const initial = await (await fetch(initialUrl,{signal})).text();
6
- const file = initial.match(/["']\.\/((?:quick-chat-floating-surface|floating-chat-surface)-[\w-]+\.js)["']/)?.[1];
7
- if (!file) throw new Error('quick-chat-icon-module-unavailable');
8
- const url = new URL(file,initialUrl).href;
6
+ const file = initial.match(/["'`]\.\/((?:quick-chat-floating-surface|floating-chat-surface)-[\w-]+\.js)["'`]/)?.[1];
7
+ const url = file ? new URL(file,initialUrl).href : await findCodexModule('floating-chat-surface',{signal,via:['quick-chat-window','page-codex-quick-chat-window','content-chat-surface']}) ?? await findCodexModule('quick-chat-floating-surface',{signal});
8
+ if (!url) throw new Error('quick-chat-icon-module-unavailable');
9
9
  const source = await (await fetch(url,{signal})).text();
10
10
  // 原生 Restore 在未传 minimizedIcon 时使用的默认聊天图标。
11
11
  const local = source.match(/children:[\w$]+\?\?\(0,[\w$]+\.jsx\)\(([\w$]+),\{\}\)/)?.[1];
@@ -1,3 +1,4 @@
1
+ import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  import {quickChatLifecycleActions, quickChatScope} from "./adapters/codex/lifecycle.mjs";
2
3
  import {quickChatHistoryVisible} from "./adapters/codex/history.mjs";
3
4
  import { readFile } from "node:fs/promises";
@@ -9,7 +10,7 @@ import { installQuickChatSidebar } from "./frontend/sidebar.mjs";
9
10
  import { createHistoryTracker } from "./domain/history.mjs";
10
11
  export async function createPageScriptSource() {
11
12
  const source = await readFile(new URL("./frontend/entry.mjs", import.meta.url), "utf8");
12
- const functions = [quickChatHistoryVisible, loadQuickChatSidebarIcon, createQuickChatSidebarAdapter, installQuickChatSidebar,
13
+ const functions = [findCodexModule, quickChatHistoryVisible, loadQuickChatSidebarIcon, createQuickChatSidebarAdapter, installQuickChatSidebar,
13
14
  quickChatLifecycleActions, quickChatScope, quickChatOpenExport, quickChatLifecycleExports, quickChatNativeCommand, installQuickChatShortcut, createQuickChatAdapter,
14
15
  createSubmissionTracker, createHistoryTracker];
15
16
  return functions.map(fn => `const ${fn.name}=${fn.toString()};`).join('\n') + '\n' + source;
@@ -1,3 +1,4 @@
1
+ import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  export function contextUsageExport(source) {
2
3
  // 新版从宿主感知的 tokenUsageInfo selector 派生 thread atom family。
3
4
  const selector = source.match(/([\w$]+)=[\w$]+\(([`"'])tokenUsageInfo\2\),([\w$]+)=[\w$]+\(/);
@@ -38,24 +39,12 @@ export function createContextUsageAdapter(signal, codex, onChange, loadFamily =
38
39
  }
39
40
  async function discover() {
40
41
  if (loadFamily) return loadFamily();
41
- const urls=[...document.querySelectorAll('link[rel=modulepreload],script[src]')].map(node=>node.href || node.src);
42
- const matching = pattern => urls.filter(url=>pattern.test(url));
43
- const queue=[...matching(/\/app-initial-[^/]+\.js(?:$|\?)/),
44
- ...matching(/\/app-shared-[^/]+\.js(?:$|\?)/),
45
- ...matching(/\/(?:app-main|index)-[^/]+\.js(?:$|\?)/)];
46
- if (!queue.length) throw new Error("context-bundle-unavailable");
47
- const visited=new Set();
48
- while (queue.length) {
49
- const url=queue.shift();
50
- if (visited.has(url)) continue;
51
- visited.add(url);
42
+ for (const name of ['app-initial','app-shared']) {
43
+ const url=await findCodexModule(name,{signal});
44
+ if (!url) continue;
52
45
  const source=await (await fetch(url,{signal})).text();
53
46
  const exported=contextUsageExport(source);
54
47
  if (exported) return (await import(url))[exported];
55
- // modulepreload 只是优化提示;沿真实入口引用发现不再预加载的原生状态模块。
56
- const references=[...source.matchAll(/["'`](\.\/app-(?:initial|main|shared)-[^/"'`]+\.js)["'`]/g)]
57
- .map(match=>new URL(match[1],url).href);
58
- queue.push(...references);
59
48
  }
60
49
  throw new Error("context-export-unavailable");
61
50
  }
@@ -1,6 +1,7 @@
1
+ import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
1
2
  import { readFile } from "node:fs/promises";
2
3
  import { contextUsageExport, createContextUsageAdapter } from "./adapters/codex/context-usage.mjs";
3
4
  export async function createPageScriptSource() {
4
5
  const source=await readFile(new URL("./session-status.mjs",import.meta.url),"utf8");
5
- return `const contextUsageExport=${contextUsageExport.toString()};\nconst createContextUsageAdapter=${createContextUsageAdapter.toString()};\n${source}`;
6
+ return `const findCodexModule=${findCodexModule.toString()};\nconst contextUsageExport=${contextUsageExport.toString()};\nconst createContextUsageAdapter=${createContextUsageAdapter.toString()};\n${source}`;
6
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecodex",
3
- "version": "2.0.48",
3
+ "version": "2.0.49",
4
4
  "description": "Lightweight, local-first plugins for the Codex desktop app.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -100,6 +100,7 @@
100
100
  "test:cli": "node --test packages/cli/test/*.test.mjs",
101
101
  "test:runtime": "node --test packages/runtime-host/test/*.test.mjs",
102
102
  "check": "node scripts/check-syntax.mjs",
103
+ "check:native": "node scripts/check-native-capabilities.mjs",
103
104
  "build:service": "scripts/build-service-app.zsh",
104
105
  "pack:check": "npm pack --dry-run",
105
106
  "pack:audit": "node scripts/audit-package.mjs",
@@ -1,3 +1,4 @@
1
+ import { findCodexModule } from '../renderer/modules.mjs';
1
2
  import { loadNativeMarkdownRenderer } from './markdown-native.mjs';
2
3
 
3
4
  export function markdownExportAlias(source, name) {
@@ -13,9 +14,8 @@ export function markdownExportAlias(source, name) {
13
14
 
14
15
  // 从 Chat Markdown 的公开组件发现悬浮操作栏和控件,不绑定构建 hash。
15
16
  export async function loadNativeMarkdownTableActions(native) {
16
- const url = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')]
17
- .map((n) => n.href || n.src)
18
- .find((url) => /\/app-initial-[^/]+\.js/.test(url));
17
+ const url = await findCodexModule('app-initial');
18
+ if (!url) throw new Error('native-table-actions-module-unavailable');
19
19
  const response = await fetch(url);
20
20
  if (!response.ok) throw new Error('native-table-actions-source-unavailable');
21
21
  const source = await response.text();
@@ -1,9 +1,9 @@
1
+ import { findCodexModule } from '../renderer/modules.mjs';
1
2
  // 使用当前编辑器所属的原生 CodeMirror 实例,避免引入第二套编辑器依赖。
2
3
  export async function loadMarkdownListAPI(view) {
3
- const links = [...document.querySelectorAll('link[rel="modulepreload"]')];
4
- for (const link of links) {
5
- if (!/\/dist-[\w-]+\.js$/.test(link.href)) continue;
6
- const exports = Object.values(await import(link.href));
4
+ const urls=await findCodexModule('dist',{all:true});
5
+ for (const url of urls) {
6
+ const exports = Object.values(await import(url));
7
7
  if (!exports.includes(view.constructor)) continue;
8
8
  const Decoration = exports.find((value) => value?.line && value?.replace && value?.mark);
9
9
  const ViewPlugin = exports.find((value) => value?.fromClass && value?.define);
@@ -1,10 +1,12 @@
1
+ import { findCodexModule, codexModuleStyles } from '../renderer/modules.mjs';
1
2
  // 直接导入原生 Marker、目录浮层和 Tooltip;仅按源码调用关系发现公开角色。
2
3
  export async function loadNativeMarkdownToc() {
3
4
  const urls = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(
4
5
  (n) => n.href || n.src,
5
6
  );
6
- const initialUrl = urls.find((url) => /\/app-initial-[^/]+\.js/.test(url));
7
- const mainUrl = urls.find((url) => /\/app-main-[^/]+\.js/.test(url));
7
+ const [initialUrl,mainUrl]=await Promise.all([
8
+ findCodexModule('app-initial'),findCodexModule('app-main'),
9
+ ]);
8
10
  if (!initialUrl || !mainUrl) throw new Error('native-page-toc-modules-unavailable');
9
11
  const read = async (url) => {
10
12
  const response = await fetch(url);
@@ -12,8 +14,10 @@ export async function loadNativeMarkdownToc() {
12
14
  return response.text();
13
15
  };
14
16
  const [initial, main] = await Promise.all([read(initialUrl), read(mainUrl)]);
17
+ const discovered=await findCodexModule('content',{all:true});
15
18
  const candidates = [
16
19
  ...new Set([
20
+ ...discovered,
17
21
  ...urls.filter((url) => /\/content-[^/]+\.js/.test(url)),
18
22
  ...[...initial.matchAll(/["']\.\/((?:content)-[\w-]+\.js)["']/g)].map(
19
23
  (m) => new URL(m[1], initialUrl).href,
@@ -85,18 +89,18 @@ export async function loadNativeMarkdownToc() {
85
89
  }
86
90
  const ReactDOM = (await binding(rootFactory, mainImports))();
87
91
  const css = initial.match(/["']\.\/((?:floating-navigation-rail(?:-layout)?)-[\w-]+\.css)["']/)?.[1];
88
- if (!css) throw new Error('native-page-toc-styles-unavailable');
89
- const cssUrl = new URL(css, initialUrl).href;
90
- if (![...document.querySelectorAll('link[rel="stylesheet"]')].some((n) => n.href === cssUrl)) {
91
- const link = document.createElement('link');
92
- link.rel = 'stylesheet';
93
- link.href = cssUrl;
94
- document.head.append(link);
95
- await new Promise((resolve, reject) => {
96
- link.onload = resolve;
97
- link.onerror = () => reject(new Error('native-page-toc-styles-unavailable'));
92
+ const cssUrls=css ? [new URL(css,initialUrl).href] : codexModuleStyles(initial,initialUrl,sourceUrl);
93
+ if (!cssUrls.length) throw new Error('native-page-toc-styles-unavailable');
94
+ await Promise.all(cssUrls.map(async cssUrl=>{
95
+ if ([...document.querySelectorAll('link[rel="stylesheet"]')].some(node=>node.href===cssUrl)) return;
96
+ const link=document.createElement('link');link.rel='stylesheet';link.href=cssUrl;
97
+ await new Promise((resolve,reject)=>{
98
+ link.onload=resolve;
99
+ link.onerror=()=>reject(new Error('native-page-toc-styles-unavailable'));
100
+ document.head.append(link);
98
101
  });
99
- }
102
+ }));
103
+
100
104
  const component = (predicate) => {
101
105
  const matches = Object.values(layout).filter(
102
106
  (value) => typeof value === 'function' && predicate(String(value)),
@@ -125,7 +125,10 @@ export function createCodexEnvironment() {
125
125
  const unknown = {status:"unknown", planType:null, source:null, key:null};
126
126
  if (signal?.aborted || !auth || auth.isLoading || !auth.userId || !auth.accountId
127
127
  || !["chatgpt", "chatgptAuthTokens"].includes(auth.authMethod)) return unknown;
128
- const data = client?.getQueryData(["accounts", "check"]);
128
+ const data = client?.getQueryCache().findAll({queryKey:["accounts","check"]})
129
+ .filter(query=>query.state.data)
130
+ .sort((a,b)=>(b.state.dataUpdatedAt??0)-(a.state.dataUpdatedAt??0))[0]?.state.data;
131
+ if (data && !Array.isArray(data.accounts)) return unknown;
129
132
  const current = data?.accounts?.find(value => value.id === auth.accountId);
130
133
  // 已有账号清单却找不到当前身份时,不借用上一个账号的登录套餐。
131
134
  if (data && !current) return unknown;
@@ -0,0 +1,66 @@
1
+ // 只沿原生应用入口链读取模块引用;预加载标签不是完整的模块清单。
2
+ export async function findCodexModule(name, {read = null, signal, all = false, waitForEntry = false, via = []} = {}) {
3
+ const links = () => [...document.querySelectorAll('link[rel="modulepreload"],script[src]')]
4
+ .map(node => node.href || node.src).filter(Boolean);
5
+ const entry = url => /\/(?:index|app-(?:main|initial))-[^/]+\.js(?:$|\?)/.test(url);
6
+ let urls=links();
7
+ signal?.throwIfAborted();
8
+ if (waitForEntry && !urls.some(entry)) {
9
+ await new Promise((resolve,reject) => {
10
+ const cleanup=()=>{observer.disconnect();signal?.removeEventListener('abort',abort);};
11
+ const abort=()=>{cleanup();reject(signal.reason);};
12
+ const check=()=>{if(links().some(entry)){cleanup();resolve();}};
13
+ const observer=new MutationObserver(check);
14
+ observer.observe(document.documentElement,{childList:true,subtree:true,attributes:true,attributeFilter:['src','href','rel']});
15
+ signal?.addEventListener('abort',abort,{once:true});
16
+ if(signal?.aborted) abort(); else check();
17
+ });
18
+ urls=links();
19
+ }
20
+ const matches=url=>new URL(url).pathname.split('/').at(-1)?.startsWith(`${name}-`)
21
+ && /\.js(?:$|\?)/.test(url);
22
+ const found=new Set(urls.filter(matches));
23
+ if (found.size && !all) return [...found][0];
24
+ const core=url=>/\/(?:index|app-(?:main|initial|primary|runtime|shared))-[^/]+\.js(?:$|\?)/.test(url)
25
+ || via.some(role=>new URL(url).pathname.split('/').at(-1)?.startsWith(`${role}-`));
26
+ const queue=urls.filter(core).sort((a,b)=>Number(a.includes('/app-shared-'))-Number(b.includes('/app-shared-')));
27
+ const visited=new Set();
28
+ const source=read ?? (async url=>{
29
+ const response=await fetch(url,{signal});
30
+ if(!response.ok) throw new Error('native-module-source-unavailable');
31
+ return response.text();
32
+ });
33
+ while(queue.length) {
34
+ signal?.throwIfAborted();
35
+ const url=queue.shift();
36
+ if(visited.has(url)) continue;
37
+ visited.add(url);
38
+ const text=await source(url);
39
+ signal?.throwIfAborted();
40
+ for(const match of text.matchAll(/["'`](\.\/[\w.-]+\.js)["'`]/g)) {
41
+ const dependency=new URL(match[1],url).href;
42
+ if(matches(dependency)) {found.add(dependency);if(!all)return dependency;}
43
+ if(core(dependency) && !visited.has(dependency))queue.push(dependency);
44
+ }
45
+ }
46
+ return all ? [...found] : null;
47
+ }
48
+
49
+ // 使用原生 lazy import 声明的 CSS 依赖,兼容 Vite 将语义样式名改成纯 hash。
50
+ export function codexModuleStyles(source, ownerUrl, moduleUrl) {
51
+ const table=source.match(/\.f=\[([^\]]+)\]/)?.[1];
52
+ if(!table)return [];
53
+ const dependencies=JSON.parse(`[${table}]`);
54
+ for(const match of source.matchAll(/import\(["'`]([^"'`]+)["'`]\)/g)) {
55
+ if(new URL(match[1],ownerUrl).href!==moduleUrl)continue;
56
+ const call=source.slice(match.index,source.indexOf('import.meta.url',match.index));
57
+ const indices=call.match(/__vite__mapDeps\(\[([\d,]+)\]\)/)?.[1];
58
+ if(!indices)return [];
59
+ return [...new Set(indices.split(',').map(Number).map(index=>{
60
+ const file=dependencies[index];
61
+ if(typeof file!=='string')throw new Error('native-module-style-dependency-unavailable');
62
+ return file;
63
+ }).filter(file=>file.endsWith('.css')).map(file=>new URL(file,ownerUrl).href))];
64
+ }
65
+ return [];
66
+ }
@@ -1,3 +1,4 @@
1
+ import { findCodexModule } from '../../codex-adapter/src/renderer/modules.mjs';
1
2
  import { createRendererSelfCheck, tokenRequirements } from "./renderer-self-check.mjs";
2
3
  import { createCodexEnvironment } from "../../codex-adapter/src/renderer/environment.mjs";
3
4
  // 注入源生成:createInjectionSource 产出注入到 Codex Renderer 的自包含脚本字符串。
@@ -10,7 +11,7 @@ import { MCP_NAVIGATION_ICONS } from "../../plugin/src/navigation-icons.mjs";
10
11
  import { randomBytes } from "node:crypto";
11
12
  import { CODEX_THEME_TOKENS, CODEX_THEME_TOKEN_ALIASES, MINECODEX_THEME_BINDINGS } from "../../ui/src/native/tokens.mjs";
12
13
 
13
- export const RUNTIME_VERSION = 113;
14
+ export const RUNTIME_VERSION = 114;
14
15
  export const HOST_BINDING_NAME = "__codexPersonalHostAction";
15
16
  export const CSP_BOOTSTRAP_VERSION = 1;
16
17
  export const CSP_BOOTSTRAP_KEY = "__mineCodexCspBootstrapVersion";
@@ -124,14 +125,14 @@ export function createInjectionSource(features, {
124
125
  bindingToken = "test-binding-token",
125
126
  runtimeSessionId = randomBytes(16).toString("hex"),
126
127
  } = {}) {
127
- function install(config, installUI, installModelSliderFeature, createCodexEnvironment, createRendererSelfCheck) {
128
+ function install(config, installUI, installModelSliderFeature, createCodexEnvironment, createRendererSelfCheck, findCodexModule) {
128
129
  if (window.top !== window) return;
129
130
  if (!document.documentElement) {
130
131
  // The document bootstrap runs before the root element exists. Defer the
131
132
  // whole install until the DOM is ready instead of crashing on a null
132
133
  // documentElement (which previously left the runtime permanently absent
133
134
  // after a renderer reload).
134
- document.addEventListener("DOMContentLoaded", () => install(config, installUI, installModelSliderFeature, createCodexEnvironment, createRendererSelfCheck), { once: true });
135
+ document.addEventListener("DOMContentLoaded", () => install(config, installUI, installModelSliderFeature, createCodexEnvironment, createRendererSelfCheck, findCodexModule), { once: true });
135
136
  return;
136
137
  }
137
138
  const currentRuntime = window.__codexPersonalRuntime;
@@ -1506,11 +1507,6 @@ export function createInjectionSource(features, {
1506
1507
  // 原生 Detail Tab capability — React fiber/右侧面板探测、原生详情页签打开与线程跳转
1507
1508
  // ========================================================================
1508
1509
 
1509
- function nativeTabModuleUrl() {
1510
- return Array.from(document.querySelectorAll('link[rel="modulepreload"]'))
1511
- .map((link) => link.href)
1512
- .find((url) => /\/assets\/app-initial-[^/]+\.js(?:\?|$)/.test(url)) ?? null;
1513
- }
1514
1510
 
1515
1511
  function reactFiberFor(element) {
1516
1512
  if (!(element instanceof Element)) return null;
@@ -1600,13 +1596,7 @@ export function createInjectionSource(features, {
1600
1596
  }
1601
1597
 
1602
1598
  async function waitForNativeTabModuleUrl() {
1603
- const deadline = performance.now() + 2_000;
1604
- do {
1605
- const moduleUrl = nativeTabModuleUrl();
1606
- if (moduleUrl) return moduleUrl;
1607
- await new Promise((resolve) => setTimeout(resolve, 50));
1608
- } while (performance.now() < deadline);
1609
- return null;
1599
+ return findCodexModule('app-initial',{signal:lifetime.signal,waitForEntry:true});
1610
1600
  }
1611
1601
 
1612
1602
  async function loadNativeTabCapability() {
@@ -1615,7 +1605,7 @@ export function createInjectionSource(features, {
1615
1605
  nativeTabCapabilityPromise = (async () => {
1616
1606
  try {
1617
1607
  const moduleUrl = await waitForNativeTabModuleUrl();
1618
- if (!moduleUrl) throw new Error("Codex app-initial modulepreload was not found");
1608
+ if (!moduleUrl) throw new Error("Codex app-initial module was not found");
1619
1609
  const appModule = await import(moduleUrl);
1620
1610
  const controller = Object.values(appModule).find((value) => (
1621
1611
  value
@@ -2468,7 +2458,7 @@ export function createInjectionSource(features, {
2468
2458
  themeBindings: MINECODEX_THEME_BINDINGS,
2469
2459
  mcpNavigationIcons: MCP_NAVIGATION_ICONS,
2470
2460
  features: runtimeFeatures,
2471
- })}, ${createUIInstallerSource()}, ${createModelSliderInstallerSource()}, ${createCodexEnvironment.toString()}, ${createRendererSelfCheck.toString()})`;
2461
+ })}, ${createUIInstallerSource()}, ${createModelSliderInstallerSource()}, ${createCodexEnvironment.toString()}, ${createRendererSelfCheck.toString()}, ${findCodexModule.toString()})`;
2472
2462
  }
2473
2463
 
2474
2464
  export function createRuntimeReadyContract({