minecodex 2.0.6 → 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 +6 -2
- package/README.md +0 -1
- package/apps/dashboard/app.js +2 -0
- package/features/images/DESIGN.md +7 -3
- package/features/images/src/adapters/codex/native-image-modules.mjs +11 -2
- package/features/images/src/adapters/codex/native-image-source.mjs +44 -22
- package/features/images/src/assets/prompts-menu.svg +15 -0
- package/features/images/src/library-prompt-page.mjs +5 -4
- package/features/images/src/library-prompt-source.mjs +2 -1
- package/features/unread-inbox/DESIGN.md +6 -4
- package/features/unread-inbox/codex-feature.json +1 -0
- package/features/unread-inbox/src/adapters/codex/modules.mjs +6 -0
- package/features/unread-inbox/src/adapters/codex/renderer.mjs +47 -5
- package/features/unread-inbox/src/injection-source.mjs +2 -2
- package/features/unread-inbox/src/unread-inbox.mjs +2 -1
- package/package.json +1 -1
- package/packages/cli/src/config.mjs +8 -6
- package/packages/cli/src/control-server.mjs +4 -0
- package/packages/runtime-host/README.md +4 -0
- package/packages/runtime-host/src/codex-injection.mjs +3 -1
- package/packages/runtime-host/src/feature-registry.mjs +1 -0
- package/packages/ui/assets/arrow-up-right.svg +2 -3
- package/packages/ui/assets/minecodex-menu-16.svg +7 -0
- package/packages/ui/assets/minecodex-menu-20.svg +7 -0
- package/packages/ui/src/components/account-menu.mjs +16 -2
- package/packages/ui/src/foundation/layers.mjs +1 -1
package/DESIGN.md
CHANGED
|
@@ -116,7 +116,7 @@ sidebar规则分状态:
|
|
|
116
116
|
层级数字是本版本实现值,不是原生CSS设计token;共同约束应集中维护语义角色与挂载点。
|
|
117
117
|
|
|
118
118
|
- 内容页面位于原生sidebar悬浮层下方;MineCodex当前Page使用20,原生左侧栏40。
|
|
119
|
-
- 通知使用共享 `notification` 挂载角色(当前
|
|
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。
|
|
@@ -183,6 +183,10 @@ macOS 后台服务 App 在明确的原生资源修订 1.0.1 中接入实心图
|
|
|
183
183
|
|
|
184
184
|
### 账号菜单中的 MineCodex 入口
|
|
185
185
|
|
|
186
|
-
Codex 26.930.21537(2026-10-03
|
|
186
|
+
Codex 26.930.21537(2026-10-03 安装包源码与隔离 DOM):`profile-dropdown-items` 的 Settings 使用原生菜单行,紧接 Help 前的 Separator;`onOpenSettings` / `settingsShortcut` 标识其行为,外层 `onClose` 关闭账号面板。MineCodex 行插在 Settings 与 Separator 之间,复制脱离 React 的原生行,保留原始 Settings 及其所属层级。左侧使用实心项目标识,右侧使用 Lucide ArrowUpRight;右侧沿用快捷键区域的间距,图标使用原生 Help 右侧箭头的 `text-tertiary` 色与 opacity 1(当前浅色为 rgba(13,13,13,0.482)),不套左侧图标的 0.75 / hover、focus 时 1 的透明度。隔离 DOM 确认左侧普通态与 Settings 同为 rgb(13,13,13)、opacity 0.75,hover / focus 时为 1。
|
|
187
|
+
|
|
188
|
+
左侧保留原生 CompactSource / LeadingSource 两套结构,只替换其中 SVG;分别继承原生 SVG 的 width、height、class 和 style。菜单标识复用当前 `codex-light-16` / `codex-light-20` 的真实填充外圈路径,引用存于 `assets/brand/references/codex-menu-16.svg` / `codex-menu-20.svg`;内部镐子仍从品牌源生成,由 `npm run build:icons` 输出两套 `packages/ui/assets/minecodex-menu-*.svg`。不能将 24px 品牌外圈缩小代替原生菜单的路径粗细。账号图标沿用原生 `currentColor` 与 `--profile-menu-icon-opacity`(桌面默认 0.75,行 hover / focus 时为 1),不另设固定灰色。此项为安装包源码核对,实际窗口视觉仍待手动验收。
|
|
189
|
+
|
|
190
|
+
右侧箭头的两段几何合并在单个 SVG path 中一次描边,避免 `currentColor` 自带 alpha 时,两个独立 path 在交叉点重复叠色。保留原资源的形状与端点规则;24px viewBox 中 stroke-width 为 1.575,在 16px 显示时实际线宽 1.05px,与原生 Help 箭头的线条粗细对齐。
|
|
187
191
|
|
|
188
192
|
入口随账号菜单重建恢复且只出现一次;补齐 Settings → MineCodex → Help 的上下键导航与 Enter/Space,保留原生 Escape 关闭行为。点击经已认证 Host bridge 调用 `mcx gui`;成功后关闭菜单,失败时显示实际错误。原生 UI 手动验收本次因 Computer Use 拒绝操作应用而未完成;隔离 DOM 契约页验证了位置、明暗颜色、点击、键盘、重建去重与清理。
|
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
|
|
package/apps/dashboard/app.js
CHANGED
|
@@ -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
|
}
|
|
@@ -12,6 +12,8 @@ hover sidebars and the native full-page shell remain owned by Codex.
|
|
|
12
12
|
|
|
13
13
|
## Image Gen
|
|
14
14
|
|
|
15
|
+
全屏图片查看器使用原生 Canvas 的 `presentation:"overlay"`,由外层 CanvasLayout 的 `bg-surface` 提供背景。Codex 26.930.21537(2026-10-03 源码与 live DOM):`dialog` / `panel` 分支会在内部画布叠加 `bg-surface-secondary`(当前浅色 #f7f7f7),不能用于这里的全屏画布。保留 standalone、原生 viewer header、底部编辑工具栏和 Composer 接入。
|
|
16
|
+
|
|
15
17
|
Codex 26.930.21537(2026-10-02 隔离实测):通过原生 `variant="browse"` 的 Image sources 契约接入。保留原生标签节点及路由,在插件拥有的 React root 中复用同一个原生 Tabs 组件,追加 Image Gen;退出时恢复原节点。原生上下文由 Adapter 从当前已提交的组件祖先读取,业务层不遍历 Fiber。
|
|
16
18
|
|
|
17
19
|
本地来源复用 ImageLibrary、ThreadMetadataIndex 及文件监听,索引存放在当前 runtime profile;按生成时间倒序,每页 36 张,滚动增量加载,搜索复用原生输入框。稳定身份为 threadId + imageGenerationItemId,不猜测云端 ID 与本地文件的关系。
|
|
@@ -20,11 +22,13 @@ Codex 26.930.21537(2026-10-02 隔离实测):通过原生 `variant="browse"
|
|
|
20
22
|
|
|
21
23
|
点击图片进入原生 Library 主内容 viewport 内的图片画布,保留 navbar、Space 二级侧栏和窗口栏;不打开 side panel,也不使用覆盖整窗的 Dialog。画布、缩放、下载、打开、Markup、Comment、Remove BG、Erase、Resize 和底部 “Ask about this file” 输入框复用原生组件。底部直接使用画布的原生 `renderComposer` / overlay 承载,沿用底部留白、居中、避让和工具状态显隐;不得另建 absolute 底部输入框。进入 Comment 时隐藏大输入框,退出时恢复;原生拖拽工具栏保留位置记忆及 Home 居中重置。初始缩放与每张图的 Zoom to fit 使用原生 `zoom` 契约,`manualZoomPercent` 初始为 `null`,不自算比例,也不继承上一张图的手动缩放。关闭回到原列表并恢复交互,标注中的关闭交由原生编辑器处理。
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
2026-10-03 跟进原生文件预览:底部复用当前 File Composer 的本地任务分支,宽度 `min(48rem, calc(100vw - 2rem))`、`auto-single-line`、`surfaceVariant:"inline"`、隐藏 utility bar 和运行位置控件,输入布局、模型控件、权限、语音及发送按钮均由原生组件决定,不再固定旧 Chat 的单行组件。画布在主内容 viewport 内,因此只在此画布范围内去掉原生 overlay header 容器的窗口交通灯左右避让,保留原生 header 自身 8px inset。背景、缩放、Open、下载、关闭、菜单及编辑状态使用原生 Canvas;按当前产品要求保留黑色编辑行动条。底部预留区通过 ResizeObserver 读取实际 File Composer 高度,加原生 `spacing × 4` 底部 inset;不沿用原生旧输入框的 118px 最小预留。行动条默认位置与拖拽下边界共用此画布尺寸,输入框展开、收起或隐藏时同步更新;默认紧凑输入框 44px 时预留为 60px,行动条与输入框间距为 16px。
|
|
26
|
+
|
|
27
|
+
本地来源以真实路径、来源 thread 和生成项 ID 接入;底部输入框首次发送时通过原生文件服务准备本地临时附件,不上传云端、不伪造 Library 身份。关闭或切换账号后晚到的附件会释放;提交接受后沿用原生本地会话导航。黑色行动条的生成编辑仍在明确提交编辑时通过原生上传准备临时附件,同一画布复用编辑附件,查看不上传到 Library。附件使用原生返回的 `id`;不得把本地 ID 伪装为 cloud file ID。生成编辑调用原生提交能力,接受后沿用原生会话导航。View prompts 通过原生菜单扩展进入已有的 File info 风格面板。以上组件契约已按 Codex 26.930.21537 的产物与隔离窗口检查;实际云端生成结果须单独验证。
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
发送准备只在用户点击发送后进行,不在开始输入时预准备;本地字节转换直接填充 typed array,避免大图片经字符串迭代器生成中间数组。该优化只减少本地准备成本,不代表上传或原生提交网络等待已经缩短。
|
|
26
30
|
|
|
27
|
-
图库菜单复用原生 mediaOverlay More Button、Dropdown、Menu.Item 与原生图标,按 Chat about this、View prompts、Add to Favorites/Remove from Favorites、Download、Add to folder、Delete 排列。View prompts 的图标约束为菜单使用的 16px
|
|
31
|
+
图库菜单复用原生 mediaOverlay More Button、Dropdown、Menu.Item 与原生图标,按 Chat about this、View prompts、Add to Favorites/Remove from Favorites、Download、Add to folder、Delete 排列。View prompts 的图标约束为菜单使用的 16px,不继承导航栏尺寸。项目现有 Lucide book-open 的合并路径图形随 Images 注入,资源位于 `src/assets/prompts-menu.svg`;图库 More、大图菜单和云端菜单共用此资源,不从 Prompts 导航节点临时取图,也不依赖 Notes 功能启用。
|
|
28
32
|
|
|
29
33
|
只有用户点击收藏或文件夹操作时,通过原生 Library upload(required persistence)上传所选 PNG;取得真实 Library ID 后保存账号、用户、本地图片 ID、SHA-256 内容指纹对应关系。收藏调用原生 setLibraryFavorite 并以服务器状态切换,文件夹使用原生 MoveLibraryItemsDialog;不在本地保存收藏或文件夹关系。云端节点读取采用原生 `/files/library/files/{library_file_id}/node` 契约,不能把上传返回的 Library ID 当普通内容 file ID。
|
|
30
34
|
|
|
@@ -10,6 +10,7 @@ export function discoverFilePreviewComposerBindings(source) {
|
|
|
10
10
|
const scopeBody=requireBody(/draftId:[\w$]+,imageDataUrl:/);
|
|
11
11
|
const homeBody=requireBody(/homeComposerController:[\w$]+,homeComposerMode:/);
|
|
12
12
|
const composerBody=requireBody(/lockedLayout:`single-line`/);
|
|
13
|
+
const localBody=requireBody(/prepareLocalConversationAttachments:/);
|
|
13
14
|
const alias=(body,local,call=false)=>{
|
|
14
15
|
const escaped=local?.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");
|
|
15
16
|
const value=escaped&&body.match(new RegExp(`(?:[,;]|let )${escaped}=([\\w$]+)${call?"\\(":"(?=[,;])"}`))?.[1];
|
|
@@ -30,6 +31,12 @@ export function discoverFilePreviewComposerBindings(source) {
|
|
|
30
31
|
composerScope:alias(scopeBody,scopes[0][2]),
|
|
31
32
|
createComposerValue:alias(scopeBody,scopes[0][3],true),
|
|
32
33
|
composerRouteScope:alias(scopeBody,scopes[1][2]),
|
|
34
|
+
FileComposer:localBody.match(/jsx\)\(([\w$]+),\{\.\.\.[\w$]+,aboveComposerContent:/)?.[1],
|
|
35
|
+
fileComposerAvailability:localBody.match(/composerModeAvailability:([\w$]+)/)?.[1],
|
|
36
|
+
localAttachmentService:localBody.match(/await ([\w$]+)\([\w$]+,[\w$]+\)\.createFileAttachment/)?.[1],
|
|
37
|
+
deleteLocalAttachment:localBody.match(/aborted&&\(([\w$]+)\(/)?.[1],
|
|
38
|
+
useFileNavigate:alias(localBody,localBody.match(/([\w$]+)\(([\w$]+)\([\w$]+\),\{state:/)?.[1],true),
|
|
39
|
+
localThreadRoute:localBody.match(/[\w$]+\(([\w$]+)\([\w$]+\),\{state:/)?.[1],
|
|
33
40
|
};
|
|
34
41
|
if(Object.values(roles).some(value=>!value))throw new Error("Native file preview Composer role changed");
|
|
35
42
|
return roles;
|
|
@@ -193,15 +200,17 @@ export function createNativeImageModules({read = async url => (await fetch(url))
|
|
|
193
200
|
const createPortal=(await binding(canvas.source,canvas.url,portalInit))().createPortal;
|
|
194
201
|
// 文件画布使用原生 Home Composer 的控制器、附件 scope 和输入组件。
|
|
195
202
|
const composerLocals=Object.values(discoverFilePreviewComposerBindings(filePreview.source));
|
|
196
|
-
const [useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope] = await Promise.all(composerLocals.map(local=>binding(filePreview.source,filePreview.url,local)));
|
|
203
|
+
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)));
|
|
197
204
|
if (!canvas.exports.t || !canvasLayout.exports.r || !Composer || !ComposerSurface || !ScopeProvider
|
|
198
205
|
|| !composerScope || !composerRouteScope?.parent
|
|
199
|
-
||
|
|
206
|
+
|| !FileComposer || !fileComposerAvailability
|
|
207
|
+
|| [createPortal,useComposerController,useHomeComposer,createComposerValue,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute].some(value=>typeof value!=="function")) {
|
|
200
208
|
throw new Error("Native Images canvas contract changed");
|
|
201
209
|
}
|
|
202
210
|
return {
|
|
203
211
|
createPortal,Canvas:canvas.exports.t,CanvasLayout:canvasLayout.exports.r,
|
|
204
212
|
useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope,
|
|
213
|
+
FileComposer,fileComposerAvailability,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute,
|
|
205
214
|
editImage:component(initial,source=>/async function/.test(source)&&/requireDispatchAcceptance:/.test(source)&&/sourceAttachment:/.test(source)&&/imageVariant/.test(source),"image edit submission"),
|
|
206
215
|
};
|
|
207
216
|
})().catch(error=>{canvasConnecting=null;throw error;});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {createNativeImageModules} from "./native-image-modules.mjs";
|
|
2
2
|
|
|
3
3
|
// 原生呈现与上下文只在此适配;构建产物发现由专用模块提供。
|
|
4
|
-
export function createNativeImageSource(codex, { requestHostAction, libraryApi, createCloudImageActions, signal, modules = createNativeImageModules() } = {}) {
|
|
4
|
+
export function createNativeImageSource(codex, { requestHostAction, libraryApi, createCloudImageActions, promptGlyphSource, signal, modules = createNativeImageModules() } = {}) {
|
|
5
5
|
const connect = modules.gallery;
|
|
6
6
|
const opened = new Map();
|
|
7
7
|
|
|
@@ -82,6 +82,7 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
82
82
|
const context = environment(anchor);
|
|
83
83
|
const {React, ReactDOM} = native;
|
|
84
84
|
const h = React.createElement;
|
|
85
|
+
const promptIcon=h("span",{className:"inline-flex size-4 items-center justify-center","aria-hidden":true,dangerouslySetInnerHTML:{__html:promptGlyphSource}});
|
|
85
86
|
let failure, currentContent, folderDialog, removeDialog, canvasImage, disposed=false, batchBusy=false;
|
|
86
87
|
let currentImages=[],currentLoading=true,selectedIds=new Set(),selectionAnchor=null,openRevision=0;
|
|
87
88
|
const scope = context.libraryScope;
|
|
@@ -251,7 +252,7 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
251
252
|
imageId:image.imageGenerationItemId, sourceConversationId:image.threadId,
|
|
252
253
|
imageSourceContext:{conversationId:image.threadId, hostId:"local"}, imageSource:"generated",
|
|
253
254
|
readOnly, openInEditor:true,
|
|
254
|
-
renderHeaderMenuActions:({renderMenu}) => renderMenu(h(native.Menu.Item, {onSelect:() => onPrompt(image), children:context.intl.formatMessage({id:"minecodex.images.viewPrompts",defaultMessage:"View prompts"})})),
|
|
255
|
+
renderHeaderMenuActions:({renderMenu}) => renderMenu(h(native.Menu.Item, {leftIcon:promptIcon,onSelect:() => onPrompt(image), children:context.intl.formatMessage({id:"minecodex.images.viewPrompts",defaultMessage:"View prompts"})})),
|
|
255
256
|
}, {addToHistory:false});
|
|
256
257
|
if (!result) throw new Error("Native image preview could not open");
|
|
257
258
|
}
|
|
@@ -271,15 +272,28 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
271
272
|
h(native.ScopeProvider,{scope:native.composerRouteScope,value:{pathname:"/",routeKind:"home",routeTemplate:"/",search:"",projectContext:null}},
|
|
272
273
|
h(native.ScopeProvider,{scope:native.composerScope,value:composerValue},h(CanvasSurface,{image,resolved,close}))));
|
|
273
274
|
// 原生文件预览占据主内容 viewport,保留导航、二级侧栏和窗口栏。
|
|
274
|
-
return native.createPortal(h("div",{className:"absolute inset-0 z-40 overflow-hidden", "data-minecodex-image-canvas":image.id,children:
|
|
275
|
+
return native.createPortal(h("div",{className:"absolute inset-0 z-40 overflow-hidden", "data-minecodex-image-canvas":image.id,children:[
|
|
276
|
+
// 画布在主内容内,原生窗口级 overlay 的交通灯避让不适用于此处。
|
|
277
|
+
h("style",{children:'[data-minecodex-image-canvas] .shrink-0:has(>[data-testid="viewer-header"]){padding-left:0!important;padding-right:0!important}'}),content,
|
|
278
|
+
]}),viewport);
|
|
275
279
|
}
|
|
276
280
|
function CanvasSurface({image,resolved,close}) {
|
|
277
281
|
const controller=native.useComposerController();
|
|
282
|
+
const navigate=native.useFileNavigate();
|
|
278
283
|
const home=native.useHomeComposer({homeComposerController:controller,homeComposerMode:"chat"});
|
|
279
284
|
const [abort]=React.useState(()=>new AbortController());
|
|
280
285
|
const [markup,setMarkup]=React.useState(false);
|
|
281
286
|
const [manualZoomPercent,setManualZoomPercent]=React.useState(null);
|
|
282
287
|
const [submitting,setSubmitting]=React.useState(false);
|
|
288
|
+
const [composerHeight,setComposerHeight]=React.useState(0);
|
|
289
|
+
const measureComposer=React.useCallback(node=>{
|
|
290
|
+
const element=node?.querySelector("[data-codex-composer-root]");
|
|
291
|
+
if(!element)return;
|
|
292
|
+
const measure=()=>setComposerHeight(element.getBoundingClientRect().height);
|
|
293
|
+
const observer=new ResizeObserver(measure);
|
|
294
|
+
observer.observe(element);measure();
|
|
295
|
+
return ()=>{observer.disconnect();setComposerHeight(0);};
|
|
296
|
+
},[]);
|
|
283
297
|
const editorRef=React.useRef(null);
|
|
284
298
|
const attachment=React.useRef(null);
|
|
285
299
|
const submissionAccepted=React.useRef(false);
|
|
@@ -319,29 +333,44 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
319
333
|
}catch(error){if(!abort.signal.aborted)operationError(error);return false;}
|
|
320
334
|
finally {if(!abort.signal.aborted)setSubmitting(false);}
|
|
321
335
|
};
|
|
322
|
-
const composer=h(native.ComposerSurface,{placement:{kind:"home"}},h(native.
|
|
323
|
-
|
|
324
|
-
className:"mx-auto w-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
336
|
+
const composer=h("div",{ref:measureComposer,className:"contents"},h(native.ComposerSurface,{placement:{kind:"home"}},h(native.FileComposer,{
|
|
337
|
+
composerController:controller,composerLayoutMode:"auto-single-line",
|
|
338
|
+
className:"mx-auto w-[min(48rem,calc(100vw-2rem))] max-w-full",
|
|
339
|
+
composerModeAvailability:native.fileComposerAvailability,
|
|
340
|
+
hideRunLocationDropdownOverride:true,showUtilityBar:false,surfaceVariant:"inline",
|
|
341
|
+
placeholderText:message("libraryFilePreview.composerPlaceholder","Ask about this file"),
|
|
342
|
+
submitDisabled:submitting,selectedProject:null,
|
|
343
|
+
// 原图仅在发送时成为本地附件,不伪造 Library 身份,也不上传云端。
|
|
344
|
+
prepareLocalConversationAttachments:async ({fileAttachments,hostId})=>{
|
|
345
|
+
assertCurrent();
|
|
346
|
+
const file=await reference(image,"upload-file");assertCurrent();
|
|
347
|
+
const attached=await native.localAttachmentService(context.scope,hostId).createFileAttachment({contents:uploadFile(file),label:file.name});
|
|
348
|
+
if(abort.signal.aborted||!isCurrent()){
|
|
349
|
+
await native.deleteLocalAttachment(context.scope,hostId,attached.path);
|
|
350
|
+
assertCurrent();
|
|
351
|
+
}
|
|
352
|
+
return [...fileAttachments,attached];
|
|
353
|
+
},
|
|
354
|
+
onLocalFirstTurnAccepted:()=>{submissionAccepted.current=true;},
|
|
355
|
+
onLocalConversationCreated:(conversationId,state)=>{submissionAccepted.current=true;close();navigate(native.localThreadRoute(conversationId),{state});},
|
|
356
|
+
})));
|
|
330
357
|
const dataUrl=resolved.dataUrl??resolved.previewSrc;
|
|
331
358
|
const canvas=h(native.Canvas,{
|
|
332
359
|
alt:image.name,title:image.name,src:dataUrl??image.sourcePath,downloadSrc:dataUrl??image.sourcePath,downloadFilename:image.name,
|
|
333
360
|
attachmentId:`image-playground:${image.imageGenerationItemId}`,attachmentSrc:image.sourcePath,
|
|
334
361
|
chatGptConversationId:props.conversationId,
|
|
335
362
|
imageSource:"generated",imageSourceContext:{conversationId:image.threadId,hostId:"local"},
|
|
336
|
-
readOnly:false,presentation:"
|
|
363
|
+
readOnly:false,presentation:"overlay",standalone:true,showViewerHeader:true,onClose:requestClose,
|
|
337
364
|
markupMode:{active:markup,onActiveChange:setMarkup,editorRef,isSubmitting:submitting,standaloneComposer:true},
|
|
338
365
|
onImageEditSubmit:submitEdit,
|
|
339
366
|
zoom:{manualZoomPercent,onZoomPercentChange:setManualZoomPercent,onZoomToFit:()=>setManualZoomPercent(null)},
|
|
340
367
|
renderComposer:visible=>h(React.Activity,{mode:visible?"visible":"hidden",children:composer}),
|
|
341
|
-
renderHeaderMenuActions:({renderMenu})=>renderMenu(h(native.Menu.Item,{onSelect:()=>onPrompt(image),children:message("minecodex.images.viewPrompts","View prompts")})),
|
|
368
|
+
renderHeaderMenuActions:({renderMenu})=>renderMenu(h(native.Menu.Item,{leftIcon:promptIcon,onSelect:()=>onPrompt(image),children:message("minecodex.images.viewPrompts","View prompts")})),
|
|
342
369
|
});
|
|
343
|
-
// 原生 overlay
|
|
344
|
-
|
|
370
|
+
// 原生 overlay 的 118px 下限属于旧输入框;画布和拖拽边界跟随实际高度。
|
|
371
|
+
const reserve=composerHeight>0?`calc(${composerHeight}px + var(--spacing)*4)`:"0px";
|
|
372
|
+
return h("div",{className:"contents",style:{"--right-panel-composer-overlay-reserve":reserve}},
|
|
373
|
+
h(native.CanvasLayout,{assetName:image.name,contentOwnsHeader:true,onClose:requestClose,children:canvas}));
|
|
345
374
|
}
|
|
346
375
|
async function chatAbout(image) {
|
|
347
376
|
assertAccount();
|
|
@@ -389,13 +418,6 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
389
418
|
const item = byId.get(id);
|
|
390
419
|
return item && {...item, icon, text:context.intl.formatMessage(message ?? item.message)};
|
|
391
420
|
};
|
|
392
|
-
const promptGlyph = document.querySelector('[aria-label="Prompts"] svg');
|
|
393
|
-
const menuGlyph = promptGlyph?.cloneNode(true);
|
|
394
|
-
if (menuGlyph) {
|
|
395
|
-
menuGlyph.setAttribute("width","16");menuGlyph.setAttribute("height","16");
|
|
396
|
-
menuGlyph.style.width="16px";menuGlyph.style.height="16px";
|
|
397
|
-
}
|
|
398
|
-
const promptIcon = menuGlyph ? h("span", {className:"inline-flex size-4 items-center justify-center", "aria-hidden":true, dangerouslySetInnerHTML:{__html:menuGlyph.outerHTML}}) : null;
|
|
399
421
|
const menuItems = [
|
|
400
422
|
{id:"chat-about", icon:native.chatIcon, text:context.intl.formatMessage({id:"libraryNext.actions.chatAboutItem",defaultMessage:"Chat about this"}),onSelect:() => {void chatAbout(image).catch(onRetry);}},
|
|
401
423
|
{id:"view-prompts", icon:promptIcon, text:context.intl.formatMessage({id:"minecodex.images.viewPrompts",defaultMessage:"View prompts"}),onSelect:() => {void open(image).then(opened=>{if(opened&&isCurrent())onPrompt(image);}).catch(operationError);}},
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- Derived from Lucide book-open (ISC): merged stroke subpaths for uniform alpha. -->
|
|
2
|
+
<svg
|
|
3
|
+
class="icon-xs shrink-0"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="16"
|
|
6
|
+
height="16"
|
|
7
|
+
viewBox="-1.5 -1.5 27 27"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="1.8"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M12 7v14 M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" />
|
|
15
|
+
</svg>
|
|
@@ -72,12 +72,13 @@ function syncMenu(record) {
|
|
|
72
72
|
item.dataset.minecodexLibraryPrompt = "menuitem";
|
|
73
73
|
const row = original.firstElementChild.cloneNode(true);
|
|
74
74
|
row.querySelector(".truncate").textContent = label("View prompts", "查看提示词");
|
|
75
|
-
//
|
|
76
|
-
const glyph =
|
|
77
|
-
|
|
75
|
+
// 图标随 Images 注入,不依赖 Prompts 导航入口是否显示。
|
|
76
|
+
const glyph = new DOMParser().parseFromString(promptGlyphSource,"image/svg+xml").documentElement;
|
|
77
|
+
for (const svg of row.querySelectorAll("svg")) {
|
|
78
78
|
const icon = glyph.cloneNode(true);
|
|
79
79
|
icon.setAttribute("width", svg.getAttribute("width"));
|
|
80
80
|
icon.setAttribute("height", svg.getAttribute("height"));
|
|
81
|
+
icon.setAttribute("class", svg.getAttribute("class") ?? "");
|
|
81
82
|
svg.replaceWith(icon);
|
|
82
83
|
}
|
|
83
84
|
item.append(row);
|
|
@@ -259,7 +260,7 @@ document.addEventListener("minecodex:image-prompt",localPromptRequested);
|
|
|
259
260
|
const localeObserver = new MutationObserver(() => mounted?.refreshLocale());
|
|
260
261
|
localeObserver.observe(document.documentElement, { attributes:true, attributeFilter:["lang"] });
|
|
261
262
|
localImageSource = installLocalImageSource(config, signal, createImageSourcesAdapter(config.codex), createNativeImageSource(config.codex, {
|
|
262
|
-
requestHostAction:config.requestHostAction, libraryApi, createCloudImageActions, signal,
|
|
263
|
+
requestHostAction:config.requestHostAction, libraryApi, createCloudImageActions, promptGlyphSource, signal,
|
|
263
264
|
}));
|
|
264
265
|
const observer = new MutationObserver(() => {
|
|
265
266
|
if (scheduled) return;
|
|
@@ -10,7 +10,8 @@ import { promptResolverSource } from "./adapters/codex/prompt-resolver-source.mj
|
|
|
10
10
|
|
|
11
11
|
export async function createPageScriptSource() {
|
|
12
12
|
const source=await readFile(new URL("./library-prompt-page.mjs",import.meta.url),"utf8");
|
|
13
|
+
const promptGlyphSource=await readFile(new URL("./assets/prompts-menu.svg",import.meta.url),"utf8");
|
|
13
14
|
const functions={createCloudImageActions,createImageSourcesAdapter,discoverFilePreviewComposerBindings,createNativeImageModules,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
|
|
14
15
|
const declarations=Object.entries(functions).map(([name,fn])=>`const ${name}=${fn.toString()};`).join("\n");
|
|
15
|
-
return `${declarations}\nconst promptFromConversation=${promptResolverSource()};\n${source}`;
|
|
16
|
+
return `${declarations}\nconst promptGlyphSource=${JSON.stringify(promptGlyphSource)};\nconst promptFromConversation=${promptResolverSource()};\n${source}`;
|
|
16
17
|
}
|
|
@@ -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
|
|
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
|
-
-
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
|
135
|
-
summary:
|
|
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
|
@@ -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", "
|
|
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 与用户入口。
|
|
@@ -10,7 +10,9 @@ import { randomBytes } from "node:crypto";
|
|
|
10
10
|
import { CODEX_THEME_TOKENS, CODEX_THEME_TOKEN_ALIASES, MINECODEX_THEME_BINDINGS } from "../../ui/src/native/tokens.mjs";
|
|
11
11
|
|
|
12
12
|
export const RUNTIME_VERSION = 108;
|
|
13
|
-
const PROJECT_ICON =
|
|
13
|
+
const PROJECT_ICON = Object.fromEntries([16, 20].map(size => [size,
|
|
14
|
+
readFileSync(new URL(`../../ui/assets/minecodex-menu-${size}.svg`, import.meta.url), "utf8"),
|
|
15
|
+
]));
|
|
14
16
|
const EXTERNAL_ICON = readFileSync(new URL("../../ui/assets/arrow-up-right.svg", import.meta.url), "utf8");
|
|
15
17
|
export const HOST_BINDING_NAME = "__codexPersonalHostAction";
|
|
16
18
|
export const CSP_BOOTSTRAP_VERSION = 1;
|
|
@@ -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}`);
|
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
viewBox="0 0 24 24"
|
|
6
6
|
fill="none"
|
|
7
7
|
stroke="currentColor"
|
|
8
|
-
stroke-width="
|
|
8
|
+
stroke-width="1.575"
|
|
9
9
|
stroke-linecap="round"
|
|
10
10
|
stroke-linejoin="round"
|
|
11
11
|
>
|
|
12
|
-
<path d="M7
|
|
13
|
-
<path d="M7 17 17 7" />
|
|
12
|
+
<path d="M7 7h10v10M7 17 17 7" />
|
|
14
13
|
</svg>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-brand-icons.mjs from assets/brand/minecodex-icon.svg. -->
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.23992 1.43098C7.46406 1.1031 8.54982 1.45396 9.38542 2.21516C10.5383 1.98366 11.9155 2.29793 12.8093 3.19172C13.6945 4.07741 14.0164 5.30994 13.7829 6.44953C14.5557 7.31992 14.8933 8.54971 14.569 9.76008C14.2447 10.9704 13.3375 11.8664 12.2331 12.2337C11.8657 13.3376 10.9704 14.2443 9.76042 14.5687C8.54999 14.893 7.32025 14.5555 6.44988 13.7825C5.3103 14.0161 4.07777 13.6943 3.19206 12.8089C2.30603 11.9228 1.98129 10.6886 2.2155 9.54817C1.44398 8.67777 1.10733 7.44883 1.43132 6.23957C1.7556 5.02965 2.66205 4.13253 3.76628 3.76496C4.13401 2.66129 5.0304 1.75519 6.23992 1.43098ZM8.84636 3.15461C8.21242 2.48417 7.42544 2.20118 6.51238 2.44563C5.58126 2.69512 4.90916 3.43143 4.69988 4.30989L4.62566 4.62434L4.3112 4.69953C3.43254 4.90862 2.69555 5.58058 2.44597 6.51203C2.19659 7.44329 2.49878 8.39365 3.15495 9.01399L3.38933 9.23567L3.29753 9.54524C3.03946 10.4106 3.25264 11.384 3.93425 12.0657C4.61596 12.7473 5.59015 12.9608 6.45574 12.7025L6.76531 12.6097L6.98699 12.845C7.6075 13.5014 8.55777 13.8045 9.48894 13.555C10.4198 13.3054 11.0903 12.5676 11.2995 11.6888L11.3747 11.3753L11.6891 11.3001C12.568 11.091 13.3058 10.4198 13.5553 9.4886C13.8048 8.55744 13.5017 7.60715 12.8454 6.98664L12.611 6.76496L12.7028 6.45539C12.961 5.59001 12.7483 4.61562 12.0671 3.93391C11.3946 3.26145 10.2567 3.03482 9.37761 3.29719L9.06804 3.38996L8.84636 3.15461Z" fill="currentColor"/>
|
|
4
|
+
<g transform="scale(0.6666666666666666)"><g id="pickaxe" transform="translate(6.24 6.24) scale(.48)" fill="currentColor">
|
|
5
|
+
<path d="M15.686 4.314 16.3 3.7a1 1 0 0 1 1.4 0l2.6 2.6a1 1 0 0 1 0 1.4l-.614.614a12.501 12.501 0 0 1 1.356 10.225 1 1 0 0 1-1.751-.119 22 22 0 0 0-3.393-6.319l-.198.199a1 1 0 0 1-1.4 0l-.219.169L6.619 20.38a1 1 0 1 1-3-3l7.912-7.912.169.232a1 1 0 0 1 0-1.4l.198-.197A22 22 0 0 0 5.58 4.71a1 1 0 0 1-.119-1.752 12.5 12.5 0 0 1 10.225 1.356Z"/>
|
|
6
|
+
</g></g>
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-brand-icons.mjs from assets/brand/minecodex-icon.svg. -->
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.79787 1.78095C9.33018 1.37054 10.6892 1.80862 11.7354 2.76044C13.1781 2.47254 14.8994 2.86492 16.0176 3.9831C17.1255 5.09146 17.5294 6.63391 17.2383 8.06025C18.2036 9.15 18.6252 10.6879 18.2197 12.2018C17.8139 13.7157 16.6796 14.8375 15.2988 15.2985C14.838 16.6794 13.7171 17.8134 12.2031 18.2194C10.689 18.6252 9.15041 18.2035 8.06057 17.238C6.63426 17.5291 5.09181 17.1254 3.98342 16.0173C2.87514 14.909 2.4696 13.3659 2.76076 11.9392C1.7958 10.8492 1.37577 9.31111 1.78127 7.79755C2.1869 6.28392 3.31944 5.16128 4.70022 4.69989C5.1615 3.31914 6.28421 2.18664 7.79787 1.78095ZM11.0518 3.94892C10.2613 3.11295 9.28034 2.76056 8.1426 3.06513C6.98179 3.37617 6.14459 4.29507 5.88381 5.39032L5.78908 5.78778L5.39065 5.88251C4.29532 6.14315 3.37656 6.98118 3.06545 8.14228C2.75466 9.30301 3.1314 10.4872 3.94924 11.2604L4.24709 11.5417L4.1299 11.9343C3.80831 13.013 4.07409 14.2271 4.92385 15.0768C5.77357 15.9262 6.98753 16.1917 8.06643 15.8698L8.459 15.7526L8.74025 16.0505C9.51379 16.8686 10.6978 17.2453 11.8584 16.9343C13.0186 16.6232 13.8554 15.7053 14.1162 14.6101L14.211 14.2116L14.6094 14.1169C15.705 13.8562 16.6245 13.0189 16.9356 11.8581C17.2464 10.6976 16.8687 9.51342 16.0508 8.73993L15.753 8.45868L15.8701 8.0661C16.192 6.98728 15.9265 5.77328 15.0772 4.92353C14.2391 4.08548 12.8208 3.80245 11.7246 4.12958L11.333 4.24677L11.0518 3.94892Z" fill="currentColor"/>
|
|
4
|
+
<g transform="scale(0.8333333333333334)"><g id="pickaxe" transform="translate(6.24 6.24) scale(.48)" fill="currentColor">
|
|
5
|
+
<path d="M15.686 4.314 16.3 3.7a1 1 0 0 1 1.4 0l2.6 2.6a1 1 0 0 1 0 1.4l-.614.614a12.501 12.501 0 0 1 1.356 10.225 1 1 0 0 1-1.751-.119 22 22 0 0 0-3.393-6.319l-.198.199a1 1 0 0 1-1.4 0l-.219.169L6.619 20.38a1 1 0 1 1-3-3l7.912-7.912.169.232a1 1 0 0 1 0-1.4l.198-.197A22 22 0 0 0 5.58 4.71a1 1 0 0 1-.119-1.752 12.5 12.5 0 0 1 10.225 1.356Z"/>
|
|
6
|
+
</g></g>
|
|
7
|
+
</svg>
|
|
@@ -4,12 +4,21 @@ export function installAccountMenuEntry(signal, ui, native) {
|
|
|
4
4
|
let node, anchor, entryBindings, opening = false;
|
|
5
5
|
const bindings = new AbortController();
|
|
6
6
|
function svg(source, template = null) {
|
|
7
|
+
if (typeof source !== "string") source = source[Number(template.getAttribute("width"))];
|
|
7
8
|
const result = new DOMParser().parseFromString(source, "image/svg+xml").documentElement;
|
|
8
9
|
result.setAttribute("aria-hidden", "true");
|
|
9
10
|
result.removeAttribute("role");
|
|
10
11
|
result.removeAttribute("aria-labelledby");
|
|
11
12
|
result.querySelectorAll("title, desc").forEach(n => n.remove());
|
|
12
13
|
result.setAttribute("class", template?.getAttribute("class") ?? "icon-xs shrink-0");
|
|
14
|
+
if (template) {
|
|
15
|
+
// 原生 16/20px 图标由外层切换,品牌资源的 24px 不能覆盖它。
|
|
16
|
+
for (const name of ["width", "height", "style"]) {
|
|
17
|
+
const value = template.getAttribute(name);
|
|
18
|
+
if (value == null) result.removeAttribute(name);
|
|
19
|
+
else result.setAttribute(name, value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
13
22
|
return result;
|
|
14
23
|
}
|
|
15
24
|
async function activate(event) {
|
|
@@ -43,9 +52,14 @@ export function installAccountMenuEntry(signal, ui, native) {
|
|
|
43
52
|
node.setAttribute("role", "menuitem");
|
|
44
53
|
node.tabIndex = -1;
|
|
45
54
|
const [leading, title, trailing] = node.querySelector("div").children;
|
|
46
|
-
|
|
55
|
+
// 保留紧凑/普通图标的原生容器及颜色、透明度和状态规则。
|
|
56
|
+
for (const template of leading.querySelectorAll("svg")) template.replaceWith(svg(icon, template));
|
|
47
57
|
title.textContent = label;
|
|
48
|
-
|
|
58
|
+
// 右侧提示沿用原生 Help 箭头的 tertiary 色,不套左侧图标的透明度。
|
|
59
|
+
trailing.style.color = "inherit";
|
|
60
|
+
const external = svg(externalIcon);
|
|
61
|
+
external.setAttribute("class", "icon-xs shrink-0 text-tertiary");
|
|
62
|
+
trailing.replaceChildren(external);
|
|
49
63
|
node.addEventListener("click", activate, { signal:entryBindings.signal });
|
|
50
64
|
node.addEventListener("pointermove", () => node.focus({ preventScroll:true }), { signal:entryBindings.signal });
|
|
51
65
|
found.item.after(node);
|
|
@@ -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:
|
|
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);
|