minecodex 2.0.13 → 2.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,16 +10,16 @@ MineCodex 给 Codex Desktop 增加一组轻量、本地优先的扩展功能。
10
10
 
11
11
  | 插件 | 说明 |
12
12
  | --- | --- |
13
- | **Model Slider** | 更清晰、更顺滑的模型切换面板:具备品牌标识、常用收藏与流畅交互 |
14
- | **Prompts** | 专页分类管理提示词与调用统计,输入框 Quick Prompts 快速调用 |
15
- | **Usage Meter** | 在 Help 与 Account 之间显示剩余额度环,悬浮查看周 / 五小时额度,点击进入 Usage 设置 |
16
- | **Images** | 在原生 Images 中浏览本地 Image Gen 图片,查看并复制提示词 |
17
- | **Hide Upsell Banner** | 隐藏输入框上方的用量提示横幅,仅优化界面展示,不影响正常的用量统计 |
18
- | **Session Status** | 在输入框下方查看当前任务的步骤、观测速度、Token 用量与上下文明细 |
19
- | **Sidebar Focus** | 展开侧栏时聚焦当前任务路径,再次点击恢复布局 |
20
- | **Sites** | 在原生 Sites 中收录项目网页与自定义网址,按需打开或启动 |
21
- | **Markdown Reading** | 用 Pages 正文排版、自适应留白与原生目录阅读文件面板的 Markdown Preview,保留原生工具栏与文字选择;默认关闭 |
22
- | **File Navigation** | 精简路径与分列文件浏览;在 Markdown 阅读卡片内独立勾选,支持单独启用,默认关闭 |
13
+ | **模型窗口** · Model Panel | 快速切换模型和思考强度,收藏常用模型。 |
14
+ | **提示词库** · Prompt Library | 分类保存常用提示词,需要时一键填入输入框。 |
15
+ | **额度环** · Usage Ring | 在侧栏查看五小时和每周剩余额度,点击打开用量详情。 |
16
+ | **会话状态** · Session Status | 查看回复速度、Token 用量和上下文占用,了解对话消耗。 |
17
+ | **Markdown 阅读增强** · Markdown Reader | 让 Markdown 文档更易读,支持章节目录、表格展开和代码复制。 |
18
+ | **文件级联浏览** · Cascading File Browser | 点击文件顶部路径,分列浏览文件夹并快速切换文件。 |
19
+ | **图片集合** · Image Collection | 集中浏览生成的图片,查看提示词并复用创作思路。 |
20
+ | **站点收藏** · Saved Sites | 发现项目正在运行的网页,收藏常用网址,随时打开。 |
21
+ | **侧栏聚焦** · Sidebar Focus | 一键收起侧栏中的其他分组,再次点击恢复原来的展开状态。 |
22
+ | **隐藏额度推广** · Hide Usage Promotions | 隐藏输入框上方的套餐升级推广,让对话更清爽。 |
23
23
 
24
24
  ## 安装
25
25
 
@@ -211,35 +211,9 @@ function createPluginCard(feature) {
211
211
  return card;
212
212
  }
213
213
 
214
- function createPluginOption(feature) {
215
- const option = element("div", "plugin-option");
216
- option.dataset.featureId = feature.id;
217
- const label = element("label");
218
- const input = element("input");
219
- input.type = "checkbox";
220
- input.name = `feature-${feature.id}`;
221
- const name = element("span", "plugin-option-name");
222
- const dot = element("span", "status-dot");
223
- label.append(input, name, dot);
224
- option.append(label);
225
- return option;
226
- }
227
-
228
214
  function updatePluginCard(card, feature) {
229
215
  for (const button of elements.languageButtons) button.disabled = pendingFeatureIds.size > 0;
230
216
  const status = featureState(feature);
231
- if (card.classList.contains("plugin-option")) {
232
- const input = card.querySelector("input");
233
- input.checked = Boolean(feature.enabled);
234
- input.disabled = pendingFeatureIds.has(feature.id);
235
- input.setAttribute("aria-label", feature.label);
236
- card.querySelector(".plugin-option-name").textContent = feature.label;
237
- const dot = card.querySelector(".status-dot");
238
- dot.className = `status-dot ${status.className}`;
239
- dot.setAttribute("aria-label", status.label);
240
- dot.title = status.label;
241
- return;
242
- }
243
217
  const input = card.querySelector('input[type="checkbox"]');
244
218
  const image = card.querySelector(".plugin-icon img");
245
219
  const name = card.querySelector(".plugin-name");
@@ -247,7 +221,7 @@ function updatePluginCard(card, feature) {
247
221
  const dot = card.querySelector(".plugin-state .status-dot");
248
222
  const stateLabel = card.querySelector(".plugin-state-label");
249
223
  card.dataset.featureId = feature.id;
250
- image.src = `${feature.iconUrl}?v=stroke-2.1`;
224
+ image.src = `${feature.iconUrl}?v=resource-1`;
251
225
  name.textContent = feature.label;
252
226
  description.textContent = feature.description;
253
227
  input.checked = Boolean(feature.enabled);
@@ -271,16 +245,9 @@ function renderPluginCards(features) {
271
245
  for (const feature of features) {
272
246
  let card = findPluginCard(feature.id);
273
247
  if (!card) {
274
- if (feature.id === "file-navigation") {
275
- const parent = findPluginCard("markdown-reading");
276
- if (!parent) continue;
277
- card = createPluginOption(feature);
278
- parent.append(card);
279
- } else {
280
- card = createPluginCard(feature);
281
- elements.pluginList.append(card);
282
- }
248
+ card = createPluginCard(feature);
283
249
  }
250
+ elements.pluginList.append(card);
284
251
  updatePluginCard(card, feature);
285
252
  }
286
253
  for (const card of [...elements.pluginList.querySelectorAll("[data-feature-id]")]) {
@@ -7,7 +7,7 @@ const t = (cn, en) => zh ? cn : en;
7
7
  const dialog = document.querySelector('#data-dialog');
8
8
  const entry = document.querySelector('#data-button');
9
9
  const list = document.querySelector('#data-list');
10
- const featureNames = { notes: 'Prompts', sites: 'Sites', 'model-slider': t('模型滑条', 'Model Slider') };
10
+ const featureNames = { notes: t('提示词库', 'Prompt Library'), sites: t('站点收藏', 'Saved Sites'), 'model-slider': t('模型窗口', 'Model Panel') };
11
11
  const status = document.querySelector('#data-status');
12
12
  const exportButton = document.querySelector('#data-export');
13
13
  const importButton = document.querySelector('#data-import');
@@ -69,7 +69,7 @@ function showInventory(data) {
69
69
  for (const feature of data.features.filter(item => item.portable)) {
70
70
  const detail = feature.id === 'notes' ? t(`${feature.categories} 个分类`, `${feature.categories} categories`) : '';
71
71
  const hint = feature.id === 'model-slider' && !feature.initialized
72
- ? t('旧收藏在新版模型滑条首次启动后收录。', 'Legacy favorites are collected when the updated slider first starts.') + ' ' + featureHints[feature.id]
72
+ ? t('旧收藏在新版模型窗口首次启动后收录。', 'Legacy favorites are collected when the updated slider first starts.') + ' ' + featureHints[feature.id]
73
73
  : featureHints[feature.id];
74
74
  row(featureNames[feature.id] || feature.id, detail, t(`${feature.count} 项`, `${feature.count} items`), hint);
75
75
  }
@@ -186,8 +186,8 @@ h2 { font-size: 14px; font-weight: 620; }
186
186
  .plugin-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--grid-gap); }
187
187
  .plugin-card { display: flex; flex-direction: column; align-items: stretch; min-width: 0; min-height: 216px; padding: var(--card-padding); border: 1px solid var(--border); border-radius: var(--card-radius); background: var(--surface); box-shadow: 0 3px 0 var(--block-shadow); }
188
188
  .plugin-topline { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
189
- .plugin-icon { display: grid; place-items: center; width: 28px; height: 28px; }
190
- .plugin-icon img { width: 24px; height: 24px; opacity: 0.75; }
189
+ .plugin-icon { display: grid; place-items: center; width: 28px; height: 28px; flex: none; }
190
+ .plugin-icon img { display: block; width: 24px; height: 24px; object-fit: contain; opacity: 0.75; }
191
191
  .plugin-copy { flex: 1; }
192
192
  .plugin-name { font-size: 16px; font-weight: 600; line-height: 1.5; letter-spacing: -0.01em; }
193
193
  .plugin-description { margin-top: 8px; font-size: 14px; line-height: 1.65; }
@@ -266,10 +266,6 @@ h2 { font-size: 14px; font-weight: 620; }
266
266
  *, *::before, *::after { scroll-behavior: auto !important; animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
267
267
  }
268
268
 
269
- .plugin-option { margin-top:12px; padding-top:12px; border-top:1px solid var(--border); }
270
- .plugin-option label { display:flex; align-items:center; gap:8px; font-size:13px; }
271
- .plugin-option input { margin:0; accent-color:var(--ink); }
272
- .plugin-option .status-dot { margin-left:auto; }
273
269
 
274
270
  /* 数据迁移沿用控制台的不透明表面与卡片尺寸。 */
275
271
  .data-dialog { width: min(520px, calc(100vw - 32px)); max-height: calc(100dvh - 48px); padding: 24px; border: 1px solid var(--border); border-bottom: 3px solid var(--block-shadow); border-radius: var(--card-radius); background: var(--surface); color: var(--ink); box-shadow: var(--shadow); overflow-y: auto; }
@@ -0,0 +1,16 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ width="24"
4
+ height="24"
5
+ viewBox="0 0 24 24"
6
+ fill="none"
7
+ stroke="currentColor"
8
+ stroke-width="2"
9
+ stroke-linecap="round"
10
+ stroke-linejoin="round"
11
+ >
12
+ <path d="M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 15 3h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" />
13
+ <path d="M20 21a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-2.9a1 1 0 0 1-.88-.55l-.42-.85a1 1 0 0 0-.92-.6H13a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" />
14
+ <path d="M3 5a2 2 0 0 0 2 2h3" />
15
+ <path d="M3 3v13a2 2 0 0 0 2 2h3" />
16
+ </svg>
@@ -3,12 +3,12 @@
3
3
  "id": "file-navigation",
4
4
  "version": "0.1.0",
5
5
  "label": {
6
- "en": "Compact file navigation",
7
- "zh-CN": "精简文件导航"
6
+ "en": "Cascading File Browser",
7
+ "zh-CN": "文件级联浏览"
8
8
  },
9
9
  "description": {
10
- "en": "Browse folders in columns from a compact file path.",
11
- "zh-CN": "精简文件路径,使用分列列表浏览文件夹并切换文件。"
10
+ "en": "Click the path above a file to browse folders in columns and open another file.",
11
+ "zh-CN": "点击文件顶部路径,分列浏览文件夹并快速切换文件。"
12
12
  },
13
13
  "entry": {
14
14
  "kind": "page-script",
@@ -19,7 +19,7 @@
19
19
  }
20
20
  },
21
21
  "icon": {
22
- "resource": "assets/book-open.svg"
22
+ "resource": "assets/folder-tree.svg"
23
23
  },
24
24
  "placement": {
25
25
  "order": 91
@@ -3,12 +3,12 @@
3
3
  "id": "hide-upsell-banner",
4
4
  "version": "0.1.11",
5
5
  "label": {
6
- "en": "Hide Upsell Banner",
7
- "zh-CN": "隐藏用量横幅"
6
+ "en": "Hide Usage Promotions",
7
+ "zh-CN": "隐藏额度推广"
8
8
  },
9
9
  "description": {
10
- "en": "Hide usage upsell banners above the composer.",
11
- "zh-CN": "隐藏输入框上方的额度推广横幅。"
10
+ "en": "Hide plan upgrade banners above the message box for a cleaner chat.",
11
+ "zh-CN": "隐藏输入框上方的套餐升级推广,让对话更清爽。"
12
12
  },
13
13
  "entry": {
14
14
  "kind": "page-script",
@@ -3,12 +3,12 @@
3
3
  "id": "images",
4
4
  "version": "0.1.12",
5
5
  "label": {
6
- "en": "Images",
7
- "zh-CN": "图片"
6
+ "en": "Image Collection",
7
+ "zh-CN": "图片集合"
8
8
  },
9
9
  "description": {
10
- "en": "Browse generated images and copy their prompts.",
11
- "zh-CN": "浏览本地生成图片,查看与复制提示词。"
10
+ "en": "Browse generated images in one place and revisit their prompts.",
11
+ "zh-CN": "集中浏览生成的图片,查看提示词并复用创作思路。"
12
12
  },
13
13
  "icon": {
14
14
  "viewBox": "0 0 24 24",
@@ -1,3 +1,14 @@
1
+ // 以调用位置和参数结构识别角色,不绑定压缩后的局部变量名。
2
+ export function discoverImageActionBinding(source, role) {
3
+ const patterns = {
4
+ switchMode: /([\w$]+)\([\w$]+,[\w$]+\);let [\w$]+=[\w$]+\.length===1/,
5
+ attachChatImages: /await ([\w$]+)\([\w$]+,[\w$]+,\[[\w$]+\],\{isTemporaryChat:/,
6
+ };
7
+ const match = source.match(patterns[role]);
8
+ if (!match) throw new Error(`Native Images action contract changed: ${role}`);
9
+ return match[1];
10
+ }
11
+
1
12
  // 按文件预览的调用关系读取角色,不依赖构建时分配的短变量名。
2
13
  export function discoverFilePreviewComposerBindings(source) {
3
14
  const starts = [...source.matchAll(/function [\w$]+\(/g)].map(match => match.index);
@@ -111,7 +122,7 @@ export function createNativeImageModules({read = async url => (await fetch(url))
111
122
  const [moveDialog,dialogs]=await Promise.all([module("library-move-dialog",selection.source),module("library-item-dialogs",selection.source)]);
112
123
  const routerLocal = selection.source.match(/get\(([\w$]+)\)\.prepareNavigation\(/)?.[1];
113
124
  const routerAtom = await binding(selection.source, selection.url, routerLocal);
114
- const switchLocal = selection.source.match(/([\w$]+)\(e,i\);let _=t.length===1/)?.[1];
125
+ const switchLocal = discoverImageActionBinding(selection.source, "switchMode");
115
126
  const switchMode = await binding(selection.source, selection.url, switchLocal);
116
127
  const draftLocal = selection.source.match(/let u=([\w$]+)\(e,\[\]\);e.set/)?.[1];
117
128
  const createChatDraft = await binding(selection.source, selection.url, draftLocal);
@@ -140,7 +151,7 @@ export function createNativeImageModules({read = async url => (await fetch(url))
140
151
  const imageActions = component(menu.exports,source=>/onOpenedInNewTab:/.test(source),"image actions");
141
152
  const imageActionSource = String(imageActions);
142
153
  const imageFileLocal = imageActionSource.match(/let\{file:[\w$]+\}=([\w$]+)\(/)?.[1];
143
- const attachLocal = imageActionSource.match(/await ([\w$]+)\(e,i,\[n\],/)?.[1];
154
+ const attachLocal = discoverImageActionBinding(imageActionSource, "attachChatImages");
144
155
  const downloadLocal = imageActionSource.match(/await ([\w$]+)\(\{downloadSrc:/)?.[1];
145
156
  const [imageFile,attachChatImages,downloadImage] = await Promise.all([imageFileLocal,attachLocal,downloadLocal].map(local=>binding(menuCoreSource,menuCoreUrl,local)));
146
157
  const toolbarClass = selection.source.match(/className:`(pointer-events-auto flex h-toolbar[^`]+)`/)?.[1];
@@ -1,7 +1,7 @@
1
1
  import { createCloudImageActions } from "./frontend/cloud-image-actions.mjs";
2
2
  import { createImageSourcesAdapter } from "./adapters/codex/image-sources.mjs";
3
3
  import { createNativeImageSource } from "./adapters/codex/native-image-source.mjs";
4
- import {createNativeImageModules, discoverFilePreviewComposerBindings} from "./adapters/codex/native-image-modules.mjs";
4
+ import {createNativeImageModules, discoverImageActionBinding, discoverFilePreviewComposerBindings} from "./adapters/codex/native-image-modules.mjs";
5
5
  import { installLocalImageSource } from "./frontend/local-image-source.mjs";
6
6
  import { createLibraryApi } from "./adapters/codex/library-api.mjs";
7
7
  import { createLibraryViewerAdapter } from "./adapters/codex/library-viewer.mjs";
@@ -11,7 +11,7 @@ import { promptResolverSource } from "./adapters/codex/prompt-resolver-source.mj
11
11
  export async function createPageScriptSource() {
12
12
  const source=await readFile(new URL("./library-prompt-page.mjs",import.meta.url),"utf8");
13
13
  const promptGlyphSource=await readFile(new URL("./assets/prompts-menu.svg",import.meta.url),"utf8");
14
- const functions={createCloudImageActions,createImageSourcesAdapter,discoverFilePreviewComposerBindings,createNativeImageModules,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
14
+ const functions={createCloudImageActions,createImageSourcesAdapter,discoverImageActionBinding,discoverFilePreviewComposerBindings,createNativeImageModules,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
15
15
  const declarations=Object.entries(functions).map(([name,fn])=>`const ${name}=${fn.toString()};`).join("\n");
16
16
  return `${declarations}\nconst promptGlyphSource=${JSON.stringify(promptGlyphSource)};\nconst promptFromConversation=${promptResolverSource()};\n${source}`;
17
17
  }
@@ -3,12 +3,12 @@
3
3
  "id": "markdown-reading",
4
4
  "version": "0.1.0",
5
5
  "label": {
6
- "en": "Markdown Reading",
7
- "zh-CN": "Markdown 阅读排版"
6
+ "en": "Markdown Reader",
7
+ "zh-CN": "Markdown 阅读增强"
8
8
  },
9
9
  "description": {
10
- "en": "Read Markdown previews with Pages typography, spacing, and native outline.",
11
- "zh-CN": "用 Pages 的正文排版、舒适留白与原生目录阅读 Markdown 预览。"
10
+ "en": "Read Markdown with a chapter outline, expandable tables, and copyable code blocks.",
11
+ "zh-CN": "让 Markdown 文档更易读,支持章节目录、表格展开和代码复制。"
12
12
  },
13
13
  "entry": {
14
14
  "kind": "page-script",
@@ -3,12 +3,12 @@
3
3
  "id": "model-slider",
4
4
  "version": "0.1.12",
5
5
  "label": {
6
- "en": "Model Slider",
7
- "zh-CN": "模型滑条"
6
+ "en": "Model Panel",
7
+ "zh-CN": "模型窗口"
8
8
  },
9
9
  "description": {
10
- "en": "Switch models and thinking levels. Keep favorites within reach.",
11
- "zh-CN": "切换模型与思考强度,快速找到常用收藏。"
10
+ "en": "Switch models and thinking levels, and keep favorites close at hand.",
11
+ "zh-CN": "快速切换模型和思考强度,收藏常用模型。"
12
12
  },
13
13
  "entry": {
14
14
  "kind": "composer-model-selector"
@@ -3,12 +3,12 @@
3
3
  "id": "notes",
4
4
  "version": "0.1.11",
5
5
  "label": {
6
- "en": "Prompts",
7
- "zh-CN": "提示词"
6
+ "en": "Prompt Library",
7
+ "zh-CN": "提示词库"
8
8
  },
9
9
  "description": {
10
- "en": "Organize reusable prompts and insert them with one click.",
11
- "zh-CN": "整理常用提示词,一键填入输入框。"
10
+ "en": "Save prompts by category and insert them into your message with one click.",
11
+ "zh-CN": "分类保存常用提示词,需要时一键填入输入框。"
12
12
  },
13
13
  "surfaceUrl": "http://127.0.0.1:47832/",
14
14
  "healthUrl": "http://127.0.0.1:47832/api/health",
@@ -2,10 +2,13 @@
2
2
  "schemaVersion": 1,
3
3
  "id": "session-status",
4
4
  "version": "0.1.0",
5
- "label": { "en": "Session Status", "zh-CN": "会话状态" },
5
+ "label": {
6
+ "en": "Session Status",
7
+ "zh-CN": "会话状态"
8
+ },
6
9
  "description": {
7
- "en": "See response speed, token usage and context space.",
8
- "zh-CN": "查看输出速度、Token 用量与上下文占用。"
10
+ "en": "See response speed, token usage, and how much context your chat uses.",
11
+ "zh-CN": "查看回复速度、Token 用量和上下文占用,了解对话消耗。"
9
12
  },
10
13
  "entry": { "kind": "page-script", "script": "src/session-status.mjs", "sourceFactory": "src/injection-source.mjs" },
11
14
  "icon": {
@@ -7,8 +7,8 @@
7
7
  "zh-CN": "侧栏聚焦"
8
8
  },
9
9
  "description": {
10
- "en": "Fold unrelated branches to keep the current task in view.",
11
- "zh-CN": "收起无关分支,专注当前任务路径。"
10
+ "en": "Collapse other sidebar groups with one click, then click again to restore them.",
11
+ "zh-CN": "一键收起侧栏中的其他分组,再次点击恢复原来的展开状态。"
12
12
  },
13
13
  "entry": {
14
14
  "kind": "page-script",
@@ -4,7 +4,7 @@ const EXPAND_GLYPH = "<svg aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/200
4
4
 
5
5
 
6
6
  const states = new Map();
7
- let active = false, restoring = false, button, timer, disposed = false;
7
+ let active = false, restoring = false, button, timer = null, disposed = false;
8
8
  const text = (en, cn) => (document.documentElement.lang || navigator.language).startsWith("zh") ? cn : en;
9
9
 
10
10
 
@@ -74,8 +74,8 @@ function reconcile() {
74
74
  if (change) { change.toggle(); schedule(); }
75
75
  }
76
76
  function schedule() {
77
- clearTimeout(timer);
78
- timer = setTimeout(reconcile, 40);
77
+ if (disposed || timer !== null) return;
78
+ timer = requestAnimationFrame(() => { timer = null; reconcile(); });
79
79
  }
80
80
  document.addEventListener("click", event => {
81
81
  if (!active || !event.isTrusted) return;
@@ -83,7 +83,15 @@ document.addEventListener("click", event => {
83
83
  active = false; states.clear(); schedule();
84
84
  }
85
85
  }, { capture:true, signal });
86
- const observer = new MutationObserver(schedule);
86
+ const observer = new MutationObserver(records => {
87
+ // 流式回复等侧栏外更新不应推迟或重复触发布局整理。
88
+ if (records.some(record =>
89
+ record.target.closest?.("#app-shell-sidebar")
90
+ || record.type === "attributes" && ["data-above-composer-conversation-id", "lang"].includes(record.attributeName)
91
+ || [...record.addedNodes ?? [], ...record.removedNodes ?? []].some(node =>
92
+ node.matches?.("#app-shell-sidebar") || node.querySelector?.("#app-shell-sidebar"))
93
+ )) schedule();
94
+ });
87
95
  observer.observe(document.documentElement, { subtree:true, childList:true, attributes:true, attributeFilter:[
88
96
  "aria-expanded", "aria-pressed", "data-app-action-sidebar-thread-selected",
89
97
  "data-app-action-sidebar-section-collapsed", "data-above-composer-conversation-id", "lang"
@@ -97,4 +105,4 @@ config.registerSelfCheck?.(() => {
97
105
  return {state:available ? "available" : "unavailable",reason:available ? null : "sidebar-focus-anchor-unavailable",
98
106
  checks:[{name:"sidebar-focus-anchor",available}],scopes:[{name:"sidebar",element:sidebar}]};
99
107
  });
100
- return () => { disposed = true; clearTimeout(timer); observer.disconnect(); button?.remove(); };
108
+ return () => { disposed = true; cancelAnimationFrame(timer); observer.disconnect(); button?.remove(); };
@@ -3,12 +3,12 @@
3
3
  "id": "sites",
4
4
  "version": "0.1.0",
5
5
  "label": {
6
- "en": "Local & Self-hosted Sites",
7
- "zh-CN": "本地与自托管站点"
6
+ "en": "Saved Sites",
7
+ "zh-CN": "站点收藏"
8
8
  },
9
9
  "description": {
10
- "en": "Find project web services and open your saved sites.",
11
- "zh-CN": "发现项目网页服务,收藏并打开常用站点。"
10
+ "en": "Find running project sites and save useful links for quick access.",
11
+ "zh-CN": "发现项目正在运行的网页,收藏常用网址,随时打开。"
12
12
  },
13
13
  "entry": {
14
14
  "kind": "page-script",
@@ -2,10 +2,13 @@
2
2
  "schemaVersion": 1,
3
3
  "id": "usage-meter",
4
4
  "version": "0.1.0",
5
- "label": { "en": "Usage Meter", "zh-CN": "额度环" },
5
+ "label": {
6
+ "en": "Usage Ring",
7
+ "zh-CN": "额度环"
8
+ },
6
9
  "description": {
7
- "en": "Check your remaining weekly and five-hour allowance.",
8
- "zh-CN": "随时查看每周与五小时剩余额度。"
10
+ "en": "Check your five-hour and weekly allowance in the sidebar; click for details.",
11
+ "zh-CN": "在侧栏查看五小时和每周剩余额度,点击打开用量详情。"
9
12
  },
10
13
  "entry": { "kind": "page-script", "script": "src/usage-meter.mjs", "sourceFactory": "src/injection-source.mjs" },
11
14
  "icon": { "viewBox": "0 0 24 24", "markup": "<circle cx=\"12\" cy=\"12\" r=\"9\"></circle><circle cx=\"12\" cy=\"12\" r=\"5\"></circle>" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecodex",
3
- "version": "2.0.13",
3
+ "version": "2.0.14",
4
4
  "description": "Lightweight, local-first plugins for the Codex desktop app.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,8 +14,8 @@ export const DEFAULT_CONFIG = Object.freeze({
14
14
  "usage-meter": true,
15
15
  "sidebar-focus": true,
16
16
  sites: true,
17
- "markdown-reading": false,
18
- "file-navigation": false,
17
+ "markdown-reading": true,
18
+ "file-navigation": true,
19
19
  }),
20
20
  });
21
21
 
@@ -14,6 +14,7 @@ import { createDataTransfer, MAX_TRANSFER_BYTES } from "./data-transfer/service.
14
14
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
15
15
  const packageMetadata = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8"));
16
16
  const MAX_BODY_BYTES = 64 * 1024;
17
+ const DASHBOARD_FEATURE_ORDER = ["model-slider", "notes", "usage-meter", "session-status", "markdown-reading", "file-navigation", "images", "sites", "sidebar-focus", "hide-upsell-banner"];
17
18
  const CONTENT_TYPES = new Map([
18
19
  [".css", "text/css; charset=utf-8"],
19
20
  [".html", "text/html; charset=utf-8"],
@@ -93,7 +94,7 @@ function acceptedHealth(featureId, health) {
93
94
  }
94
95
 
95
96
  async function loadFeatureDefinitions(paths, locale = "en") {
96
- return Promise.all(FEATURE_IDS.map(async (id) => {
97
+ return Promise.all([...FEATURE_IDS].sort((a, b) => DASHBOARD_FEATURE_ORDER.indexOf(a) - DASHBOARD_FEATURE_ORDER.indexOf(b)).map(async (id) => {
97
98
  const manifestPath = path.join(paths.featuresRoot, id, "codex-feature.json");
98
99
  const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
99
100
  return {
@@ -206,11 +207,15 @@ async function iconResponse(id, paths, response) {
206
207
  iconPath = path.join(paths.featuresRoot, id, "assets", "icons", "book-open.svg");
207
208
  } else if (FEATURE_IDS.includes(id)) {
208
209
  const manifest = JSON.parse(await readFile(path.join(paths.featuresRoot, id, "codex-feature.json"), "utf8"));
209
- const viewBox = manifest.icon?.viewBox ?? "0 0 24 24";
210
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round">${manifest.icon.markup}</svg>`;
211
- response.writeHead(200, { "Content-Type": "image/svg+xml", "Cache-Control": "public, max-age=3600" });
212
- response.end(svg);
213
- return;
210
+ if (manifest.icon?.resource) {
211
+ iconPath = path.resolve(paths.featuresRoot, id, manifest.icon.resource);
212
+ } else {
213
+ const viewBox = manifest.icon?.viewBox ?? "0 0 24 24";
214
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round">${manifest.icon.markup}</svg>`;
215
+ response.writeHead(200, { "Content-Type": "image/svg+xml", "Cache-Control": "public, max-age=3600" });
216
+ response.end(svg);
217
+ return;
218
+ }
214
219
  } else {
215
220
  json(response, 404, { error: "NOT_FOUND" });
216
221
  return;
@@ -737,7 +737,7 @@ export function createInjectionSource(features, {
737
737
  "--codex-summary-row-line-height": "24px",
738
738
  "--codex-summary-label-font-size": tokens["--text-base"] ?? "14px",
739
739
  "--codex-summary-label-line-height": tokens["--text-base--line-height"] ?? "1.5",
740
- "--codex-summary-font-weight": tokens["--vscode-font-weight"] ?? (style.fontWeight || "445"),
740
+ "--codex-summary-font-weight": style.fontWeight || "445",
741
741
  "--codex-summary-icon-size": "18px",
742
742
  "--codex-summary-icon-stroke-width": "1.5px",
743
743
  "--codex-summary-panel-radius": panelRadius,
@@ -109,6 +109,7 @@ export class CodexRuntime {
109
109
  this.rendererTargetIds = new Set();
110
110
  this.targetFailures = new Map();
111
111
  this.targetChecks = new Map();
112
+ this.targetCheckDeadlines = new Map();
112
113
  this.clients = new Map();
113
114
  this.scriptIds = new Map();
114
115
  this.bindingTokens = new Map();
@@ -245,6 +246,7 @@ export class CodexRuntime {
245
246
  this.rendererTargetIds.clear();
246
247
  this.targetFailures.clear();
247
248
  this.targetChecks.clear();
249
+ this.targetCheckDeadlines.clear();
248
250
  this.rendererDiscovered = false;
249
251
  this.browserClient?.socket.close();
250
252
  this.browserClient = null;
@@ -714,6 +716,7 @@ export class CodexRuntime {
714
716
  this.scriptIds.delete(id);
715
717
  this.bindingTokens.delete(id);
716
718
  this.targetChecks.delete(id);
719
+ this.targetCheckDeadlines.delete(id);
717
720
  this.hostActionChains.delete(id);
718
721
  this.injectedTargets.delete(id);
719
722
  this.targetFailures.delete(id);
@@ -735,6 +738,7 @@ export class CodexRuntime {
735
738
  this.bindingTokens.delete(target.id);
736
739
  this.injectedTargets.delete(target.id);
737
740
  this.targetChecks.delete(target.id);
741
+ this.targetCheckDeadlines.delete(target.id);
738
742
  }
739
743
  if (target.type !== "page" || target.url !== "app://-/index.html" || this.injectedTargets.has(target.id)) {
740
744
  continue;
@@ -759,6 +763,7 @@ export class CodexRuntime {
759
763
  if (this.clients.get(target.id) !== client) return;
760
764
  // 新文档撤销启动检查,下一次接入周期重新检查一次。
761
765
  this.targetChecks.delete(target.id);
766
+ this.targetCheckDeadlines.delete(target.id);
762
767
  this.notifyStatusChange();
763
768
  });
764
769
  client.on?.("Runtime.bindingCalled", (params) => {
@@ -825,7 +830,12 @@ export class CodexRuntime {
825
830
  async checkTarget(targetId) {
826
831
  const client=this.clients.get(targetId);
827
832
  if (!client || this.stopping) return false;
828
- if (this.targetChecks.has(targetId)) return true;
833
+ const previous = this.targetChecks.get(targetId);
834
+ const needsRetry = !previous?.checkedAt || previous.plugins.some(plugin =>
835
+ ["unknown", "unavailable", "degraded"].includes(plugin.state));
836
+ if (previous && (!needsRetry || Date.now() >= this.targetCheckDeadlines.get(targetId))) return true;
837
+ // 页面启动时锚点尚未就绪:借用发现周期有限重试,稳定后停止检查。
838
+ if (!this.targetCheckDeadlines.has(targetId)) this.targetCheckDeadlines.set(targetId, Date.now() + 30_000);
829
839
  try {
830
840
  const response=await client.send("Runtime.evaluate", {
831
841
  expression:`(() => { const runtime=window.__codexPersonalRuntime; return runtime?.version === ${RUNTIME_VERSION} && runtime.sessionId === ${JSON.stringify(this.runtimeSessionId)} ? runtime.selfCheck() : null; })()`,
@@ -876,6 +886,7 @@ export class CodexRuntime {
876
886
  client.socket.close();
877
887
  this.injectedTargets.delete(targetId);
878
888
  this.targetChecks.delete(targetId);
889
+ this.targetCheckDeadlines.delete(targetId);
879
890
  this.notifyStatusChange();
880
891
  }
881
892
  }
@@ -920,6 +931,7 @@ export class CodexRuntime {
920
931
  this.rendererTargetIds.clear();
921
932
  this.targetFailures.clear();
922
933
  this.targetChecks.clear();
934
+ this.targetCheckDeadlines.clear();
923
935
  this.rendererDiscovered = false;
924
936
  this.notifyStatusChange();
925
937
  this.monitorPromise = null;
@@ -67,5 +67,6 @@ export function createNativeThemeAdapter(themeTokenNames,themeTokenAliases,theme
67
67
  }
68
68
  };
69
69
  }
70
- return {read,dispose,checkTokens,bindScope,diagnostics:() => checkTokens(document.documentElement,themeTokenNames)};
70
+ // 根清单仅展示已定义变量;必要值缺失仍由控件作用域检查报告。
71
+ return {read,dispose,checkTokens,bindScope,diagnostics:() => checkTokens(document.documentElement,themeTokenNames).filter(token => token.available)};
71
72
  }
@@ -30,7 +30,6 @@ export const CODEX_THEME_TOKENS = Object.freeze([
30
30
  "--color-token-text-tertiary",
31
31
  "--color-token-border",
32
32
  "--color-token-focus-border",
33
- "--color-token-conversation-summary-trailing",
34
33
  "--color-token-bg-primary",
35
34
  "--color-token-bg-secondary",
36
35
  "--color-token-bg-tertiary",
@@ -51,8 +50,6 @@ export const CODEX_THEME_TOKENS = Object.freeze([
51
50
  "--color-background-button-secondary-hover",
52
51
  "--color-background-button-tertiary-hover",
53
52
  "--color-text-button-tertiary",
54
- "--color-token-button-background",
55
- "--color-token-button-foreground",
56
53
  "--color-text-foreground",
57
54
  "--color-text-foreground-secondary",
58
55
  "--color-text-foreground-tertiary",
@@ -82,7 +79,6 @@ export const CODEX_THEME_TOKENS = Object.freeze([
82
79
  "--leading-relaxed",
83
80
  "--font-weight-normal",
84
81
  "--font-weight-medium",
85
- "--vscode-font-weight",
86
82
  "--default-mono-font-family",
87
83
  "--text-heading-lg",
88
84
  "--text-heading-md",