minecodex 2.0.7 → 2.0.8

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/DESIGN.md CHANGED
@@ -116,7 +116,7 @@ sidebar规则分状态:
116
116
  层级数字是本版本实现值,不是原生CSS设计token;共同约束应集中维护语义角色与挂载点。
117
117
 
118
118
  - 内容页面位于原生sidebar悬浮层下方;MineCodex当前Page使用20,原生左侧栏40。
119
- - 通知使用共享 `notification` 挂载角色(当前30),位于内容页面之上、原生sidebar与模态浮层之下;通知业务负责在原生侧栏 peeking 时暂停呈现,不抢占原生导航。
119
+ - 通知使用共享 `notification` 挂载角色(当前45),位于内容页面与常驻二级sidebar(40)之上、原生菜单和模态浮层(通常50)之下,避免 Space 等页面的侧栏遮挡通知;通知业务负责在原生侧栏 peeking 时暂停呈现,不抢占原生导航。
120
120
  - 原生Dialog、Dropdown、Popover和Composer Floating父容器通常为50;同数值不保证相同层级,仍受挂载顺序与stacking context影响。
121
121
  - 模态层挂在窗口根容器/原生Portal,遮罩覆盖整个窗口;不要嵌在Page的低层级容器内。原生`dialog.showModal()`使用top layer,不能仅用z-index推断。
122
122
  - Tooltip使用专用角色与层级;现有MineCodex实现值不是原生标准,不传播为通用token。
package/README.md CHANGED
@@ -16,7 +16,6 @@ MineCodex 给 Codex Desktop 增加一组轻量、本地优先的扩展功能。
16
16
  | **Images** | 在原生 Images 中浏览本地 Image Gen 图片,查看并复制提示词 |
17
17
  | **Hide Upsell Banner** | 隐藏输入框上方的用量提示横幅,仅优化界面展示,不影响正常的用量统计 |
18
18
  | **Session Status** | 在输入框下方查看当前任务的步骤、观测速度、Token 用量与上下文明细 |
19
- | **Task Switcher** | 会话侧栏不可见时显示通知条,点击跳转到已完成未读或需要处理的会话 |
20
19
 
21
20
  ## 安装
22
21
 
@@ -209,6 +209,8 @@ function findPluginCard(featureId) {
209
209
  }
210
210
 
211
211
  function renderPluginCards(features) {
212
+ // Task 暂停接入,旧版服务返回的卡片也不展示。
213
+ features = features.filter(feature => feature.id !== "unread-inbox");
212
214
  for (const child of [...elements.pluginList.children]) {
213
215
  if (!child.dataset.featureId) child.remove();
214
216
  }
@@ -1,16 +1,18 @@
1
1
  # 任务切换:会话通知
2
2
 
3
+ 状态(2026-10-03):暂停接入,等待重新设计注入场景。manifest 标记 disabled,Runtime Host 不发现或组装此功能;CLI 和控制台不提供启用入口,旧启用配置被移除。以下保留设计与源码供后续评估,不代表当前可用功能。
4
+
3
5
  通用视觉、材质、按钮、Tooltip 与原生 sidebar 层级遵循根 [DESIGN.md](../../DESIGN.md)。本文件记录通知条的触发、布局和状态规则。
4
6
 
5
7
  ## 触发与原生边界
6
8
 
7
- 会话侧栏不可见时,左上方自动显示通知堆栈,包括 Home 折叠状态,以及 Space、Projects 等没有会话侧栏的页面。判断真实会话导航内容的可见性,不把“侧栏展开偏好”当作当前页面具有会话侧栏的证据。Home 临时侧栏或其他原生侧栏 peeking 展开期间隐藏通知;离开后恢复,原生悬浮侧栏保持可进入、可点击。
9
+ 会话侧栏不可见时,左上方自动显示通知堆栈,包括 Home 折叠状态,以及 Space、Projects 等没有会话侧栏的页面。判断真实会话导航内容的可见性,不把“侧栏展开偏好”当作当前页面具有会话侧栏的证据。Home 为当前导航且侧栏展开时,按原生 trigger 的 aria-expanded 立即隐藏通知,不等待会话列表挂载、布局或每秒刷新;原生状态 CSS 约束同步隐藏卡片、装饰和展开按钮,Adapter 同时检查全部可见会话列表。Home 临时侧栏或其他原生侧栏 peeking 展开期间隐藏通知;离开后恢复,原生悬浮侧栏保持可进入、可点击。
8
10
 
9
11
  不创建额外入口,不改 Chat 标题的属性、热区和窗口拖拽行为,不替换 Sidebar.left 图标,也不修改 Home 原生蓝点。不点击 Activity、不切换原生筛选或展开偏好。旧入口在 Runtime Host 替换插件时由旧实例 cleanup 恢复。
10
12
 
11
13
  ## 通知条
12
14
 
13
- 堆栈以 Navbar 右侧8px、原生标题栏下沿8px为左上角原点;无标题栏内容时取 Navbar 顶缘。位置不依赖可点击的会话标题。宽320px,窄窗口缩小,右侧/底部保留12px。每条是独立的共享 opaque Popover,圆角16px,间距8px;没有列表标题、整块菜单底板或页面遮罩。堆栈位于页面之上、原生侧栏和原生模态浮层之下(当前角色实现层级30)。空白区域不拦截点击,溢出可滚动。默认仅显示前三条通知;超过三条时,第三条下方露出两层逐级缩进的卡片边缘,并显示“X more notifications”按钮,X 为总数减三。点击后展开全部,使用窗口剩余高度;只有展开内容超出该高度时才启用底部半条左右的渐隐,不再显示遮挡数量文字。滚动到底部解除渐隐。展开状态仅限当前页面;原生内存路由 pathname 变化(包括会话跳转)后恢复三条,并回到堆栈顶部。同页通知刷新、滚动及临时侧栏隐藏不重置。跨页收拢使用320ms缓出高度过渡,多余卡片淡出,层叠与按钮跟随收拢显现;尊重 reduced-motion。通知清空、账号切换或插件重载也重置。
15
+ 堆栈以 Navbar 右侧8px、原生标题栏下沿8px为左上角原点;无标题栏内容时取 Navbar 顶缘。位置不依赖可点击的会话标题。宽320px,窄窗口缩小,右侧/底部保留12px。每条是独立的共享 opaque Popover,圆角16px,间距8px;没有列表标题、整块菜单底板或页面遮罩。堆栈位于页面和常驻二级侧栏之上、原生菜单和模态浮层之下(当前角色实现层级45);原生侧栏 peeking 时暂停呈现。空白区域不拦截点击,溢出可滚动。默认仅显示前三条通知;超过三条时,第三条下方露出两层逐级缩进的卡片边缘,并显示“X more notifications”按钮,X 为总数减三。点击后展开全部,使用窗口剩余高度;只有展开内容超出该高度时才启用底部半条左右的渐隐,不再显示遮挡数量文字。滚动到底部解除渐隐。展开状态仅限当前页面;原生内存路由 pathname 变化(包括会话跳转)后恢复三条,并回到堆栈顶部。同页通知刷新、滚动及临时侧栏隐藏不重置。跨页收拢使用320ms缓出高度过渡,多余卡片淡出,层叠与按钮跟随收拢显现;尊重 reduced-motion。通知清空、账号切换或插件重载也重置。
14
16
 
15
17
  条目保持现有信息结构:13px/18px标题、可用时的12px/16px原生回答摘要(最多两行,超出截断;短摘要不强占两行)、11px/16px项目名与12px原生项目图标。远程图标紧随标题;状态显示需要回复/等待批准标签、相对时间。未读且已结束的条目在时间右侧显示蓝点。内容 padding 四周统一使用功能 spacing token `--mcx-notification-padding:14px`,行间4px。状态文字和无障碍标签随宿主语言,用户标题和摘要不翻译。
16
18
 
@@ -25,11 +27,11 @@
25
27
  - 通知不自动超时消失;可点击跳转、关闭单条,或等原生状态消除。关闭后同一事件不重复弹出,新事件可以再次提醒。
26
28
  - 侧栏显示时暂停呈现,保持状态跟踪;重新折叠时显示仍有效的通知。账号变化清空本窗口关闭记录。
27
29
  - Needs input 的倒计时读取原生自动处理请求:pendingRequest 的 userInputRequestId 必须匹配 resolution 的 requestId,且 resolutionState.status 为 scheduled。标签同步剩余秒数及进度,暂停或延期跟随原生数据。归零不自行推断 Working;原生 attention 清除并恢复 Loading 后才退出。当前安装版本 Awaiting approval 没有倒计时契约,不伪造计时或自动批准。
28
- - 不持久化通知正文,不发送消息、不执行批准操作。每秒读取原生 store,DOM/窗口布局变化也触发刷新;不为通知发送网络请求。首次连接读取安装包 JS 以解析原生角色。
30
+ - 不持久化通知正文,不发送消息、不执行批准操作。订阅原生 store 变化,并保留每秒刷新以更新相对时间与倒计时;DOM/窗口布局变化也触发刷新;不为通知发送网络请求。首次连接读取安装包 JS 以解析原生角色。
29
31
 
30
32
  ## 数据适配与验证边界
31
33
 
32
- Codex 26.930.21537,2026-10-03:隔离 Renderer 确认侧栏只挂载7条,而原生 `allSidebarThreadKeys` 目录含72条。Adapter 从 Navbar 当前 React scope 读取原生目录、需要回复/批准、工作与未读 selector,并复用原生会话导航函数。不会因项目折叠或 Space 页面隐藏 Home 列表而只剩可见条目。项目归属来自原生项目目录;摘要和项目图标在相应原生行已挂载时读取,缺失不编造或保留旧消息。
34
+ Codex 26.930.21537,2026-10-03:隔离 Renderer 确认侧栏只挂载7条,而原生 `allSidebarThreadKeys` 目录含72条。Adapter 从 Navbar 当前 React scope 读取原生目录、需要回复/批准、工作与未读 selector,并复用原生会话导航函数。不会因项目折叠或 Space 页面隐藏 Home 列表而只剩可见条目。项目归属来自原生项目目录;标题使用原生实时标题 selector。摘要沿用 View Activity 的数据与转换契约:从原生 turns selector 倒序查找最近的 agentMessage,调用同一原生文本转换函数,不再从侧栏行 DOM 读取。原生 scope.watch 订阅目录、项目、标题、状态、未读及回复数据,更新时刷新原卡片;摘要晚到不改变通知事件、排序或关闭记录。未读/待输入/待批准时机仍由原生状态决定,不为了等待摘要而延迟提醒;原生没有可用回复时不编造文字。项目图标仍在相应原生行已挂载时读取。
33
35
 
34
36
  压缩符号及导出名通过语义调用关系动态解析;文件名哈希不作为契约。缺失模块/scope/状态契约时隐藏通知并通过 `nativeCapabilities()` 报告,不使用旧账号或旧会话快照假装成功。尚未创建的任务占位不显示,原有非 Codex 的 ChatGPT 会话不因本次改版新增覆盖范围。
35
37
 
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "id": "unread-inbox",
4
+ "disabled": true,
4
5
  "version": "0.1.0",
5
6
  "label": {
6
7
  "en": "Task Switcher",
@@ -15,7 +15,13 @@ export function discoverThreadBindings(source) {
15
15
  const pendingValue = requireMatch(/=([\w$]+)\?\.pendingRequest/, row);
16
16
  const durationName = requireMatch(/durationMs:([\w$]+)/, row);
17
17
  const durationMs = Number(requireMatch(new RegExp(`${escape(durationName)}=([0-9.eE+]+)`)));
18
+ const previewIndex=source.search(/\.type===`agentMessage`/);
19
+ const preview=source.slice(source.lastIndexOf("=>{",previewIndex),source.indexOf("return null})",previewIndex));
20
+ const previewHelper=requireMatch(/let [\w$]+=([\w$]+)\([\w$]+\.text\)/,preview);
18
21
  const roles = {
22
+ activityTurns:requireMatch(/let [\w$]+=[\w$]+\(([\w$]+),[\w$]+\);if\([\w$]+==null\)return null;for/,preview),
23
+ previewText:requireMatch(new RegExp(`function ${escape(previewHelper)}\\([\\w$]+\\)\\{let [\\w$]+=([\\w$]+)\\(`)),
24
+ title:requireMatch(/=[\w$]+\(([\w$]+),\{hostId:[^}]+,threadId:[\w$]+\}\)\?\?[\w$]+\?\.title/,row),
19
25
  resolution: selectorFor(resolutionValue),
20
26
  pending: selectorFor(pendingValue),
21
27
  catalog: requireMatch(/allSidebarThreadKeys:[\w$]+,pinnedThreadKeys:[\w$]+\}=[\w$]+\(([\w$]+)\)/),
@@ -65,9 +65,46 @@ function threadRecord(row) {
65
65
  return {read:threadRecord};
66
66
  }
67
67
 
68
+ export function notificationVisibilityStyles() {
69
+ // 原生展开状态变化时同步隐藏,不等待 MutationObserver 或每秒刷新。
70
+ return ':root:has([data-sidebar-destination="builtin:home"][aria-current="page"]):has([data-app-shell-sidebar-trigger][aria-expanded="true"]) [data-minecodex-unread] { display:none; }';
71
+ }
72
+
68
73
  // 会话侧栏是可卸载视图,通知从同一原生 store 读取完整目录。
69
74
  export function createNotificationAdapter(codex, load, invalidate) {
70
75
  let bindings=null, loading=null, retryAt=0, disposed=false;
76
+ let watchedScope=null, stopWatching=null;
77
+ function watch(scope) {
78
+ if(watchedScope===scope)return;
79
+ stopWatching?.();watchedScope=scope;
80
+ stopWatching=scope.watch(({get})=>{
81
+ const catalog=get(bindings.catalog);
82
+ get(bindings.projects,"codex");
83
+ for(const key of catalog.allSidebarThreadKeys){
84
+ const summary=get(bindings.thread,key)?.summary;
85
+ if(!summary?.conversationId||!summary.hostId)continue;
86
+ const args={conversationId:summary.conversationId,hostId:summary.hostId};
87
+ get(bindings.title,{threadId:summary.conversationId,hostId:summary.hostId});
88
+ get(bindings.activityTurns,summary.conversationId);
89
+ get(bindings.status,args);get(bindings.attention,args);
90
+ get(bindings.unread,{conversationId:summary.conversationId,fallbackRootUnread:summary.hasUnreadTurn});
91
+ }
92
+ invalidate();
93
+ });
94
+ }
95
+ function activitySummary(scope,conversationId) {
96
+ const turns=scope.get(bindings.activityTurns,conversationId);
97
+ // 与 View Activity 相同:从最近的 agentMessage 读取原生纯文本预览。
98
+ for(let i=(turns?.length??0)-1;i>=0;i--){
99
+ const items=turns[i].items;
100
+ for(let j=items.length-1;j>=0;j--){
101
+ if(items[j].type!=="agentMessage")continue;
102
+ const text=bindings.previewText(items[j].text);
103
+ if(text.length)return text;
104
+ }
105
+ }
106
+ return "";
107
+ }
71
108
  function scopeFor(rail) {
72
109
  let fiber=codex.currentFiber(rail);
73
110
  for (let depth=0;fiber&&depth<160;depth++,fiber=fiber.return) {
@@ -95,9 +132,12 @@ export function createNotificationAdapter(codex, load, invalidate) {
95
132
  return pageKey!==null;
96
133
  });
97
134
  const home=rail.querySelector('[data-sidebar-destination="builtin:home"]');
98
- const sidebar=document.querySelector('[data-slate-sidebar-content]:not([inert])');
99
135
  const isVisible=e=>!!e && !e.closest('[inert],[aria-hidden="true"]') && e.checkVisibility() && e.getBoundingClientRect().width>0;
100
- const homeSidebar=isVisible(sidebar) && isVisible(sidebar.querySelector('[data-app-action-sidebar-scroll]'));
136
+ // 展开状态先于列表完成布局;先隐藏通知,避免过渡期间与侧栏重叠。
137
+ const homeExpanded=home?.getAttribute("aria-current")==="page"
138
+ && !!document.querySelector('[data-app-shell-sidebar-trigger][aria-expanded="true"]');
139
+ const homeSidebar=homeExpanded || [...document.querySelectorAll('[data-app-action-sidebar-scroll]')]
140
+ .some(list=>isVisible(list.closest('[data-slate-sidebar-content]')) && isVisible(list));
101
141
  const peeking=home?.getAttribute("aria-expanded")==="true" || !!document.querySelector('[data-slate-sidebar-peeking="true"]');
102
142
  const titlebar=[...document.querySelectorAll('[data-app-shell-titlebar-slot="main"]')].find(isVisible);
103
143
  const rect=rail.getBoundingClientRect(), titleRect=titlebar?.getBoundingClientRect();
@@ -108,10 +148,12 @@ export function createNotificationAdapter(codex, load, invalidate) {
108
148
  ensure();
109
149
  const rail=codex.rail(), scope=rail && scopeFor(rail);
110
150
  if (!bindings || !scope) {
151
+ stopWatching?.();stopWatching=null;watchedScope=null;
111
152
  if (!scope) codex.report("task-notification-catalog",false,"native-scope-unavailable");
112
153
  return null;
113
154
  }
114
155
  try {
156
+ watch(scope);
115
157
  const catalog=scope.get(bindings.catalog);
116
158
  const projectCatalog=scope.get(bindings.projects,"codex");
117
159
  const projects=new Map();
@@ -131,8 +173,8 @@ export function createNotificationAdapter(codex, load, invalidate) {
131
173
  ? {deadlineMs:resolution.resolutionState.deadlineMs,durationMs:bindings.countdownDuration} : null;
132
174
  const unread=scope.get(bindings.unread,{conversationId,fallbackRootUnread:summary.hasUnreadTurn});
133
175
  const row=rows.get(key), mounted=row ? rowAdapter.read(row) : null;
134
- return [{key,threadId:conversationId,hostId,title:summary.title||mounted?.title||"",
135
- summary:mounted?.summary||"",project:projects.get(key)??mounted?.project??null,
176
+ return [{key,threadId:conversationId,hostId,title:scope.get(bindings.title,{threadId:conversationId,hostId})??summary.title??"",
177
+ summary:activitySummary(scope,conversationId),project:projects.get(key)??null,
136
178
  timestamp:unread.unreadMessageAt??summary.recencyAt??summary.updatedAt??null,
137
179
  unread:unread.hasUnread,working:status==="loading"&&attention!=="response",
138
180
  attention:attention==="approval"?"approval":attention==="response"?"input":null,
@@ -152,5 +194,5 @@ export function createNotificationAdapter(codex, load, invalidate) {
152
194
  return null;
153
195
  }
154
196
  }
155
- return {snapshot,layout,dispose(){disposed=true;bindings=null;}};
197
+ return {snapshot,layout,dispose(){disposed=true;stopWatching?.();stopWatching=null;watchedScope=null;bindings=null;}};
156
198
  }
@@ -1,9 +1,9 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { createThreadAdapter, createNotificationAdapter } from "./adapters/codex/renderer.mjs";
2
+ import { createThreadAdapter, createNotificationAdapter, notificationVisibilityStyles } from "./adapters/codex/renderer.mjs";
3
3
  import { discoverThreadBindings, loadThreadBindings } from "./adapters/codex/modules.mjs";
4
4
  import { createNotificationQueue } from "./domain/notifications.mjs";
5
5
  export async function createPageScriptSource() {
6
6
  const source=await readFile(new URL("./unread-inbox.mjs",import.meta.url),"utf8");
7
- return [createThreadAdapter,createNotificationAdapter,discoverThreadBindings,loadThreadBindings,createNotificationQueue]
7
+ return [createThreadAdapter,createNotificationAdapter,notificationVisibilityStyles,discoverThreadBindings,loadThreadBindings,createNotificationQueue]
8
8
  .map(fn=>`const ${fn.name}=${fn.toString()};`).join("\n")+"\n"+source;
9
9
  }
@@ -44,6 +44,7 @@ style.textContent = `
44
44
  [${marker}=panel] .mcx-countdown { display:inline-block; margin-inline-start:5px; font-variant-numeric:tabular-nums; min-width:2.5ch; }
45
45
  @media (prefers-reduced-motion:reduce) { [${marker}=panel] .mcx-attention::before { transition:none; } }
46
46
  `;
47
+ style.textContent += notificationVisibilityStyles();
47
48
  document.head.append(style);
48
49
  const panel=document.createElement("section");
49
50
  panel.setAttribute(marker,"panel");
@@ -298,7 +299,7 @@ function schedule() {
298
299
  const observer=new MutationObserver(mutations=>{
299
300
  if(mutations.some(m=>!panel.contains(m.target)&&m.target!==style&&!more.contains(m.target)&&!tail.contains(m.target)))schedule();
300
301
  });
301
- observer.observe(document.documentElement,{subtree:true,childList:true,attributes:true,attributeFilter:["inert","aria-hidden","aria-expanded","data-app-shell-sidebar-open","data-slate-sidebar-peeking","lang"]});
302
+ observer.observe(document.documentElement,{subtree:true,childList:true,attributes:true,attributeFilter:["inert","aria-hidden","aria-expanded","aria-current","data-app-shell-sidebar-open","data-slate-sidebar-peeking","lang"]});
302
303
  window.addEventListener("resize",schedule,{signal});
303
304
  // 隐藏的原生视图不一定重绘;每秒读取 store,不发送请求或切换 Activity。
304
305
  const timer=setInterval(schedule,1000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecodex",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "Lightweight, local-first plugins for the Codex desktop app.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -2,7 +2,7 @@ import { randomBytes } from "node:crypto";
2
2
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
 
5
- export const FEATURE_IDS = Object.freeze(["model-slider", "notes", "images", "hide-upsell-banner", "session-status", "usage-meter", "unread-inbox", "sidebar-focus", "sites"]);
5
+ export const FEATURE_IDS = Object.freeze(["model-slider", "notes", "images", "hide-upsell-banner", "session-status", "usage-meter", "sidebar-focus", "sites"]);
6
6
  export const DEFAULT_CONFIG = Object.freeze({
7
7
  schemaVersion: 1,
8
8
  features: Object.freeze({
@@ -12,7 +12,6 @@ export const DEFAULT_CONFIG = Object.freeze({
12
12
  "hide-upsell-banner": true,
13
13
  "session-status": true,
14
14
  "usage-meter": true,
15
- "unread-inbox": true,
16
15
  "sidebar-focus": true,
17
16
  sites: true,
18
17
  }),
@@ -34,13 +33,16 @@ export function normalizeConfig(value = {}) {
34
33
  if (value.schemaVersion !== undefined && value.schemaVersion !== 1) {
35
34
  throw new Error(`Unsupported MineCodex config schema: ${value.schemaVersion}`);
36
35
  }
36
+ const features = {
37
+ ...(value.features && typeof value.features === "object" ? value.features : {}),
38
+ ...normalizeFeatures(value.features),
39
+ };
40
+ // 暂停的 Task 通知不继承旧配置,也不再提供开启入口。
41
+ delete features["unread-inbox"];
37
42
  return {
38
43
  ...value,
39
44
  schemaVersion: 1,
40
- features: {
41
- ...(value.features && typeof value.features === "object" ? value.features : {}),
42
- ...normalizeFeatures(value.features),
43
- },
45
+ features,
44
46
  };
45
47
  }
46
48
 
@@ -328,6 +328,10 @@ export async function createControlServer({
328
328
  const featureMatch = url.pathname.match(/^\/api\/features\/([a-z-]+)$/);
329
329
  if (request.method === "PATCH" && featureMatch) {
330
330
  requireMutationOrigin(request, host, boundPort);
331
+ if (!FEATURE_IDS.includes(featureMatch[1])) {
332
+ json(response, 404, { error: "NOT_FOUND" });
333
+ return;
334
+ }
331
335
  const body = await readJson(request);
332
336
  await updateFeature(paths.configPath, featureMatch[1], body.enabled);
333
337
  const features = await featureStatuses(paths, runtimeManager, locale);
@@ -207,3 +207,7 @@ Model Slider 的模型规则、原生控制器适配和专有呈现位于插件
207
207
  Runtime Host 在账号菜单 Settings 下挂载 MineCodex 项目入口,不属于可开关的插件。`minecodex/open-control-panel` 是独立 builtin action,必须通过对应 renderer 的 binding token 验证;它不接受任意 URL 或 shell 参数,固定调用包内 CLI 的 `gui` 命令。组件的项目图标、外链箭头与菜单模板由共享 UI 提供。
208
208
 
209
209
  CLI 继续通过一次性 browser exchange 打开控制台。macOS 默认 Safari 与 Chrome/Chromium、Edge、Brave、Vivaldi 支持查找现有控制台标签页并激活;没有匹配页才调用系统默认浏览器打开登录地址。自动化权限被拒绝时返回错误,不用重复开页掩盖失败。其他浏览器和 Windows 保留系统 URL 打开行为,目前不保证标签页复用。
210
+
211
+ ### 暂停功能的接入
212
+
213
+ `codex-feature.json` 的 `disabled:true` 表示保留源码但暂停运行时接入。发现阶段在加载 Adapter、资源和注入源码之前跳过该功能,显式启用 ID 也不能恢复它。Task Toast(`unread-inbox`)于 2026-10-03 暂停;CLI/控制台已移除其注册入口,配置归一化清除旧启用项。恢复时须同时重新评估注入场景并恢复 manifest 与用户入口。
@@ -334,6 +334,7 @@ export async function discoverFeatures(featuresRoot, { env = process.env } = {})
334
334
  }
335
335
  try {
336
336
  const manifest = JSON.parse(source);
337
+ if (manifest.disabled === true) continue;
337
338
  features.push(await normalizeFeature(manifest, projectDir, env));
338
339
  } catch (error) {
339
340
  throw new Error(`Invalid ${entry.name}/${MANIFEST_NAME}: ${error.message}`);
@@ -4,7 +4,7 @@ export function installLayers(signal) {
4
4
  style.setAttribute("data-minecodex-ui-styles", "layers");
5
5
  style.textContent = `
6
6
  [data-mcx-ui-layer=page] { position:fixed; z-index:20; }
7
- [data-mcx-ui-layer=notification] { position:fixed; z-index:30; }
7
+ [data-mcx-ui-layer=notification] { position:fixed; z-index:45; }
8
8
  [data-mcx-ui-layer=floating], [data-mcx-ui-layer=modal] { position:fixed; z-index:50; }
9
9
  `;
10
10
  document.head.append(style);