minecodex 2.0.52 → 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.
@@ -3,6 +3,7 @@ import { saveQvDiagram } from '../../mermaid/export.mjs';
3
3
 
4
4
  // 图表下载协议只由实验性功能加载,阅读增强不依赖此能力。
5
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)} = {}) {
6
+ if (native.rspack) return loadRspackMermaidActions(native);
6
7
  const url = await findCodexModule('app-initial',{read});
7
8
  if (!url) throw new Error('native-diagram-module-unavailable');
8
9
  const source = await read(url);
@@ -41,3 +42,17 @@ export async function loadNativeMermaidActions(native, {read = async url => { co
41
42
  return saveQvDiagram(downloadSrc,saveFile,markdownDownloadsDirectory,suggestedFilename);
42
43
  } };
43
44
  }
45
+
46
+
47
+ export function loadRspackMermaidActions(native) {
48
+ const registry=native.rspack;
49
+ const preview=registry.module(source=>source.includes('imagePreviewDialog.label')&&source.includes('suggestedFilename:')&&source.includes('downloadSrc:'),'diagram preview download');
50
+ const call=preview.source.match(/\(0,([\w$]+\.[\w$]+)\)\(\{downloadSrc:[^}]*suggestedFilename:/);
51
+ const icon=call&&preview.source.slice(call.index).match(/children:\(0,[\w$]+\.jsx\)\(([\w$]+\.[\w$]+),\{className:"icon-xs"\}/)?.[1];
52
+ if (!call || !icon) throw new Error('native-diagram-download-unavailable');
53
+ const downloadImage=registry.binding(preview,call[1]),DownloadIcon=registry.binding(preview,icon);
54
+ if (typeof DownloadIcon!=='function' || typeof downloadImage!=='function'
55
+ || !String(downloadImage).includes('workspaceFiles.saveStream')
56
+ || !String(downloadImage).includes('Failed to download image')) throw new Error('native-diagram-download-unavailable');
57
+ return {...native,DownloadIcon,downloadImage};
58
+ }
@@ -14,14 +14,14 @@ import { exportQvDiagram, saveQvDiagram } from './mermaid/export.mjs';
14
14
  import { createQvLoading } from './mermaid/loading.mjs';
15
15
  import { createMarkdownMermaidControls } from './adapters/codex/mermaid-viewer.mjs';
16
16
  import { installMarkdownMermaid } from './adapters/codex/mermaid.mjs';
17
- import { loadNativeMermaidActions } from './adapters/codex/native.mjs';
17
+ import { loadNativeMermaidActions, loadRspackMermaidActions } from './adapters/codex/native.mjs';
18
18
  import { createMermaidRuntime } from './adapters/codex/runtime.mjs';
19
19
 
20
20
  export async function createPageScriptSource() {
21
21
  const bindings = [findCodexModule, codexModuleStyles,codexModuleRegistry,loadRspackMarkdownListAPI,loadRspackMarkdownToc,loadRspackMarkdownTableActions,loadRspackMarkdownRenderer, createMarkdownPreviewAdapter, loadMarkdownListAPI, captureMarkdownProviders,
22
22
  loadNativeMarkdownToc, loadNativeMarkdownRenderer, markdownExportAlias, loadNativeMarkdownTableActions,
23
23
  createQvResources, createQvFrame, readQvTheme, exportQvDiagram, saveQvDiagram, createQvLoading,
24
- createMarkdownMermaidControls, installMarkdownMermaid, loadNativeMermaidActions, createMermaidRuntime];
24
+ createMarkdownMermaidControls, installMarkdownMermaid, loadNativeMermaidActions, loadRspackMermaidActions, createMermaidRuntime];
25
25
  const declarations = bindings.map(binding => `const ${binding.name} = ${binding.toString()};`).join('\n');
26
26
  const entry = await readFile(new URL('./mermaid-diagrams.mjs',import.meta.url),'utf8');
27
27
  return `const markdownDownloadsDirectory=${JSON.stringify(join(homedir(),'Downloads'))};\n${declarations}\n${entry}`;
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecodex",
3
- "version": "2.0.52",
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",
@@ -1,9 +1,8 @@
1
1
  // Markdown 共用原生角色;只沿已注册模块与原生 lazy 声明绑定依赖。
2
2
  export async function loadRspackMarkdownToc(registry) {
3
- if (!registry.entries.some(entry=>entry.source.includes('data-heading-preview'))) {
4
- const page=registry.module(source=>source.includes(').PageEditorContent'),'Pages content loader');
5
- await registry.lazy(page,'PageEditorContent');
6
- }
3
+ // 工厂已注册不等于依赖已初始化;并行加载的阅读与图表增强共用原生 import 完成点。
4
+ const page=registry.module(source=>source.includes(').PageEditorContent'),'Pages content loader');
5
+ await registry.lazy(page,'PageEditorContent');
7
6
  const owner=registry.module(source=>source.includes('data-heading-preview')&&source.includes('page-table-of-contents'),'Pages table of contents');
8
7
  const body=marker=>{
9
8
  const at=owner.source.indexOf(marker),start=owner.source.lastIndexOf('function ',at);
@@ -66,62 +66,80 @@ export function codexModuleStyles(source, ownerUrl, moduleUrl) {
66
66
  }
67
67
 
68
68
  // rspack 的入口公开原生 require;只读取已注册模块,不重新执行模块工厂。
69
- export async function codexModuleRegistry({read = async url => (await fetch(url)).text(), load = url => import(url)} = {}) {
70
- for (const script of document.querySelectorAll('script[src]')) {
71
- const url = script.src;
72
- if (!url) continue;
73
- const source = await read(url);
74
- if (!/export\s*\{[^}]*\bas\s+__webpack_require__\b/.test(source)) continue;
75
- const runtime = (await load(url)).__webpack_require__;
76
- if (typeof runtime !== 'function' || !runtime.m || !runtime.c) throw new Error('native-module-registry-unavailable');
77
- const sourceOf = value => {
78
- try { return Function.prototype.toString.call(value); } catch { return ''; }
79
- };
80
- const entries = Object.entries(runtime.m).map(([id, factory]) => ({id, source:sourceOf(factory)}));
81
- const unique = (values, role) => {
82
- if (values.length !== 1) throw new Error(`Native module contract changed: ${role}`);
83
- return values[0];
84
- };
85
- const resolvedExports = new Map();
86
- const exportsOf = entry => {
87
- if (resolvedExports.has(entry.id)) return resolvedExports.get(entry.id);
88
- const cached = runtime.c[entry.id];
89
- if (!cached || cached.error) throw new Error('native-module-not-loaded');
90
- return cached.exports;
91
- };
92
- const module = (predicate, role) => unique(entries.filter(entry => predicate(entry.source, entry)), role);
93
- const value = (entry, predicate, role) => unique(Object.values(exportsOf(entry)).filter(item => predicate(item, sourceOf(item))), role);
94
- const binding = (entry, reference) => {
95
- const [owner, ...properties] = reference.split('.');
96
- const escaped = owner.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
97
- const id = entry.source.match(new RegExp(`(?:^|[^\\w$])${escaped}=[\\w$]+\\("([^"\\n]+)"\\)`))?.[1];
98
- if (!id) throw new Error('native-module-binding-unavailable');
99
- let result = exportsOf({id});
100
- for (const property of properties) result = result?.[property];
101
- if (result == null) throw new Error('native-module-export-unavailable');
102
- return result;
103
- };
104
- const lazy = async (entry, name) => {
105
- const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
106
- const match = entry.source.match(new RegExp(`[\\w$]+\\.e\\((\\d+)\\)\\.then\\([\\w$]+\\.bind\\([\\w$]+,"([^"\\n]+)"\\)\\)\\.then\\([\\w$]+=>\\(\\{default:[\\w$]+\\.${escaped}\\}\\)\\)`));
107
- let id;
108
- if (match) {
109
- await runtime.e(Number(match[1]));
110
- id=match[2];
111
- } else {
112
- // Pages 与 KaTeX 使用具名异步导入;依赖列表仍完全来自原生声明。
113
- const group=entry.source.match(new RegExp(`Promise\\.all\\(\\[([^\\]]+)\\]\\)\\.then\\([\\w$]+\\.bind\\([\\w$]+,"([^"\\n]+)"\\)\\)(?:\\)\\.${escaped}\\b|\\.then\\(\\(\\{${escaped}(?=[:,}]))`));
114
- if(!group || !/^(?:[\w$]+\.e\(\d+\),?)+$/.test(group[1])) throw new Error('native-module-lazy-contract-unavailable');
115
- await Promise.all([...group[1].matchAll(/\.e\((\d+)\)/g)].map(item=>runtime.e(Number(item[1]))));
116
- id=group[2];
117
- }
118
- const exports = await runtime(id);
119
- if (exports[name] == null) throw new Error('native-module-lazy-export-unavailable');
120
- resolvedExports.set(id,exports);
121
- entries.splice(0,entries.length,...Object.entries(runtime.m).map(([id,factory])=>({id,source:sourceOf(factory)})));
122
- return {id, source:sourceOf(runtime.m[id])};
123
- };
124
- return {entries, module, value, binding, lazy, exports:exportsOf, sourceOf};
69
+ export async function codexModuleRegistry({read = async url => (await fetch(url)).text(), load = url => import(url), signal, waitForEntry = false} = {}) {
70
+ const scripts=()=>[...document.querySelectorAll('script[src]')];
71
+ const visited=new Set();
72
+ while (true) {
73
+ signal?.throwIfAborted();
74
+ for (const script of scripts()) {
75
+ signal?.throwIfAborted();
76
+ if (visited.has(script.src)) continue;
77
+ visited.add(script.src);
78
+ const url = script.src;
79
+ if (!url) continue;
80
+ const source = await read(url);
81
+ if (!/export\s*\{[^}]*\bas\s+__webpack_require__\b/.test(source)) continue;
82
+ const runtime = (await load(url)).__webpack_require__;
83
+ if (typeof runtime !== 'function' || !runtime.m || !runtime.c) throw new Error('native-module-registry-unavailable');
84
+ const sourceOf = value => {
85
+ try { return Function.prototype.toString.call(value); } catch { return ''; }
86
+ };
87
+ const entries = Object.entries(runtime.m).map(([id, factory]) => ({id, source:sourceOf(factory)}));
88
+ const unique = (values, role) => {
89
+ if (values.length !== 1) throw new Error(`Native module contract changed: ${role}`);
90
+ return values[0];
91
+ };
92
+ const resolvedExports = new Map();
93
+ const exportsOf = entry => {
94
+ if (resolvedExports.has(entry.id)) return resolvedExports.get(entry.id);
95
+ const cached = runtime.c[entry.id];
96
+ if (!cached || cached.error) throw new Error('native-module-not-loaded');
97
+ return cached.exports;
98
+ };
99
+ const module = (predicate, role) => unique(entries.filter(entry => predicate(entry.source, entry)), role);
100
+ const value = (entry, predicate, role) => unique(Object.values(exportsOf(entry)).filter(item => predicate(item, sourceOf(item))), role);
101
+ const binding = (entry, reference) => {
102
+ const [owner, ...properties] = reference.split('.');
103
+ const escaped = owner.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
104
+ const id = entry.source.match(new RegExp(`(?:^|[^\\w$])${escaped}=[\\w$]+\\("([^"\\n]+)"\\)`))?.[1];
105
+ if (!id) throw new Error('native-module-binding-unavailable');
106
+ let result = exportsOf({id});
107
+ for (const property of properties) result = result?.[property];
108
+ if (result == null) throw new Error('native-module-export-unavailable');
109
+ return result;
110
+ };
111
+ const lazy = async (entry, name) => {
112
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
113
+ const match = entry.source.match(new RegExp(`[\\w$]+\\.e\\((\\d+)\\)\\.then\\([\\w$]+\\.bind\\([\\w$]+,"([^"\\n]+)"\\)\\)\\.then\\([\\w$]+=>\\(\\{default:[\\w$]+\\.${escaped}\\}\\)\\)`));
114
+ let id;
115
+ if (match) {
116
+ await runtime.e(Number(match[1]));
117
+ id=match[2];
118
+ } else {
119
+ // Pages 与 KaTeX 使用具名异步导入;依赖列表仍完全来自原生声明。
120
+ const group=entry.source.match(new RegExp(`Promise\\.all\\(\\[([^\\]]+)\\]\\)\\.then\\([\\w$]+\\.bind\\([\\w$]+,"([^"\\n]+)"\\)\\)(?:\\)\\.${escaped}\\b|\\.then\\(\\(\\{${escaped}(?=[:,}]))`));
121
+ if(!group || !/^(?:[\w$]+\.e\(\d+\),?)+$/.test(group[1])) throw new Error('native-module-lazy-contract-unavailable');
122
+ await Promise.all([...group[1].matchAll(/\.e\((\d+)\)/g)].map(item=>runtime.e(Number(item[1]))));
123
+ id=group[2];
124
+ }
125
+ const exports = await runtime(id);
126
+ if (exports[name] == null) throw new Error('native-module-lazy-export-unavailable');
127
+ resolvedExports.set(id,exports);
128
+ entries.splice(0,entries.length,...Object.entries(runtime.m).map(([id,factory])=>({id,source:sourceOf(factory)})));
129
+ return {id, source:sourceOf(runtime.m[id])};
130
+ };
131
+ return {entries, module, value, binding, lazy, exports:exportsOf, sourceOf};
132
+ }
133
+ if (!waitForEntry || scripts().some(script=>/\/(?:index|app-(?:main|initial))-[^/]+\.js(?:$|\?)/.test(script.src))) return null;
134
+ // HTML 仍在逐个挂载入口脚本时,首个 chunk 不一定拥有注册表。
135
+ await new Promise((resolve,reject)=>{
136
+ const cleanup=()=>{observer.disconnect();signal?.removeEventListener('abort',abort);};
137
+ const abort=()=>{cleanup();reject(signal.reason);};
138
+ const check=()=>{if(scripts().some(script=>!visited.has(script.src))){cleanup();resolve();}};
139
+ const observer=new MutationObserver(check);
140
+ observer.observe(document.documentElement,{childList:true,subtree:true,attributes:true,attributeFilter:['src']});
141
+ signal?.addEventListener('abort',abort,{once:true});
142
+ if(signal?.aborted)abort();else check();
143
+ });
125
144
  }
126
- return null;
127
145
  }