@michengai/dsh-codex-ui 0.2.110 → 0.2.112
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/CHANGELOG.md +59 -0
- package/CHANGELOG.zh-CN.md +59 -0
- package/lib/client.js +217 -22
- package/lib/index.d.mts +1 -9
- package/lib/index.mjs +57 -66
- package/package.json +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,65 @@
|
|
|
4
4
|
|
|
5
5
|
This changelog records recent releases of DSH Codex UI and its one-click installer. Earlier changes remain available in the [Git history](https://github.com/MichengAI/dsh-codex-ui/commits/main).
|
|
6
6
|
|
|
7
|
+
## Unreleased
|
|
8
|
+
|
|
9
|
+
## 0.2.112 - 2026-09-08
|
|
10
|
+
|
|
11
|
+
### Release automation
|
|
12
|
+
|
|
13
|
+
- Fixed Chromium installation steps in release workflows and added YAML structure and prerequisite validation.
|
|
14
|
+
- Installer releases now generate and commit bilingual CHANGELOG entries before extracting release notes; missing either language blocks publication.
|
|
15
|
+
|
|
16
|
+
### Breaking changes
|
|
17
|
+
|
|
18
|
+
- Removed the public `crossSiteRequest` export. Consumers must use the host connection’s `requestRejection()` contract. Business GET routes now require authentication, including loopback requests (401 when signed out); missing authentication services return 503. The old REST paths have no compatibility aliases. Requires `@deepseek-ai/dsh-client-connection >=0.1.2-rc.1` and Cordis `>=4.0.2`.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Added localized 401/403/503 feedback for business requests and an explicit local-cache notice when workspace preferences cannot sync.
|
|
23
|
+
- Consolidated empty-chat padding and ignored stale invisible drop markers; conflicting distinct drop positions now emit a deduplicated diagnostic.
|
|
24
|
+
|
|
25
|
+
- Stopped applying custom-group spacing between projects in an ungrouped project list, keeping the empty-conversation label vertically centered between project rows.
|
|
26
|
+
- Centered empty-group text between adjacent group headers, accounting for the external group gap, and unified both empty-state labels at 13px/18px with the same muted color.
|
|
27
|
+
- Standardized the Chinese empty-conversation label to "暂无聊天".
|
|
28
|
+
- Aligned empty-group text with the group title without changing project or conversation indentation.
|
|
29
|
+
- Matched the Codex sidebar reorder marker's 8px hollow ring and 2px rounded stroke, extending the line toward its start and removing the ring-to-line gap.
|
|
30
|
+
- Unified group, project, and conversation reorder indicators around the midpoint between visible rows, including scrolling and collapsed content, instead of fixed offsets from wrapper elements.
|
|
31
|
+
- Added 4px below each project's conversation list, matching its top gap so the last conversation no longer touches the next project row; the spacing collapses with the list.
|
|
32
|
+
- Prevented the group reorder indicator above the first sidebar group from being clipped by the expanded Projects section.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Increased group labels to 14px/600 with 32px rows and 12px spacing; project folders now switch between equally sized open and closed icons according to expansion state.
|
|
37
|
+
- Strengthened sidebar groups with persistent disclosure indicators, medium-weight labels, subtle backgrounds, and spacing while preserving existing project and conversation indentation and row widths. Expansion uses the arrow state without resembling a selected conversation; improved group touch targets and keyboard focus styling.
|
|
38
|
+
- Moved Codex UI's business REST routes from `/api/michengai/codex-ui/*` to `/api/dsh-codex-ui/*`, aligning their naming with the Skills Manager and IM Connect business endpoints; the old routes are no longer registered. The host baseline now requires `@deepseek-ai/dsh-client-connection >=0.1.2-rc.1` and Cordis `>=4.0.2`.
|
|
39
|
+
|
|
40
|
+
### Security
|
|
41
|
+
|
|
42
|
+
- Delegated all five business REST routes to the DSH host's `requestRejection()` checks for trusted hosts, origins, and login cookies. Requests now fail closed when authentication is unavailable without blocking legitimate reverse-proxy origins through a loopback-only check.
|
|
43
|
+
|
|
44
|
+
### Tests
|
|
45
|
+
|
|
46
|
+
- Run Chromium layout checks in `pnpm test` and CI without screenshots, including exact tertiary colors and empty-state padding. Added cross-section drag cleanup and pinned-host-package trust-boundary regression coverage with login stubbed as authenticated.
|
|
47
|
+
|
|
48
|
+
- Added business-route registration and authentication regression coverage for the new and removed paths, 401, 403, unavailable authentication, unchanged request-header forwarding, and an external-origin write authorized by the host boundary.
|
|
49
|
+
|
|
50
|
+
## suite-installer-v0.1.29 - 2026-09-08
|
|
51
|
+
|
|
52
|
+
- Updated the suite to Codex UI `0.2.111` and IM Connect `0.1.38`, keeping the channel sidebar compatible with the authenticated `/api/dsh-im-connect/channels` endpoint.
|
|
53
|
+
- Refreshed exact member versions from the official npm registry for reproducible one-click installation.
|
|
54
|
+
|
|
55
|
+
## 0.2.111 - 2026-09-08
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- Updated the channel sidebar to use `/api/dsh-im-connect/channels`, matching IM Connect `0.1.38` after removal of the old management endpoint.
|
|
60
|
+
|
|
61
|
+
## suite-installer-v0.1.28 - 2026-09-08
|
|
62
|
+
|
|
63
|
+
- Updated the bundled Codex UI to `0.2.110`, including standalone DSH Web plugin-update recovery guidance.
|
|
64
|
+
- Refreshed exact member versions from the official npm registry for reproducible one-click installation.
|
|
65
|
+
|
|
7
66
|
## 0.2.110 - 2026-09-08
|
|
8
67
|
|
|
9
68
|
### Fixed
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,65 @@
|
|
|
4
4
|
|
|
5
5
|
本日志记录 DSH Codex UI 及其一键安装器的最近发布;更早的变更可查看 [Git 提交历史](https://github.com/MichengAI/dsh-codex-ui/commits/main)。
|
|
6
6
|
|
|
7
|
+
## 未发布
|
|
8
|
+
|
|
9
|
+
## 0.2.112 - 2026-09-08
|
|
10
|
+
|
|
11
|
+
### 发布流程
|
|
12
|
+
|
|
13
|
+
- 修复发布工作流的 Chromium 安装步骤位置,增加 YAML 结构及测试前置条件校验。
|
|
14
|
+
- 安装器自动发布先生成并提交双语 CHANGELOG,再提取 Release 说明;缺少任一语言时阻止发布。
|
|
15
|
+
|
|
16
|
+
### 不兼容变更
|
|
17
|
+
|
|
18
|
+
- 移除公共导出 `crossSiteRequest`,调用方应改用宿主 connection 的 `requestRejection()` 契约。业务 GET 现在必须认证,包括回环请求(未登录返回 401);缺少认证服务返回 503。旧 REST 地址没有兼容入口。要求 `@deepseek-ai/dsh-client-connection >=0.1.2-rc.1` 与 Cordis `>=4.0.2`。
|
|
19
|
+
|
|
20
|
+
### 修复
|
|
21
|
+
|
|
22
|
+
- 业务请求增加 401/403/503 专属本地化提示;项目偏好无法同步时明确提示正在使用本地缓存。
|
|
23
|
+
- 合并空聊天四边间距声明,跳过不可见的陈旧拖拽标记;多个不同落点冲突时输出去重诊断。
|
|
24
|
+
|
|
25
|
+
- 普通项目列表不再误用自定义分组的组间距,使“暂无聊天”在上下项目胶囊之间保持居中。
|
|
26
|
+
- 将外部组间距纳入空分组文案的垂直居中计算;“暂无项目”和“暂无聊天”统一为 13px 字号、18px 行高及相同灰色。
|
|
27
|
+
- 空项目文案由“没有聊天”统一为“暂无聊天”。
|
|
28
|
+
- 分组内“暂无项目”与分组标题文字左对齐,不改变项目和会话胶囊的缩进。
|
|
29
|
+
- 参考 Codex 侧栏插入线,采用 8px 空心圆环与 2px 圆头线,向起点延长直线并消除圆环与线段的断口。
|
|
30
|
+
- 分组、项目与会话排序蓝线统一按相邻可见行的边界中点定位,适配滚动与折叠内容,不再依赖外层容器的固定偏移。
|
|
31
|
+
- 项目内会话列表底部补齐与顶部一致的 4px 留白,避免最后一条会话贴住下一个项目胶囊;留白随列表一起折叠。
|
|
32
|
+
- 修复分组拖到侧栏第一组之前时,顶部蓝色插入线被项目分区容器裁剪的问题。
|
|
33
|
+
|
|
34
|
+
### 变更
|
|
35
|
+
|
|
36
|
+
- 分组标题增强至 14px/600、32px 行高和 12px 组间距;项目文件夹随展开、收起切换等尺寸图标。
|
|
37
|
+
- 强化侧栏分组的常驻箭头、标题字重、轻底色与组间留白;保留项目和会话原有缩进及胶囊宽度,展开状态仅由箭头表达,避免与会话选中混淆;补充分组触摸目标与键盘焦点样式。
|
|
38
|
+
- 将 Codex UI 自有业务 REST 从 `/api/michengai/codex-ui/*` 统一迁移到 `/api/dsh-codex-ui/*`,与 Skills Manager 和 IM Connect 的业务 endpoint 命名保持一致;旧地址不再注册。宿主依赖基线相应要求 `@deepseek-ai/dsh-client-connection >=0.1.2-rc.1` 与 Cordis `>=4.0.2`。
|
|
39
|
+
|
|
40
|
+
### 安全
|
|
41
|
+
|
|
42
|
+
- 五个业务 REST 全部委托 DSH 宿主 `requestRejection()` 校验可信 Host、Origin 和登录 Cookie;认证服务不可用时关闭失败,不再用仅限回环地址的判断阻断合法反向代理访问。
|
|
43
|
+
|
|
44
|
+
### 测试
|
|
45
|
+
|
|
46
|
+
- 将无截图的 Chromium 布局检查纳入 `pnpm test` 和 CI,精确检查三级灰与空状态间距;补齐跨区拖拽清理及锁定宿主发布包的信任边界回归,登录层固定为已登录。
|
|
47
|
+
|
|
48
|
+
- 新增业务路由注册和认证回归测试,覆盖新旧路径、401、403、认证不可用、请求头原样转交及宿主认证放行的外部 Origin 写请求。
|
|
49
|
+
|
|
50
|
+
## suite-installer-v0.1.29 - 2026-09-08
|
|
51
|
+
|
|
52
|
+
- 将套件更新至 Codex UI `0.2.111` 和 IM Connect `0.1.38`,保证频道侧栏兼容已认证的 `/api/dsh-im-connect/channels` 接口。
|
|
53
|
+
- 从官方 npm 刷新成员精确版本,保证一键安装可复现。
|
|
54
|
+
|
|
55
|
+
## 0.2.111 - 2026-09-08
|
|
56
|
+
|
|
57
|
+
### 修复
|
|
58
|
+
|
|
59
|
+
- 频道侧栏改用 `/api/dsh-im-connect/channels`,与删除旧管理地址后的 IM Connect `0.1.38` 保持一致。
|
|
60
|
+
|
|
61
|
+
## suite-installer-v0.1.28 - 2026-09-08
|
|
62
|
+
|
|
63
|
+
- 将套件中的 Codex UI 更新至 `0.2.110`,包含独立 DSH Web 的插件更新恢复指引。
|
|
64
|
+
- 从官方 npm 刷新成员精确版本,保证一键安装可复现。
|
|
65
|
+
|
|
7
66
|
## 0.2.110 - 2026-09-08
|
|
8
67
|
|
|
9
68
|
### 修复
|
package/lib/client.js
CHANGED
|
@@ -270,7 +270,7 @@ window.__ModuleLoader__.load({
|
|
|
270
270
|
}
|
|
271
271
|
//#endregion
|
|
272
272
|
//#region src/client/channel-api.ts
|
|
273
|
-
const CHANNELS_ENDPOINT = "/dsh-im-connect/
|
|
273
|
+
const CHANNELS_ENDPOINT = "/api/dsh-im-connect/channels";
|
|
274
274
|
/** DSH 会话头不能写 origin=im,频道会话只靠 id 前缀。 */
|
|
275
275
|
function isChannelSession(id) {
|
|
276
276
|
return id.startsWith("im:");
|
|
@@ -848,6 +848,32 @@ window.__ModuleLoader__.load({
|
|
|
848
848
|
function pendingInteractionForSession(sessionId, pendingInteractions, summaryKind) {
|
|
849
849
|
return visiblePendingKind(summaryKind) ?? visiblePendingKind(pendingInteractions.get(sessionId)?.kind);
|
|
850
850
|
}
|
|
851
|
+
//#endregion
|
|
852
|
+
//#region src/business-api.ts
|
|
853
|
+
const CODEX_UI_API_BASE = "/api/dsh-codex-ui";
|
|
854
|
+
const CODEX_UI_API_ENDPOINTS = Object.freeze({
|
|
855
|
+
connectors: `${CODEX_UI_API_BASE}/connectors`,
|
|
856
|
+
dependencies: `${CODEX_UI_API_BASE}/dependencies`,
|
|
857
|
+
openInExplorer: `${CODEX_UI_API_BASE}/open-in-explorer`,
|
|
858
|
+
preferences: `${CODEX_UI_API_BASE}/preferences`,
|
|
859
|
+
sessionMove: `${CODEX_UI_API_BASE}/session-move`
|
|
860
|
+
});
|
|
861
|
+
//#endregion
|
|
862
|
+
//#region src/client/business-request-error.ts
|
|
863
|
+
var BusinessRequestError = class extends Error {
|
|
864
|
+
status;
|
|
865
|
+
constructor(status) {
|
|
866
|
+
super(`业务请求失败:HTTP ${status}`);
|
|
867
|
+
this.status = status;
|
|
868
|
+
this.name = "BusinessRequestError";
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
function businessRequestErrorKey(error) {
|
|
872
|
+
if (!(error instanceof BusinessRequestError)) return void 0;
|
|
873
|
+
if (error.status === 401) return "errors.unauthorized";
|
|
874
|
+
if (error.status === 403) return "errors.forbidden";
|
|
875
|
+
if (error.status === 503) return "errors.serviceUnavailable";
|
|
876
|
+
}
|
|
851
877
|
const WORKSPACE_GROUP_ERROR_MESSAGES = {
|
|
852
878
|
"group-invalid": "分组信息无效。",
|
|
853
879
|
"workspace-invalid": "项目标识无效。",
|
|
@@ -1001,7 +1027,7 @@ window.__ModuleLoader__.load({
|
|
|
1001
1027
|
/** 浏览器本地持久化键;用于工作区偏好的首帧恢复与 Host 故障兜底。 */
|
|
1002
1028
|
const PINNED_WORKSPACES_STORAGE_KEY = "dsh-codex-ui.pinned-workspace-ids";
|
|
1003
1029
|
const WORKSPACE_GROUPS_STORAGE_KEY = "dsh-codex-ui.workspace-groups.v1";
|
|
1004
|
-
const WORKSPACE_PREFERENCES_ENDPOINT =
|
|
1030
|
+
const WORKSPACE_PREFERENCES_ENDPOINT = CODEX_UI_API_ENDPOINTS.preferences;
|
|
1005
1031
|
/** 清理无效或重复的工作区标识。 */
|
|
1006
1032
|
function normalizePinnedWorkspaceIds(ids) {
|
|
1007
1033
|
return [...new Set(ids.filter((id) => id.trim() !== ""))];
|
|
@@ -1040,7 +1066,7 @@ window.__ModuleLoader__.load({
|
|
|
1040
1066
|
cache: "no-store",
|
|
1041
1067
|
signal: AbortSignal.timeout(5e3)
|
|
1042
1068
|
});
|
|
1043
|
-
if (!response.ok) throw new
|
|
1069
|
+
if (!response.ok) throw new BusinessRequestError(response.status);
|
|
1044
1070
|
const payload = await response.json();
|
|
1045
1071
|
if (payload === null || typeof payload !== "object") throw new Error("置顶偏好响应格式无效。");
|
|
1046
1072
|
const record = payload;
|
|
@@ -1067,7 +1093,7 @@ window.__ModuleLoader__.load({
|
|
|
1067
1093
|
}),
|
|
1068
1094
|
signal: AbortSignal.timeout(5e3)
|
|
1069
1095
|
});
|
|
1070
|
-
if (!response.ok) throw new
|
|
1096
|
+
if (!response.ok) throw new BusinessRequestError(response.status);
|
|
1071
1097
|
}
|
|
1072
1098
|
/** 兼容旧调用方;新代码应同时写入分组。 */
|
|
1073
1099
|
async function readHostPinnedWorkspaceIds(fetcher = fetch) {
|
|
@@ -2009,6 +2035,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2009
2035
|
}
|
|
2010
2036
|
/** 将业务错误映射为当前语言,未知底层错误统一脱敏。 */
|
|
2011
2037
|
function userErrorText(error, t) {
|
|
2038
|
+
const requestKey = businessRequestErrorKey(error);
|
|
2039
|
+
if (requestKey !== void 0) return t(requestKey);
|
|
2012
2040
|
if (error instanceof UserFacingError) return error.message;
|
|
2013
2041
|
if (error instanceof WorkspaceGroupError) return t(WORKSPACE_GROUP_ERROR_KEYS[error.code]);
|
|
2014
2042
|
if (error instanceof HostActionError) return t(hostActionErrorKey(error));
|
|
@@ -2016,16 +2044,20 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2016
2044
|
}
|
|
2017
2045
|
/** 安装错误保留用户可执行的处理建议,但不直接展示 Host 原文。 */
|
|
2018
2046
|
function installErrorText(error, t) {
|
|
2047
|
+
const requestKey = businessRequestErrorKey(error);
|
|
2048
|
+
if (requestKey !== void 0) return t(requestKey);
|
|
2019
2049
|
const message = error instanceof Error ? error.message : String(error);
|
|
2020
2050
|
return t(INSTALL_ERROR_KEYS.find(([pattern]) => pattern.test(message))?.[1] ?? "about.installFailed");
|
|
2021
2051
|
}
|
|
2022
2052
|
//#endregion
|
|
2023
2053
|
//#region src/client/session-move.ts
|
|
2024
|
-
const SESSION_MOVE_ENDPOINT =
|
|
2054
|
+
const SESSION_MOVE_ENDPOINT = CODEX_UI_API_ENDPOINTS.sessionMove;
|
|
2025
2055
|
const MOVE_ACTION_PREFIX = "move-session:";
|
|
2026
2056
|
const SESSION_MOVE_ERROR_KEYS = {
|
|
2027
2057
|
"session-move/unavailable": "sessions.moveUnavailable",
|
|
2028
2058
|
"session-move/service-unavailable": "sessions.moveUnavailable",
|
|
2059
|
+
"session-move/unauthorized": "sessions.moveUnauthorized",
|
|
2060
|
+
"session-move/forbidden": "sessions.moveForbidden",
|
|
2029
2061
|
"session-move/session-not-found": "sessions.moveNotFound",
|
|
2030
2062
|
"session-move/workspace-not-found": "sessions.moveNotFound",
|
|
2031
2063
|
"session-move/subagent-unsupported": "sessions.moveSubagent",
|
|
@@ -2101,6 +2133,85 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2101
2133
|
for (const sessionId of new Set(sessionIds)) await archiveSession(sessionId);
|
|
2102
2134
|
}
|
|
2103
2135
|
//#endregion
|
|
2136
|
+
//#region src/client/workspace-drop-indicator.ts
|
|
2137
|
+
const ROWS = ".dcu-wb-section-head,.dcu-wb-collection-head,.dcu-wb-project-head,.dcu-wb-session,.dcu-wb-nochat,.dcu-wb-empty";
|
|
2138
|
+
const TARGETS = ".dcu-wb-drop,.dcu-wb-group-order-drop";
|
|
2139
|
+
function measureWorkspaceDropIndicator(root, onTargets) {
|
|
2140
|
+
const measured = Array.from(root.querySelectorAll(TARGETS)).filter((node) => node.closest("[data-open=false]") === null).map((target) => ({
|
|
2141
|
+
target,
|
|
2142
|
+
rect: measureTarget(root, target)
|
|
2143
|
+
})).filter((item) => item.rect !== void 0);
|
|
2144
|
+
const unique = measured.filter((item, index) => !measured.slice(0, index).some((previous) => previous.rect.top === item.rect.top && previous.rect.left === item.rect.left && previous.rect.width === item.rect.width));
|
|
2145
|
+
onTargets?.(unique.map((item) => item.target));
|
|
2146
|
+
return unique[0]?.rect;
|
|
2147
|
+
}
|
|
2148
|
+
function measureTarget(root, target) {
|
|
2149
|
+
const section = target.closest(".dcu-wb-section") ?? root;
|
|
2150
|
+
const rows = Array.from(section.querySelectorAll(ROWS)).filter((node) => node.closest("[data-open=false]") === null).map((node) => ({
|
|
2151
|
+
node,
|
|
2152
|
+
rect: node.getBoundingClientRect()
|
|
2153
|
+
})).filter((row) => row.rect.height > 0 && row.rect.width > 0);
|
|
2154
|
+
const owned = rows.filter((row) => row.node === target || target.contains(row.node));
|
|
2155
|
+
const after = target.classList.contains("dcu-wb-drop-after");
|
|
2156
|
+
let upper;
|
|
2157
|
+
let lower;
|
|
2158
|
+
let anchor = target.getBoundingClientRect();
|
|
2159
|
+
if (owned.length > 0) {
|
|
2160
|
+
const edge = after ? owned[owned.length - 1] : owned[0];
|
|
2161
|
+
const index = rows.indexOf(edge);
|
|
2162
|
+
anchor = edge.rect;
|
|
2163
|
+
upper = after ? edge.rect.bottom : rows[index - 1]?.rect.bottom;
|
|
2164
|
+
lower = after ? rows[index + 1]?.rect.top : edge.rect.top;
|
|
2165
|
+
} else if (target.matches(".dcu-wb-pin-start,.dcu-wb-pin-end")) {
|
|
2166
|
+
if (anchor.width <= 0 || anchor.height <= 0) return void 0;
|
|
2167
|
+
upper = rows.filter((row) => row.rect.bottom <= anchor.top).at(-1)?.rect.bottom;
|
|
2168
|
+
lower = rows.find((row) => row.rect.top >= anchor.bottom)?.rect.top;
|
|
2169
|
+
if (lower === void 0) lower = anchor.bottom;
|
|
2170
|
+
if (upper === void 0) upper = anchor.top;
|
|
2171
|
+
} else return void 0;
|
|
2172
|
+
const center = upper === void 0 ? lower - 4 : lower === void 0 ? upper + 4 : (upper + lower) / 2;
|
|
2173
|
+
const box = root.getBoundingClientRect();
|
|
2174
|
+
return {
|
|
2175
|
+
top: center - box.top - root.clientTop + root.scrollTop - 4,
|
|
2176
|
+
left: anchor.left - box.left - root.clientLeft + root.scrollLeft + 4,
|
|
2177
|
+
width: Math.max(0, anchor.width - 12)
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
/** 仅拖拽期间刷新,滚动、折叠动画和尺寸变化都使用当前帧的布局;结束时完整清理。 */
|
|
2181
|
+
function mountWorkspaceDropIndicator(root) {
|
|
2182
|
+
const line = root.ownerDocument.createElement("div");
|
|
2183
|
+
line.className = "dcu-wb-drop-indicator";
|
|
2184
|
+
line.setAttribute("aria-hidden", "true");
|
|
2185
|
+
root.append(line);
|
|
2186
|
+
root.dataset.dropIndicator = "measured";
|
|
2187
|
+
const view = root.ownerDocument.defaultView;
|
|
2188
|
+
let frame = 0;
|
|
2189
|
+
let previous = "";
|
|
2190
|
+
let previousConflict = [];
|
|
2191
|
+
const update = () => {
|
|
2192
|
+
const rect = measureWorkspaceDropIndicator(root, (targets) => {
|
|
2193
|
+
const conflict = targets.length > 1 ? targets : [];
|
|
2194
|
+
if (conflict.length > 1 && (conflict.length !== previousConflict.length || conflict.some((target, index) => target !== previousConflict[index]))) console.warn("Codex UI 拖拽存在多个有效落点,请检查跨区状态清理。", { count: conflict.length });
|
|
2195
|
+
previousConflict = conflict;
|
|
2196
|
+
});
|
|
2197
|
+
const next = rect === void 0 ? "" : `${rect.top},${rect.left},${rect.width}`;
|
|
2198
|
+
line.hidden = rect === void 0;
|
|
2199
|
+
if (rect !== void 0 && next !== previous) {
|
|
2200
|
+
line.style.top = `${rect.top}px`;
|
|
2201
|
+
line.style.left = `${rect.left}px`;
|
|
2202
|
+
line.style.width = `${rect.width}px`;
|
|
2203
|
+
}
|
|
2204
|
+
previous = next;
|
|
2205
|
+
frame = view.requestAnimationFrame(update);
|
|
2206
|
+
};
|
|
2207
|
+
update();
|
|
2208
|
+
return () => {
|
|
2209
|
+
view.cancelAnimationFrame(frame);
|
|
2210
|
+
line.remove();
|
|
2211
|
+
delete root.dataset.dropIndicator;
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
//#endregion
|
|
2104
2215
|
//#region src/client/CodexWorkspaceBrowser.tsx
|
|
2105
2216
|
const DISCLOSURE_EXIT_MS = 180;
|
|
2106
2217
|
function NewSessionIcon() {
|
|
@@ -2259,8 +2370,40 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2259
2370
|
.dcu-wb-rename-input:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 1px color-mix(in srgb,var(--dsw-alias-state-business-primary) 28%,transparent)}
|
|
2260
2371
|
`;
|
|
2261
2372
|
const runningStyles = `.dcu-wb-running{position:absolute;right:10px;top:50%;flex:none;width:12px;height:12px;margin-top:-6px;border:2px solid color-mix(in srgb,var(--dcu-sidebar-secondary) 25%,transparent);border-top-color:var(--dcu-sidebar-secondary);border-radius:50%;animation:dcu-wb-spin .8s linear infinite}@keyframes dcu-wb-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.dcu-wb-running{animation:none}}`;
|
|
2262
|
-
const typographyStyles = `.dcu-wb{--dcu-wb-disclosure-duration:180ms;--dcu-wb-disclosure-ease:cubic-bezier(.16,1,.3,1);font:14px/20px var(--dcu-font,var(--dsw-font-family))}.dcu-wb-section-label{color:var(--dcu-sidebar-secondary);font:13px/20px var(--dcu-font,var(--dsw-font-family));font-weight:400;letter-spacing:0;padding-left:0}.dcu-wb-section-caret{transition:opacity var(--dcu-wb-disclosure-duration) var(--dcu-wb-disclosure-ease),transform var(--dcu-wb-disclosure-duration) var(--dcu-wb-disclosure-ease)}.dcu-wb-section-head .dcu-wb-section-caret{position:static;left:auto;top:auto;opacity:.78}.dcu-wb-section-body,.dcu-wb-project-body,.dcu-wb-collection-body{display:block;min-height:0;height:auto;overflow:clip;opacity:1;transform:none;visibility:visible}.dcu-wb-section-body[data-open=false],.dcu-wb-project-body[data-open=false],.dcu-wb-collection-body[data-open=false]{display:block;height:0;opacity:0;transform:translateY(-2px);pointer-events:none}.dcu-wb-section-body[data-open=true]:has(.dcu-wb-drop),.dcu-wb-project-body[data-open=true]:has(.dcu-wb-drop),.dcu-wb-collection-body[data-open=true]:has(.dcu-wb-drop){overflow:visible}.dcu-wb-section-body[data-open=true]>div{animation:none}@media (prefers-reduced-motion:reduce){.dcu-wb-section-caret{transition:none}}.dcu-wb-project-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-primary)}.dcu-wb-session-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-secondary)}.dcu-wb-session.dcu-wb-selected .dcu-wb-session-title,.dcu-wb-session:hover .dcu-wb-session-title{color:var(--dcu-sidebar-primary)}.dcu-wb-empty{color:var(--dcu-sidebar-tertiary);font-size:13px;line-height:18px}
|
|
2263
|
-
const collectionLayoutStyles =
|
|
2373
|
+
const typographyStyles = `.dcu-wb{--dcu-wb-disclosure-duration:180ms;--dcu-wb-disclosure-ease:cubic-bezier(.16,1,.3,1);font:14px/20px var(--dcu-font,var(--dsw-font-family))}.dcu-wb-section-label{color:var(--dcu-sidebar-secondary);font:13px/20px var(--dcu-font,var(--dsw-font-family));font-weight:400;letter-spacing:0;padding-left:0}.dcu-wb-section-caret{transition:opacity var(--dcu-wb-disclosure-duration) var(--dcu-wb-disclosure-ease),transform var(--dcu-wb-disclosure-duration) var(--dcu-wb-disclosure-ease)}.dcu-wb-section-head .dcu-wb-section-caret{position:static;left:auto;top:auto;opacity:.78}.dcu-wb-section-body,.dcu-wb-project-body,.dcu-wb-collection-body{display:block;min-height:0;height:auto;overflow:clip;opacity:1;transform:none;visibility:visible}.dcu-wb-section-body[data-open=false],.dcu-wb-project-body[data-open=false],.dcu-wb-collection-body[data-open=false]{display:block;height:0;opacity:0;transform:translateY(-2px);pointer-events:none}.dcu-wb-section-body[data-open=true]:has(.dcu-wb-drop),.dcu-wb-project-body[data-open=true]:has(.dcu-wb-drop),.dcu-wb-collection-body[data-open=true]:has(.dcu-wb-drop){overflow:visible}.dcu-wb-section-body[data-open=true]>div{animation:none}@media (prefers-reduced-motion:reduce){.dcu-wb-section-caret{transition:none}}.dcu-wb-project-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-primary)}.dcu-wb-session-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-secondary)}.dcu-wb-session.dcu-wb-selected .dcu-wb-session-title,.dcu-wb-session:hover .dcu-wb-session-title{color:var(--dcu-sidebar-primary)}.dcu-wb-empty{color:var(--dcu-sidebar-tertiary);font-size:13px;line-height:18px}`;
|
|
2374
|
+
const collectionLayoutStyles = `
|
|
2375
|
+
.dcu-wb-tree{position:relative}
|
|
2376
|
+
.dcu-wb-tree[data-drop-indicator=measured] .dcu-wb-drop::before,.dcu-wb-tree[data-drop-indicator=measured] .dcu-wb-group-order-drop::before{display:none}
|
|
2377
|
+
.dcu-wb-drop-indicator{position:absolute;z-index:3;height:8px;pointer-events:none;color:var(--dsw-alias-state-business-primary)}
|
|
2378
|
+
.dcu-wb-drop-indicator::before{content:"";position:absolute;left:6px;right:0;top:3px;height:2px;border-radius:999px;background:currentColor}
|
|
2379
|
+
.dcu-wb-drop-indicator::after{content:"";position:absolute;box-sizing:border-box;left:0;top:0;width:8px;height:8px;border:2px solid currentColor;border-radius:50%}
|
|
2380
|
+
.dcu-wb-project-body:has(>.dcu-wb-session)::after{content:"";display:block;height:4px;pointer-events:none}
|
|
2381
|
+
.dcu-wb-section-body[data-open=true]:has(.dcu-wb-group-order-drop){overflow:visible}
|
|
2382
|
+
.dcu-wb-collection-body{padding-left:0}
|
|
2383
|
+
.dcu-wb-collection-body>.dcu-wb-group-member:first-child{padding-top:4px}
|
|
2384
|
+
.dcu-wb-collection-body .dcu-wb-project-head{padding-left:8px}
|
|
2385
|
+
.dcu-wb-collection-body>.dcu-wb-empty{padding:8px 8px 8px 26px}
|
|
2386
|
+
.dcu-wb-collection-body>.dcu-wb-empty,.dcu-wb-nochat{font-size:13px;line-height:18px;color:var(--dcu-sidebar-tertiary)}
|
|
2387
|
+
.dcu-wb-nochat{padding:1px 8px 5px 28px}
|
|
2388
|
+
.dcu-wb-collection:has(+.dcu-wb-collection)>.dcu-wb-collection-body>.dcu-wb-empty,.dcu-wb-collection:has(+.dcu-wb-ungrouped)>.dcu-wb-collection-body>.dcu-wb-empty{padding-top:14px;padding-bottom:2px}
|
|
2389
|
+
.dcu-wb-collection-body::before,.dcu-wb-group-member::after{display:none}
|
|
2390
|
+
.dcu-wb-collections{gap:12px}
|
|
2391
|
+
.dcu-wb-collections:has(>.dcu-wb-project){gap:0}
|
|
2392
|
+
.dcu-wb-collection-head{min-height:32px}
|
|
2393
|
+
.dcu-wb-collection-head:hover{background:var(--dcu-sidebar-hover)}
|
|
2394
|
+
.dcu-wb-collection-head.dcu-wb-group-drop{background:var(--dcu-sidebar-hover)}
|
|
2395
|
+
.dcu-wb-collection-head{background:color-mix(in srgb,var(--dcu-sidebar-hover) 55%,transparent)}
|
|
2396
|
+
.dcu-wb-collection-label{gap:4px;min-height:32px;font-size:14px;font-weight:600}
|
|
2397
|
+
.dcu-wb-collection-label .dcu-wb-section-caret{width:14px;height:14px;opacity:1;color:var(--dcu-sidebar-primary)}
|
|
2398
|
+
.dcu-wb-collection-label .dcu-wb-section-caret svg{width:14px;height:14px}
|
|
2399
|
+
.dcu-wb-collection-label .dcu-wb-section-caret svg path{stroke-width:2}
|
|
2400
|
+
.dcu-wb-collection-count{flex:none;font-size:12px;font-weight:400;padding-left:8px}
|
|
2401
|
+
.dcu-wb-ungrouped .dcu-wb-collection-label{color:var(--dcu-sidebar-secondary)}
|
|
2402
|
+
.dcu-wb-collection-head:focus-within,.dcu-wb-collection-head:has(.dcu-wb-more[aria-expanded=true]){background:var(--dcu-sidebar-hover)}
|
|
2403
|
+
.dcu-wb-workspace-move-drop>.dcu-wb-collection-head{background:transparent}
|
|
2404
|
+
.dcu-wb-collection-label:focus-visible,.dcu-wb-project-head:focus-visible,.dcu-wb-session:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px;border-radius:6px}
|
|
2405
|
+
@media (pointer:coarse){.dcu-wb-collection-head,.dcu-wb-collection-label{min-height:36px}.dcu-wb-collection-actions{width:28px}.dcu-wb-collection-actions .dcu-wb-more{width:28px;height:28px}}
|
|
2406
|
+
`;
|
|
2264
2407
|
const WORKSPACE_TREE_STYLE = stylesheet$4 + runningStyles + typographyStyles + collectionLayoutStyles;
|
|
2265
2408
|
function storage$1() {
|
|
2266
2409
|
return browserStorage();
|
|
@@ -2296,6 +2439,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2296
2439
|
const pinnedHostDirtyRef = (0, react.useRef)(false);
|
|
2297
2440
|
const pinnedHostSkipWriteRef = (0, react.useRef)();
|
|
2298
2441
|
const pinnedHostWriteRef = (0, react.useRef)(Promise.resolve());
|
|
2442
|
+
const [preferencesFailure, setPreferencesFailure] = (0, react.useState)();
|
|
2299
2443
|
const workspaceBaselineRef = (0, react.useRef)({
|
|
2300
2444
|
ready: false,
|
|
2301
2445
|
validIds: []
|
|
@@ -2314,12 +2458,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2314
2458
|
};
|
|
2315
2459
|
const pending = pinnedHostWriteRef.current.then(async () => {
|
|
2316
2460
|
await writeHostWorkspacePreferences(snapshot.pinnedWorkspaceIds, snapshot.workspaceGroups);
|
|
2461
|
+
setPreferencesFailure(void 0);
|
|
2317
2462
|
if (!pinnedHostSupportsWorkspaceGroupsRef.current) return;
|
|
2318
2463
|
if (!sameIds(pinnedWorkspaceIdsRef.current, snapshot.pinnedWorkspaceIds) || !sameWorkspaceGroups(workspaceGroupsRef.current, snapshot.workspaceGroups)) return;
|
|
2319
2464
|
workspaceGroupsPendingHostSyncRef.current = false;
|
|
2320
2465
|
saveWorkspaceGroupsCache(storage$1(), snapshot.workspaceGroups, false);
|
|
2321
2466
|
});
|
|
2322
|
-
pinnedHostWriteRef.current = pending.catch(() =>
|
|
2467
|
+
pinnedHostWriteRef.current = pending.catch((reason) => {
|
|
2468
|
+
setPreferencesFailure(reason);
|
|
2469
|
+
});
|
|
2323
2470
|
};
|
|
2324
2471
|
const setPinnedWorkspaceIds = (update) => {
|
|
2325
2472
|
pinnedHostDirtyRef.current = true;
|
|
@@ -2427,7 +2574,9 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2427
2574
|
}
|
|
2428
2575
|
savePinnedWorkspaceIds(storage$1(), ids);
|
|
2429
2576
|
if (writeHost) queuePinnedHostWrite(ids, groups);
|
|
2430
|
-
}).catch(() => {
|
|
2577
|
+
}).catch((reason) => {
|
|
2578
|
+
if (!alive) return;
|
|
2579
|
+
setPreferencesFailure(reason);
|
|
2431
2580
|
pinnedHostHydratedRef.current = true;
|
|
2432
2581
|
});
|
|
2433
2582
|
return () => {
|
|
@@ -2980,7 +3129,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2980
3129
|
top: box.top
|
|
2981
3130
|
}, { immediate: true });
|
|
2982
3131
|
},
|
|
2983
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
|
|
3132
|
+
children: isExpanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { size: 16 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
|
|
2984
3133
|
}),
|
|
2985
3134
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2986
3135
|
className: "dcu-wb-project-title",
|
|
@@ -3592,6 +3741,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3592
3741
|
};
|
|
3593
3742
|
const treeRef = (0, react.useRef)(null);
|
|
3594
3743
|
useSectionDisclosureMotion(treeRef);
|
|
3744
|
+
const sorting = workspaceDragId !== void 0 || workspaceGroupDragId !== void 0 || sessionDrag !== void 0;
|
|
3745
|
+
(0, react.useLayoutEffect)(() => {
|
|
3746
|
+
if (!wide || !sorting || treeRef.current === null) return;
|
|
3747
|
+
return mountWorkspaceDropIndicator(treeRef.current);
|
|
3748
|
+
}, [wide, sorting]);
|
|
3595
3749
|
if (!wide) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3596
3750
|
className: "dcu-wb dcu-wb-rail",
|
|
3597
3751
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$4 })
|
|
@@ -3606,6 +3760,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3606
3760
|
typographyStyles,
|
|
3607
3761
|
collectionLayoutStyles
|
|
3608
3762
|
] }),
|
|
3763
|
+
preferencesFailure !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3764
|
+
className: "dcu-wb-error",
|
|
3765
|
+
role: "status",
|
|
3766
|
+
children: [t("workspace.preferencesLocalOnly"), businessRequestErrorKey(preferencesFailure) !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" ", userErrorText(preferencesFailure, t)] })]
|
|
3767
|
+
}),
|
|
3609
3768
|
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3610
3769
|
className: "dcu-wb-error",
|
|
3611
3770
|
role: "alert",
|
|
@@ -6139,7 +6298,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6139
6298
|
"linking",
|
|
6140
6299
|
"building"
|
|
6141
6300
|
]);
|
|
6142
|
-
const endpoint =
|
|
6301
|
+
const endpoint = CODEX_UI_API_ENDPOINTS.dependencies;
|
|
6143
6302
|
const stylesheet$2 = `
|
|
6144
6303
|
.dcu-about{color:var(--dsw-alias-label-primary)}.dcu-about h2{margin:0;font-size:20px;line-height:28px}.dcu-about-intro{margin:6px 0 22px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}.dcu-about h3{margin:24px 0 8px;font-size:14px;line-height:20px}.dcu-about-features{margin:0;padding-left:20px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:24px}.dcu-about-dependencies-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:24px}.dcu-about-dependencies-heading h3{margin:0}.dcu-about-dependencies-heading+.dcu-about-intro{margin-bottom:14px}.dcu-about-dependencies{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px}.dcu-about-dependency{display:flex;align-items:center;gap:12px;min-height:64px;padding:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dcu-about-dependency:last-child{border-bottom:0}.dcu-about-copy{min-width:0;flex:1}.dcu-about-name{font-size:13px;line-height:20px;font-weight:600}.dcu-about-package{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.dcu-about-status{display:flex;align-items:center;gap:5px;font-size:12px}.dcu-about-status[data-installed=true]{color:var(--dsw-alias-state-success-primary)}.dcu-about-status[data-installed=false]{color:var(--dsw-alias-state-error-primary)}.dcu-about-status[data-update=true]{color:var(--dsw-alias-state-warning-primary)}.dcu-about-install{display:inline-flex;align-items:center;justify-content:center;gap:5px;min-height:32px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:6px 9px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}.dcu-about-install:hover:not(:disabled){background:var(--dsw-specific-menu-item-hover)}.dcu-about-install:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.dcu-about-install:disabled{cursor:wait;opacity:.65}.dcu-about-update-all{min-width:92px}.dcu-about-message{margin:10px 0 0;border-radius:8px;padding:9px 10px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.dcu-about-message[data-error=true]{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);color:var(--dsw-alias-state-error-primary)}.dcu-about-progress{display:grid;grid-template-columns:auto 1fr auto;gap:8px 10px;align-items:center;margin-top:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:9px 10px}.dcu-about-progress code{min-width:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-about-progress-pct{color:var(--dsw-alias-label-tertiary);font-size:12px;font-variant-numeric:tabular-nums}.dcu-about-progress-bar{grid-column:1/-1;height:4px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-border-l2)}.dcu-about-progress-fill{height:100%;background:var(--dsw-alias-state-business-primary);transition:width .2s ease}.dcu-about-progress-fill[data-wave=true]{width:28%;animation:dcu-about-progress-wave 1.2s ease-in-out infinite}@keyframes dcu-about-progress-wave{0%{transform:translateX(-60%)}100%{transform:translateX(280%)}}
|
|
6145
6304
|
.dcu-about-title-row{display:flex;align-items:center;gap:8px;min-width:0}.dcu-about-links{display:flex;align-items:center;gap:4px}.dcu-about-external-link{display:inline-flex;align-items:center;gap:5px;min-height:28px;padding:0 8px;color:var(--dsw-alias-label-secondary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;font-size:12px;font-weight:500;line-height:18px;text-decoration:none;white-space:nowrap}.dcu-about-external-link:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dcu-about-external-link:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dcu-about-external-link svg{flex:none}@media(max-width:720px){.dcu-about-title-row{flex-wrap:wrap}}
|
|
@@ -6190,6 +6349,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6190
6349
|
const [progress, setProgress] = (0, react.useState)();
|
|
6191
6350
|
const [message, setMessage] = (0, react.useState)();
|
|
6192
6351
|
const [refreshFailed, setRefreshFailed] = (0, react.useState)(false);
|
|
6352
|
+
const [loadFailure, setLoadFailure] = (0, react.useState)();
|
|
6193
6353
|
const alive = (0, react.useRef)(true);
|
|
6194
6354
|
const root = (0, react.useRef)(null);
|
|
6195
6355
|
const stateRef = (0, react.useRef)("loading");
|
|
@@ -6210,14 +6370,17 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6210
6370
|
cache: "no-store",
|
|
6211
6371
|
signal
|
|
6212
6372
|
});
|
|
6373
|
+
if (!response.ok) throw new BusinessRequestError(response.status);
|
|
6213
6374
|
const payload = await response.json();
|
|
6214
6375
|
if (signal?.aborted || currentRequest !== requestId.current) return;
|
|
6215
|
-
if (!
|
|
6376
|
+
if (!Array.isArray(payload.dependencies) || !payload.dependencies.every(isDependencyStatus)) throw new Error();
|
|
6216
6377
|
setDependencies(payload.dependencies);
|
|
6217
6378
|
setState("ready");
|
|
6218
6379
|
setRefreshFailed(false);
|
|
6380
|
+
setLoadFailure(void 0);
|
|
6219
6381
|
} catch (error) {
|
|
6220
6382
|
if (signal?.aborted || error instanceof DOMException && error.name === "AbortError" || currentRequest !== requestId.current) return;
|
|
6383
|
+
setLoadFailure(error);
|
|
6221
6384
|
if (stateRef.current === "ready") setRefreshFailed(true);
|
|
6222
6385
|
else setState("failed");
|
|
6223
6386
|
}
|
|
@@ -6282,6 +6445,11 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6282
6445
|
setMessage(void 0);
|
|
6283
6446
|
try {
|
|
6284
6447
|
const response = await fetch(`${endpoint}?dependency=${encodeURIComponent(id)}`, { method: "POST" });
|
|
6448
|
+
if ([
|
|
6449
|
+
401,
|
|
6450
|
+
403,
|
|
6451
|
+
503
|
|
6452
|
+
].includes(response.status)) throw new BusinessRequestError(response.status);
|
|
6285
6453
|
const payload = await response.json();
|
|
6286
6454
|
if (!response.ok || !Array.isArray(payload.dependencies) || !payload.dependencies.every(isDependencyStatus)) throw new Error(typeof payload.error === "string" ? payload.error : t("about.installFailed"));
|
|
6287
6455
|
if (!alive.current) return;
|
|
@@ -6310,6 +6478,11 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6310
6478
|
setMessage(void 0);
|
|
6311
6479
|
try {
|
|
6312
6480
|
const response = await fetch(`${endpoint}?action=update-all`, { method: "POST" });
|
|
6481
|
+
if ([
|
|
6482
|
+
401,
|
|
6483
|
+
403,
|
|
6484
|
+
503
|
|
6485
|
+
].includes(response.status)) throw new BusinessRequestError(response.status);
|
|
6313
6486
|
const payload = await response.json();
|
|
6314
6487
|
if (!response.ok || !Array.isArray(payload.dependencies) || !payload.dependencies.every(isDependencyStatus)) throw new Error(typeof payload.error === "string" ? payload.error : t("about.installFailed"));
|
|
6315
6488
|
if (!alive.current) return;
|
|
@@ -6405,7 +6578,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6405
6578
|
className: "dcu-about-message",
|
|
6406
6579
|
"data-error": "true",
|
|
6407
6580
|
role: "alert",
|
|
6408
|
-
children: t("about.statusFailed")
|
|
6581
|
+
children: t(businessRequestErrorKey(loadFailure) ?? "about.statusFailed")
|
|
6409
6582
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6410
6583
|
className: "dcu-about-dependencies",
|
|
6411
6584
|
"aria-busy": installing !== void 0,
|
|
@@ -6468,7 +6641,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6468
6641
|
className: "dcu-about-message",
|
|
6469
6642
|
"data-error": "true",
|
|
6470
6643
|
role: "status",
|
|
6471
|
-
children: t("about.refreshFailed")
|
|
6644
|
+
children: t(businessRequestErrorKey(loadFailure) ?? "about.refreshFailed")
|
|
6472
6645
|
}),
|
|
6473
6646
|
message !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6474
6647
|
className: "dcu-about-message",
|
|
@@ -6560,6 +6733,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6560
6733
|
const sessionId = (0, react.useSyncExternalStore)(sessionStore.subscribe, () => sessionStore.getSnapshot().current);
|
|
6561
6734
|
const [connectors, setConnectors] = (0, react.useState)([]);
|
|
6562
6735
|
const [state, setState] = (0, react.useState)("loading");
|
|
6736
|
+
const [failure, setFailure] = (0, react.useState)();
|
|
6563
6737
|
(0, react.useEffect)(() => {
|
|
6564
6738
|
if (sessionId === void 0) {
|
|
6565
6739
|
setConnectors([]);
|
|
@@ -6568,15 +6742,18 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6568
6742
|
}
|
|
6569
6743
|
const controller = new AbortController();
|
|
6570
6744
|
setState("loading");
|
|
6571
|
-
fetch(
|
|
6572
|
-
if (!response.ok) throw new
|
|
6745
|
+
fetch(`${CODEX_UI_API_ENDPOINTS.connectors}?sessionId=${encodeURIComponent(sessionId)}`, { signal: controller.signal }).then(async (response) => {
|
|
6746
|
+
if (!response.ok) throw new BusinessRequestError(response.status);
|
|
6573
6747
|
const payload = await response.json();
|
|
6574
6748
|
if (!Array.isArray(payload.connectors) || !payload.connectors.every(isConnector)) throw new Error("连接器目录返回格式无效。");
|
|
6575
6749
|
if (!controller.signal.aborted) setConnectors(payload.connectors);
|
|
6576
6750
|
}).then(() => {
|
|
6577
6751
|
if (!controller.signal.aborted) setState("ready");
|
|
6578
|
-
}).catch(() => {
|
|
6579
|
-
if (!controller.signal.aborted)
|
|
6752
|
+
}).catch((error) => {
|
|
6753
|
+
if (!controller.signal.aborted) {
|
|
6754
|
+
setFailure(error);
|
|
6755
|
+
setState("failed");
|
|
6756
|
+
}
|
|
6580
6757
|
});
|
|
6581
6758
|
return () => {
|
|
6582
6759
|
controller.abort();
|
|
@@ -6592,7 +6769,8 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6592
6769
|
});
|
|
6593
6770
|
if (state === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6594
6771
|
className: "dcu-connector-empty",
|
|
6595
|
-
|
|
6772
|
+
role: "alert",
|
|
6773
|
+
children: t(businessRequestErrorKey(failure) ?? "connectors.failed")
|
|
6596
6774
|
});
|
|
6597
6775
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6598
6776
|
className: "dcu-connector-list",
|
|
@@ -6722,7 +6900,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6722
6900
|
"workspace.renameGroup": "重命名分组",
|
|
6723
6901
|
"workspace.recent": "最近",
|
|
6724
6902
|
"workspace.recentEmpty": "无聊天",
|
|
6725
|
-
"workspace.noChat": "
|
|
6903
|
+
"workspace.noChat": "暂无聊天",
|
|
6726
6904
|
"workspace.empty": "暂无项目",
|
|
6727
6905
|
"workspace.newSession": "新建会话",
|
|
6728
6906
|
"workspace.rename": "重命名项目",
|
|
@@ -6770,6 +6948,8 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6770
6948
|
"sessions.openPath": "在资源管理器中打开",
|
|
6771
6949
|
"sessions.moveWorkspace": "项目",
|
|
6772
6950
|
"sessions.moveUnavailable": "会话移动服务暂不可用,请重启 DSH 后重试。",
|
|
6951
|
+
"sessions.moveUnauthorized": "登录状态已失效,请刷新页面并重新登录。",
|
|
6952
|
+
"sessions.moveForbidden": "当前访问地址不受信任,或请求来源已被拒绝。",
|
|
6773
6953
|
"sessions.moveNotFound": "会话或目标项目已不存在,请刷新后重试。",
|
|
6774
6954
|
"sessions.moveSubagent": "子代理会话不能移动到其他项目。",
|
|
6775
6955
|
"sessions.moveBusy": "该会话正在移动,请稍后重试。",
|
|
@@ -6859,6 +7039,10 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6859
7039
|
"time.minutes": "{count}分",
|
|
6860
7040
|
"time.hours": "{count}小时",
|
|
6861
7041
|
"time.days": "{count}天",
|
|
7042
|
+
"errors.unauthorized": "请先登录 DSH,再重试。",
|
|
7043
|
+
"errors.forbidden": "请求被安全检查拒绝,请从可信的 DSH 地址重试。",
|
|
7044
|
+
"errors.serviceUnavailable": "服务暂不可用,请稍后重试。",
|
|
7045
|
+
"workspace.preferencesLocalOnly": "项目偏好尚未同步,当前使用本地缓存。",
|
|
6862
7046
|
"errors.generic": "操作未完成,请重试。",
|
|
6863
7047
|
"errors.groupInvalid": "分组信息无效。",
|
|
6864
7048
|
"errors.workspaceInvalid": "项目标识无效。",
|
|
@@ -6976,6 +7160,8 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
6976
7160
|
"sessions.openPath": "Open in file explorer",
|
|
6977
7161
|
"sessions.moveWorkspace": "Project",
|
|
6978
7162
|
"sessions.moveUnavailable": "Conversation moving is unavailable. Restart DSH and try again.",
|
|
7163
|
+
"sessions.moveUnauthorized": "Your login session has expired. Refresh the page and sign in again.",
|
|
7164
|
+
"sessions.moveForbidden": "The current address is not trusted, or the request origin was rejected.",
|
|
6979
7165
|
"sessions.moveNotFound": "The conversation or target project no longer exists. Refresh and try again.",
|
|
6980
7166
|
"sessions.moveSubagent": "Subagent conversations cannot be moved to another project.",
|
|
6981
7167
|
"sessions.moveBusy": "This conversation is already being moved. Try again shortly.",
|
|
@@ -7065,6 +7251,10 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
7065
7251
|
"time.minutes": "{count}m",
|
|
7066
7252
|
"time.hours": "{count}h",
|
|
7067
7253
|
"time.days": "{count}d",
|
|
7254
|
+
"errors.unauthorized": "Sign in to DSH, then try again.",
|
|
7255
|
+
"errors.forbidden": "The security check rejected this request. Try again from a trusted DSH address.",
|
|
7256
|
+
"errors.serviceUnavailable": "The service is unavailable. Try again later.",
|
|
7257
|
+
"workspace.preferencesLocalOnly": "Workspace preferences are not synced. Using the local cache.",
|
|
7068
7258
|
"errors.generic": "The action could not be completed. Try again.",
|
|
7069
7259
|
"errors.groupInvalid": "The group information is invalid.",
|
|
7070
7260
|
"errors.workspaceInvalid": "The project identifier is invalid.",
|
|
@@ -7074,7 +7264,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
7074
7264
|
};
|
|
7075
7265
|
//#endregion
|
|
7076
7266
|
//#region src/client/host-open-path.ts
|
|
7077
|
-
const OPEN_IN_EXPLORER_ENDPOINT =
|
|
7267
|
+
const OPEN_IN_EXPLORER_ENDPOINT = CODEX_UI_API_ENDPOINTS.openInExplorer;
|
|
7078
7268
|
/** “在资源管理器中打开”属于明确的系统动作,不应被侧边栏文件预览器接管。 */
|
|
7079
7269
|
async function openPathInHost(connection, path, fetcher = fetch) {
|
|
7080
7270
|
let foregroundResponse;
|
|
@@ -7086,6 +7276,11 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
7086
7276
|
});
|
|
7087
7277
|
} catch {}
|
|
7088
7278
|
if (foregroundResponse?.ok) return;
|
|
7279
|
+
if (foregroundResponse !== void 0 && [
|
|
7280
|
+
401,
|
|
7281
|
+
403,
|
|
7282
|
+
503
|
|
7283
|
+
].includes(foregroundResponse.status)) throw new BusinessRequestError(foregroundResponse.status);
|
|
7089
7284
|
if (foregroundResponse !== void 0 && foregroundResponse.status !== 404 && foregroundResponse.status !== 501) {
|
|
7090
7285
|
const payload = await foregroundResponse.json().catch(() => ({}));
|
|
7091
7286
|
throw new Error(typeof payload.error === "string" ? payload.error : "无法在前台打开资源管理器。");
|
|
@@ -7326,7 +7521,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
|
|
|
7326
7521
|
const OFFICIAL_TURN_MARK_ATTRIBUTE = "data-dcu-official-turn-mark";
|
|
7327
7522
|
const OFFICIAL_TURN_TOOLTIP_ATTRIBUTE = "data-dcu-official-turn-tooltip";
|
|
7328
7523
|
const OFFICIAL_TURN_NAVIGATOR_SUPPORTED_ATTRIBUTE = "data-dcu-official-turn-navigator-supported";
|
|
7329
|
-
const CAPABILITIES_ENDPOINT =
|
|
7524
|
+
const CAPABILITIES_ENDPOINT = `${CODEX_UI_API_ENDPOINTS.dependencies}?action=capabilities`;
|
|
7330
7525
|
function isOfficialTurnNavigator(element) {
|
|
7331
7526
|
return element instanceof HTMLElement && element.tagName === "NAV" && element.style.getPropertyValue("--turn-natural-height") !== "" && element.querySelector("button[type=\"button\"][aria-label]") !== null;
|
|
7332
7527
|
}
|
package/lib/index.d.mts
CHANGED
|
@@ -4,16 +4,8 @@ type HostRequest = {
|
|
|
4
4
|
method?: string;
|
|
5
5
|
url?: string;
|
|
6
6
|
headers?: Record<string, string | string[] | undefined>;
|
|
7
|
-
socket?: {
|
|
8
|
-
remoteAddress?: string;
|
|
9
|
-
};
|
|
10
7
|
[Symbol.asyncIterator]?: () => AsyncIterator<Uint8Array | string>;
|
|
11
8
|
};
|
|
12
|
-
/**
|
|
13
|
-
* 依赖安装会改写用户配置并拉起子进程,只允许回环地址上的同源浏览器请求。
|
|
14
|
-
* Origin、Host 和 Sec-Fetch-Site 都可被非浏览器客户端伪造,不能单独作为授权依据。
|
|
15
|
-
*/
|
|
16
|
-
declare function crossSiteRequest(request: HostRequest): boolean;
|
|
17
9
|
/** 把安装错误收成可给浏览器看的文案:我们自己的中文说明保留,带本地路径的底层错误脱敏。 */
|
|
18
10
|
declare function publicDependencyError(error: unknown): string;
|
|
19
11
|
declare class RequestBodyTooLargeError extends Error {}
|
|
@@ -23,4 +15,4 @@ declare const inject: string[];
|
|
|
23
15
|
/** 提供不泄露地址、命令和凭证的连接器目录。 */
|
|
24
16
|
declare function apply(ctx: Context): void;
|
|
25
17
|
//#endregion
|
|
26
|
-
export { RequestBodyTooLargeError, apply,
|
|
18
|
+
export { RequestBodyTooLargeError, apply, inject, publicDependencyError, readRequestBody };
|
package/lib/index.mjs
CHANGED
|
@@ -5,6 +5,16 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
8
|
+
//#region src/business-api.ts
|
|
9
|
+
const CODEX_UI_API_BASE = "/api/dsh-codex-ui";
|
|
10
|
+
const CODEX_UI_API_ENDPOINTS = Object.freeze({
|
|
11
|
+
connectors: `${CODEX_UI_API_BASE}/connectors`,
|
|
12
|
+
dependencies: `${CODEX_UI_API_BASE}/dependencies`,
|
|
13
|
+
openInExplorer: `${CODEX_UI_API_BASE}/open-in-explorer`,
|
|
14
|
+
preferences: `${CODEX_UI_API_BASE}/preferences`,
|
|
15
|
+
sessionMove: `${CODEX_UI_API_BASE}/session-move`
|
|
16
|
+
});
|
|
17
|
+
//#endregion
|
|
8
18
|
//#region src/dependencies.ts
|
|
9
19
|
const SUITE_PACKAGE = "@michengai/dsh-codex-suite";
|
|
10
20
|
const SUITE_MEMBER_PACKAGES = [
|
|
@@ -1558,41 +1568,17 @@ function writeWorkspacePreferences(pinnedWorkspaceIds, workspaceGroups = [], pat
|
|
|
1558
1568
|
}
|
|
1559
1569
|
//#endregion
|
|
1560
1570
|
//#region src/index.ts
|
|
1561
|
-
const connectorsEndpoint =
|
|
1562
|
-
const dependenciesEndpoint =
|
|
1563
|
-
const explorerEndpoint =
|
|
1564
|
-
const preferencesEndpoint =
|
|
1565
|
-
const sessionMoveEndpoint =
|
|
1571
|
+
const connectorsEndpoint = CODEX_UI_API_ENDPOINTS.connectors;
|
|
1572
|
+
const dependenciesEndpoint = CODEX_UI_API_ENDPOINTS.dependencies;
|
|
1573
|
+
const explorerEndpoint = CODEX_UI_API_ENDPOINTS.openInExplorer;
|
|
1574
|
+
const preferencesEndpoint = CODEX_UI_API_ENDPOINTS.preferences;
|
|
1575
|
+
const sessionMoveEndpoint = CODEX_UI_API_ENDPOINTS.sessionMove;
|
|
1566
1576
|
const maxPreferencesBodyBytes = 32768;
|
|
1567
|
-
function isLoopbackAddress(value) {
|
|
1568
|
-
const address = value?.toLowerCase().replace(/^\[|\]$/g, "");
|
|
1569
|
-
return address === "localhost" || address === "localhost." || address === "::1" || address?.startsWith("127.") === true || address?.startsWith("::ffff:127.") === true;
|
|
1570
|
-
}
|
|
1571
1577
|
function headerValue(headers, name) {
|
|
1572
1578
|
const value = headers[name];
|
|
1573
1579
|
if (typeof value === "string") return value;
|
|
1574
1580
|
if (Array.isArray(value)) return value[0];
|
|
1575
1581
|
}
|
|
1576
|
-
/**
|
|
1577
|
-
* 依赖安装会改写用户配置并拉起子进程,只允许回环地址上的同源浏览器请求。
|
|
1578
|
-
* Origin、Host 和 Sec-Fetch-Site 都可被非浏览器客户端伪造,不能单独作为授权依据。
|
|
1579
|
-
*/
|
|
1580
|
-
function crossSiteRequest(request) {
|
|
1581
|
-
if (!isLoopbackAddress(request.socket?.remoteAddress)) return true;
|
|
1582
|
-
const headers = request.headers;
|
|
1583
|
-
if (headers === void 0) return true;
|
|
1584
|
-
const site = headerValue(headers, "sec-fetch-site");
|
|
1585
|
-
if (site !== void 0 && site !== "same-origin") return true;
|
|
1586
|
-
const origin = headerValue(headers, "origin");
|
|
1587
|
-
const host = headerValue(headers, "host");
|
|
1588
|
-
if (origin === void 0 || host === void 0) return true;
|
|
1589
|
-
try {
|
|
1590
|
-
const url = new URL(origin);
|
|
1591
|
-
return url.protocol !== "http:" && url.protocol !== "https:" || !isLoopbackAddress(url.hostname) || url.host !== host;
|
|
1592
|
-
} catch {
|
|
1593
|
-
return true;
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
1582
|
/** 把安装错误收成可给浏览器看的文案:我们自己的中文说明保留,带本地路径的底层错误脱敏。 */
|
|
1597
1583
|
function publicDependencyError(error) {
|
|
1598
1584
|
const message = error instanceof Error ? error.message : "依赖管理暂不可用。";
|
|
@@ -1615,6 +1601,35 @@ async function readRequestBody(request, maxBytes = maxPreferencesBodyBytes) {
|
|
|
1615
1601
|
}
|
|
1616
1602
|
return Buffer.concat(chunks).toString("utf8");
|
|
1617
1603
|
}
|
|
1604
|
+
/**
|
|
1605
|
+
* 所有业务 REST 依赖 connection >=0.1.2-rc.1 的 Host/Origin/Fetch Metadata 与登录检查。
|
|
1606
|
+
* 必须在读取请求体及执行副作用前调用;不得将 undefined 以外的拒绝结果当作放行。
|
|
1607
|
+
* 发布包信任边界由 tests/business-rest-auth.assert.ts 的真实宿主契约用例验证。
|
|
1608
|
+
*/
|
|
1609
|
+
function authenticateBusinessRequest(ctx, request, response, errorBody = (_status, message) => ({ error: message })) {
|
|
1610
|
+
const reject = (status, message) => {
|
|
1611
|
+
response.writeHead(status, {
|
|
1612
|
+
"content-type": "application/json; charset=utf-8",
|
|
1613
|
+
"cache-control": "no-store"
|
|
1614
|
+
});
|
|
1615
|
+
response.end(JSON.stringify(errorBody(status, message)));
|
|
1616
|
+
return false;
|
|
1617
|
+
};
|
|
1618
|
+
try {
|
|
1619
|
+
const connection = ctx.get("connection");
|
|
1620
|
+
if (typeof connection?.requestRejection !== "function") {
|
|
1621
|
+
ctx.logger.warn("Codex UI 业务 REST 认证不可用:缺少 connection.requestRejection。");
|
|
1622
|
+
return reject(503, "宿主认证服务暂不可用。");
|
|
1623
|
+
}
|
|
1624
|
+
const rejection = connection.requestRejection(request);
|
|
1625
|
+
if (rejection !== void 0) return reject(rejection, rejection === 401 ? "请先登录 DSH。" : "已拒绝不可信或跨站请求。");
|
|
1626
|
+
return true;
|
|
1627
|
+
} catch (error) {
|
|
1628
|
+
const reason = error instanceof Error ? `${error.name}: ${error.message}` : typeof error;
|
|
1629
|
+
ctx.logger.warn("Codex UI 业务 REST 认证调用失败:%s", reason);
|
|
1630
|
+
return reject(503, "宿主认证服务暂不可用。");
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1618
1633
|
const inject = [
|
|
1619
1634
|
"webServer",
|
|
1620
1635
|
"agents",
|
|
@@ -1650,6 +1665,13 @@ function publicSessionMoveError(error) {
|
|
|
1650
1665
|
}[code] ?? "暂时无法移动该会话,请稍后重试。"
|
|
1651
1666
|
};
|
|
1652
1667
|
}
|
|
1668
|
+
function sessionMoveAuthenticationError(status, error) {
|
|
1669
|
+
return {
|
|
1670
|
+
ok: false,
|
|
1671
|
+
code: status === 401 ? "session-move/unauthorized" : status === 403 ? "session-move/forbidden" : "session-move/service-unavailable",
|
|
1672
|
+
error
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1653
1675
|
/** 提供不泄露地址、命令和凭证的连接器目录。 */
|
|
1654
1676
|
function apply(ctx) {
|
|
1655
1677
|
const host = hostServices(ctx);
|
|
@@ -1660,6 +1682,7 @@ function apply(ctx) {
|
|
|
1660
1682
|
kind: "exact",
|
|
1661
1683
|
path: connectorsEndpoint,
|
|
1662
1684
|
handler: async (request, response) => {
|
|
1685
|
+
if (!authenticateBusinessRequest(ctx, request, response)) return;
|
|
1663
1686
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
1664
1687
|
response.writeHead(405);
|
|
1665
1688
|
response.end();
|
|
@@ -1702,6 +1725,7 @@ function apply(ctx) {
|
|
|
1702
1725
|
kind: "exact",
|
|
1703
1726
|
path: dependenciesEndpoint,
|
|
1704
1727
|
handler: async (request, response) => {
|
|
1728
|
+
if (!authenticateBusinessRequest(ctx, request, response)) return;
|
|
1705
1729
|
const url = new URL(request.url ?? "/", "http://localhost");
|
|
1706
1730
|
try {
|
|
1707
1731
|
if (request.method === "GET") {
|
|
@@ -1731,14 +1755,6 @@ function apply(ctx) {
|
|
|
1731
1755
|
return;
|
|
1732
1756
|
}
|
|
1733
1757
|
if (request.method === "POST") {
|
|
1734
|
-
if (crossSiteRequest(request)) {
|
|
1735
|
-
response.writeHead(403, {
|
|
1736
|
-
"content-type": "application/json; charset=utf-8",
|
|
1737
|
-
"cache-control": "no-store"
|
|
1738
|
-
});
|
|
1739
|
-
response.end(JSON.stringify({ error: "已拒绝非本机同源请求。" }));
|
|
1740
|
-
return;
|
|
1741
|
-
}
|
|
1742
1758
|
if (url.searchParams.get("action") === "update-all") {
|
|
1743
1759
|
const runtime = resolveDependencyRuntime(ctx);
|
|
1744
1760
|
const notifyParent = canRequestParentReload(runtime);
|
|
@@ -1800,6 +1816,7 @@ function apply(ctx) {
|
|
|
1800
1816
|
kind: "exact",
|
|
1801
1817
|
path: preferencesEndpoint,
|
|
1802
1818
|
handler: async (request, response) => {
|
|
1819
|
+
if (!authenticateBusinessRequest(ctx, request, response)) return;
|
|
1803
1820
|
try {
|
|
1804
1821
|
if (request.method === "GET" || request.method === "HEAD") {
|
|
1805
1822
|
const preferences = await readWorkspacePreferences();
|
|
@@ -1811,14 +1828,6 @@ function apply(ctx) {
|
|
|
1811
1828
|
return;
|
|
1812
1829
|
}
|
|
1813
1830
|
if (request.method === "PUT") {
|
|
1814
|
-
if (crossSiteRequest(request)) {
|
|
1815
|
-
response.writeHead(403, {
|
|
1816
|
-
"content-type": "application/json; charset=utf-8",
|
|
1817
|
-
"cache-control": "no-store"
|
|
1818
|
-
});
|
|
1819
|
-
response.end(JSON.stringify({ error: "已拒绝跨站请求。" }));
|
|
1820
|
-
return;
|
|
1821
|
-
}
|
|
1822
1831
|
const body = JSON.parse(await readRequestBody(request));
|
|
1823
1832
|
const record = body !== null && typeof body === "object" ? body : void 0;
|
|
1824
1833
|
const pinnedWorkspaceIds = record === void 0 ? void 0 : parsePinnedWorkspaceIds(record.pinnedWorkspaceIds);
|
|
@@ -1878,23 +1887,12 @@ function apply(ctx) {
|
|
|
1878
1887
|
kind: "exact",
|
|
1879
1888
|
path: sessionMoveEndpoint,
|
|
1880
1889
|
handler: async (request, response) => {
|
|
1890
|
+
if (!authenticateBusinessRequest(ctx, request, response, sessionMoveAuthenticationError)) return;
|
|
1881
1891
|
if (request.method !== "POST") {
|
|
1882
1892
|
response.writeHead(405, { allow: "POST" });
|
|
1883
1893
|
response.end();
|
|
1884
1894
|
return;
|
|
1885
1895
|
}
|
|
1886
|
-
if (crossSiteRequest(request)) {
|
|
1887
|
-
response.writeHead(403, {
|
|
1888
|
-
"content-type": "application/json; charset=utf-8",
|
|
1889
|
-
"cache-control": "no-store"
|
|
1890
|
-
});
|
|
1891
|
-
response.end(JSON.stringify({
|
|
1892
|
-
ok: false,
|
|
1893
|
-
code: "session-move/cross-site",
|
|
1894
|
-
error: "已拒绝跨站请求。"
|
|
1895
|
-
}));
|
|
1896
|
-
return;
|
|
1897
|
-
}
|
|
1898
1896
|
try {
|
|
1899
1897
|
const body = JSON.parse(await readRequestBody(request));
|
|
1900
1898
|
const record = body !== null && typeof body === "object" ? body : void 0;
|
|
@@ -1951,19 +1949,12 @@ function apply(ctx) {
|
|
|
1951
1949
|
kind: "exact",
|
|
1952
1950
|
path: explorerEndpoint,
|
|
1953
1951
|
handler: async (request, response) => {
|
|
1952
|
+
if (!authenticateBusinessRequest(ctx, request, response)) return;
|
|
1954
1953
|
if (request.method !== "POST") {
|
|
1955
1954
|
response.writeHead(405, { allow: "POST" });
|
|
1956
1955
|
response.end();
|
|
1957
1956
|
return;
|
|
1958
1957
|
}
|
|
1959
|
-
if (crossSiteRequest(request)) {
|
|
1960
|
-
response.writeHead(403, {
|
|
1961
|
-
"content-type": "application/json; charset=utf-8",
|
|
1962
|
-
"cache-control": "no-store"
|
|
1963
|
-
});
|
|
1964
|
-
response.end(JSON.stringify({ error: "已拒绝跨站请求。" }));
|
|
1965
|
-
return;
|
|
1966
|
-
}
|
|
1967
1958
|
if (process.platform !== "win32") {
|
|
1968
1959
|
response.writeHead(501, {
|
|
1969
1960
|
"content-type": "application/json; charset=utf-8",
|
|
@@ -2022,4 +2013,4 @@ function apply(ctx) {
|
|
|
2022
2013
|
}, "michengai-codex-ui: catalogs");
|
|
2023
2014
|
}
|
|
2024
2015
|
//#endregion
|
|
2025
|
-
export { RequestBodyTooLargeError, apply,
|
|
2016
|
+
export { RequestBodyTooLargeError, apply, inject, publicDependencyError, readRequestBody };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-codex-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.112",
|
|
4
4
|
"description": "以 Codex 风格重构 DSH Web 侧栏的独立客户端插件",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -62,10 +62,12 @@
|
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "tsc --noEmit && tsdown",
|
|
64
64
|
"typecheck": "tsc --noEmit",
|
|
65
|
-
"test": "pnpm run typecheck && vitest run && tsdown && node tests/run-assertions.mjs"
|
|
65
|
+
"test": "pnpm run typecheck && vitest run && tsdown && node tests/run-assertions.mjs && pnpm run test:layout",
|
|
66
|
+
"test:layout": "node scripts/verify-workspace-drop-layout.mjs --run"
|
|
66
67
|
},
|
|
67
68
|
"peerDependencies": {
|
|
68
|
-
"@deepseek-ai/cordis": ">=4.0.
|
|
69
|
+
"@deepseek-ai/cordis": ">=4.0.2 <5.0.0",
|
|
70
|
+
"@deepseek-ai/dsh-client-connection": ">=0.1.2-rc.1 <0.2.0",
|
|
69
71
|
"@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.5 <0.2.0",
|
|
70
72
|
"@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.5 <0.2.0",
|
|
71
73
|
"@deepseek-ai/dsh-client-ui-conversation": ">=0.1.0-rc.5 <0.2.0",
|
|
@@ -119,12 +121,14 @@
|
|
|
119
121
|
"@types/node": "^26.0.0",
|
|
120
122
|
"@types/react": "~18.3.1",
|
|
121
123
|
"jsdom": "^29.1.1",
|
|
124
|
+
"playwright": "1.63.0",
|
|
122
125
|
"react": "^18.3.1",
|
|
123
126
|
"react-dom": "^18.3.1",
|
|
124
127
|
"tsdown": "^0.22.2",
|
|
125
128
|
"tsx": "^4.22.4",
|
|
126
129
|
"typescript": "^6.0.3",
|
|
127
|
-
"vitest": "^4.1.8"
|
|
130
|
+
"vitest": "^4.1.8",
|
|
131
|
+
"workflow-yaml": "npm:yaml@2.9.0"
|
|
128
132
|
},
|
|
129
133
|
"dependencies": {
|
|
130
134
|
"lucide-react": "^1.39.0"
|