@michengai/dsh-codex-ui 0.2.49 → 0.2.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/README.zh-CN.md +5 -2
- package/dist/client.js +3914 -1325
- package/dist/index.d.mts +15 -1
- package/dist/index.mjs +89 -7
- package/package.json +11 -9
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { Context } from "@deepseek-ai/cordis";
|
|
2
2
|
//#region src/index.d.ts
|
|
3
|
+
type HostRequest = {
|
|
4
|
+
method?: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* 判断浏览器请求是否跨站。依赖安装会改写用户配置并拉起子进程,
|
|
10
|
+
* 恶意网页只需一个表单就能跨站触发,必须先按 Sec-Fetch-Site(优先,
|
|
11
|
+
* 且无法被页面伪造)再按 Origin 与 Host 的比对阻断;无这些头的
|
|
12
|
+
* 非浏览器客户端(curl、CLI)仍然放行。
|
|
13
|
+
*/
|
|
14
|
+
declare function crossSiteRequest(request: HostRequest): boolean;
|
|
15
|
+
/** 把安装错误收成可给浏览器看的文案:我们自己的中文说明保留,带本地路径的底层错误脱敏。 */
|
|
16
|
+
declare function publicDependencyError(error: unknown): string;
|
|
3
17
|
declare const inject: string[];
|
|
4
18
|
/** 提供不泄露地址、命令和凭证的连接器目录。 */
|
|
5
19
|
declare function apply(ctx: Context): void;
|
|
6
20
|
//#endregion
|
|
7
|
-
export { apply, inject };
|
|
21
|
+
export { apply, crossSiteRequest, inject, publicDependencyError };
|
package/dist/index.mjs
CHANGED
|
@@ -20,6 +20,18 @@ const MANAGED_DEPENDENCIES = [
|
|
|
20
20
|
{
|
|
21
21
|
id: "archive",
|
|
22
22
|
packageName: "@michengai/dsh-archive-manager"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "im",
|
|
26
|
+
packageName: "@michengai/dsh-im-connect"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "schedule",
|
|
30
|
+
packageName: "@michengai/dsh-automation"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "market",
|
|
34
|
+
packageName: "dshmarket"
|
|
23
35
|
}
|
|
24
36
|
];
|
|
25
37
|
function managedDependency(id) {
|
|
@@ -48,12 +60,22 @@ async function installedPackageVersion(packageName) {
|
|
|
48
60
|
throw error;
|
|
49
61
|
}
|
|
50
62
|
}
|
|
63
|
+
/** npm latest 查询缓存有效期:避免每次打开“关于”页都打 7 个 registry 请求。 */
|
|
64
|
+
const LATEST_CACHE_TTL_MS = 3e5;
|
|
65
|
+
const latestCache = /* @__PURE__ */ new Map();
|
|
51
66
|
async function npmLatestVersion(packageName) {
|
|
67
|
+
const hit = latestCache.get(packageName);
|
|
68
|
+
if (hit !== void 0 && Date.now() - hit.at < LATEST_CACHE_TTL_MS) return hit.version;
|
|
52
69
|
try {
|
|
53
70
|
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`, { signal: AbortSignal.timeout(5e3) });
|
|
54
71
|
if (!response.ok) return void 0;
|
|
55
72
|
const manifest = await response.json();
|
|
56
|
-
|
|
73
|
+
if (typeof manifest.version !== "string") return void 0;
|
|
74
|
+
latestCache.set(packageName, {
|
|
75
|
+
version: manifest.version,
|
|
76
|
+
at: Date.now()
|
|
77
|
+
});
|
|
78
|
+
return manifest.version;
|
|
57
79
|
} catch {
|
|
58
80
|
return;
|
|
59
81
|
}
|
|
@@ -61,8 +83,13 @@ async function npmLatestVersion(packageName) {
|
|
|
61
83
|
function escapeRegExp(value) {
|
|
62
84
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
63
85
|
}
|
|
86
|
+
/** 允许写入 YAML 单引号白名单的版本:semver 及常见预发布后缀,禁止引号与空白。 */
|
|
87
|
+
function isSafeReleaseVersion(version) {
|
|
88
|
+
return /^v?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version);
|
|
89
|
+
}
|
|
64
90
|
/** 将用户确认的精确版本合并进 Profile 的 pnpm 发布时间保护例外。 */
|
|
65
91
|
function applyReleaseExclude(source, packageName, version) {
|
|
92
|
+
if (!isSafeReleaseVersion(version)) throw new Error("npm 返回了无法识别的最新版本。");
|
|
66
93
|
const eol = source.includes("\r\n") ? "\r\n" : "\n";
|
|
67
94
|
const existing = new RegExp(`^ - '${escapeRegExp(packageName)}@([^']*)'\\s*$`, "m").exec(source);
|
|
68
95
|
if (existing !== null) {
|
|
@@ -142,6 +169,7 @@ function pluginCommandError(stderr) {
|
|
|
142
169
|
const detail = stderr.replace(/\s+/g, " ").trim();
|
|
143
170
|
if (detail.includes("minimumReleaseAge") || detail.includes("Release age")) return /* @__PURE__ */ new Error("更新被 pnpm 发布时间保护拦截。请确认已写入当前版本白名单后重试。");
|
|
144
171
|
if (detail.includes("EPERM") || detail.includes("EBUSY") || detail.includes("EACCES")) return /* @__PURE__ */ new Error("无法覆盖正在运行的插件文件。请先停止 DSH Web,再点击更新。");
|
|
172
|
+
if (detail.includes("NO_MATCHING_VERSION") || detail.includes("No matching version")) return /* @__PURE__ */ new Error("当前 npm 镜像还没有这个版本。请稍后重试,或改用官方源安装。");
|
|
145
173
|
return /* @__PURE__ */ new Error("从 npm 安装或更新依赖失败。请检查网络、npm registry 或发布时间保护后重试。");
|
|
146
174
|
}
|
|
147
175
|
/**
|
|
@@ -183,14 +211,29 @@ function runDshPlugin(args) {
|
|
|
183
211
|
});
|
|
184
212
|
});
|
|
185
213
|
}
|
|
214
|
+
/** 并发安装互斥:pnpm 锁文件竞争会触发 EPERM/EBUSY,同一时间只允许一个安装进程。 */
|
|
215
|
+
let installing = false;
|
|
186
216
|
/** 仅允许安装固定依赖,避免把浏览器输入转成任意命令。 */
|
|
187
217
|
async function installDependency(id) {
|
|
218
|
+
if (installing) throw new Error("已有依赖安装正在进行,请等待完成后再试。");
|
|
219
|
+
installing = true;
|
|
220
|
+
try {
|
|
221
|
+
return await installDependencyLocked(id);
|
|
222
|
+
} finally {
|
|
223
|
+
installing = false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async function installDependencyLocked(id) {
|
|
188
227
|
const dependency = managedDependency(id);
|
|
189
228
|
if (dependency === void 0) throw new Error("不支持安装该依赖。");
|
|
190
229
|
const latestVersion = await npmLatestVersion(dependency.packageName);
|
|
191
230
|
if (latestVersion === void 0) throw new Error("无法获取 npm 最新版本,请检查网络或 npm registry 后重试。");
|
|
192
231
|
await ensureLatestReleaseAllowed(dependency.packageName, latestVersion);
|
|
193
|
-
await runDshPlugin([
|
|
232
|
+
await runDshPlugin([
|
|
233
|
+
"add",
|
|
234
|
+
`${dependency.packageName}@${latestVersion}`,
|
|
235
|
+
"--registry=https://registry.npmjs.org/"
|
|
236
|
+
]);
|
|
194
237
|
const installed = await installedPackageVersion(dependency.packageName);
|
|
195
238
|
if (installed !== latestVersion) throw new Error(`已请求 ${dependency.packageName}@${latestVersion},但当前仍是 ${installed ?? "未安装"}。请先停止 DSH Web 后再更新。`);
|
|
196
239
|
return dependencyStatuses();
|
|
@@ -206,7 +249,6 @@ function requireService(ctx, key, method) {
|
|
|
206
249
|
function hostServices(ctx) {
|
|
207
250
|
return {
|
|
208
251
|
webServer: requireService(ctx, "webServer", "register"),
|
|
209
|
-
sessions: requireService(ctx, "sessions", "get"),
|
|
210
252
|
agents: requireService(ctx, "agents", "get"),
|
|
211
253
|
tools: requireService(ctx, "tools", "schemas")
|
|
212
254
|
};
|
|
@@ -215,9 +257,41 @@ function hostServices(ctx) {
|
|
|
215
257
|
//#region src/index.ts
|
|
216
258
|
const connectorsEndpoint = "/api/michengai/codex-ui/connectors";
|
|
217
259
|
const dependenciesEndpoint = "/api/michengai/codex-ui/dependencies";
|
|
260
|
+
function headerValue(headers, name) {
|
|
261
|
+
const value = headers[name];
|
|
262
|
+
if (typeof value === "string") return value;
|
|
263
|
+
if (Array.isArray(value)) return value[0];
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* 判断浏览器请求是否跨站。依赖安装会改写用户配置并拉起子进程,
|
|
267
|
+
* 恶意网页只需一个表单就能跨站触发,必须先按 Sec-Fetch-Site(优先,
|
|
268
|
+
* 且无法被页面伪造)再按 Origin 与 Host 的比对阻断;无这些头的
|
|
269
|
+
* 非浏览器客户端(curl、CLI)仍然放行。
|
|
270
|
+
*/
|
|
271
|
+
function crossSiteRequest(request) {
|
|
272
|
+
const headers = request.headers;
|
|
273
|
+
if (headers === void 0) return false;
|
|
274
|
+
const site = headerValue(headers, "sec-fetch-site");
|
|
275
|
+
if (site === "same-origin" || site === "none") return false;
|
|
276
|
+
if (site !== void 0) return true;
|
|
277
|
+
const origin = headerValue(headers, "origin");
|
|
278
|
+
if (origin === void 0) return false;
|
|
279
|
+
const host = headerValue(headers, "host");
|
|
280
|
+
if (host === void 0) return true;
|
|
281
|
+
try {
|
|
282
|
+
return new URL(origin).host !== host;
|
|
283
|
+
} catch {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/** 把安装错误收成可给浏览器看的文案:我们自己的中文说明保留,带本地路径的底层错误脱敏。 */
|
|
288
|
+
function publicDependencyError(error) {
|
|
289
|
+
const message = error instanceof Error ? error.message : "依赖管理暂不可用。";
|
|
290
|
+
if (/[A-Za-z]:[\\/]|\/(?:home|Users|var|tmp)\//.test(message)) return "依赖管理暂不可用,请查看服务端日志。";
|
|
291
|
+
return message;
|
|
292
|
+
}
|
|
218
293
|
const inject = [
|
|
219
294
|
"webServer",
|
|
220
|
-
"sessions",
|
|
221
295
|
"agents",
|
|
222
296
|
"tools"
|
|
223
297
|
];
|
|
@@ -283,6 +357,14 @@ function apply(ctx) {
|
|
|
283
357
|
return;
|
|
284
358
|
}
|
|
285
359
|
if (request.method === "POST") {
|
|
360
|
+
if (crossSiteRequest(request)) {
|
|
361
|
+
response.writeHead(403, {
|
|
362
|
+
"content-type": "application/json; charset=utf-8",
|
|
363
|
+
"cache-control": "no-store"
|
|
364
|
+
});
|
|
365
|
+
response.end(JSON.stringify({ error: "已拒绝跨站请求。" }));
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
286
368
|
const dependencies = await installDependency(url.searchParams.get("dependency"));
|
|
287
369
|
response.writeHead(200, {
|
|
288
370
|
"content-type": "application/json; charset=utf-8",
|
|
@@ -297,12 +379,12 @@ function apply(ctx) {
|
|
|
297
379
|
response.writeHead(405);
|
|
298
380
|
response.end();
|
|
299
381
|
} catch (error) {
|
|
300
|
-
|
|
382
|
+
ctx.logger.warn("dependencies endpoint failed: %s", error);
|
|
301
383
|
response.writeHead(503, {
|
|
302
384
|
"content-type": "application/json; charset=utf-8",
|
|
303
385
|
"cache-control": "no-store"
|
|
304
386
|
});
|
|
305
|
-
response.end(JSON.stringify({ error:
|
|
387
|
+
response.end(JSON.stringify({ error: publicDependencyError(error) }));
|
|
306
388
|
}
|
|
307
389
|
}
|
|
308
390
|
});
|
|
@@ -313,4 +395,4 @@ function apply(ctx) {
|
|
|
313
395
|
}, "michengai-codex-ui: catalogs");
|
|
314
396
|
}
|
|
315
397
|
//#endregion
|
|
316
|
-
export { apply, inject };
|
|
398
|
+
export { apply, crossSiteRequest, inject, publicDependencyError };
|
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.54",
|
|
4
4
|
"description": "以 Codex 风格重构 DSH Web 侧栏的独立客户端插件",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsc --noEmit && tsdown",
|
|
56
|
-
"test": "tsx tests/pinned-sessions.assert.ts && tsx tests/session-manager.assert.ts && tsx tests/workspace-browser.assert.ts && tsx tests/sidebar-search.assert.ts && vitest run tests/client-runtime.integration.spec.ts && tsx tests/settings-integration.assert.ts && tsx tests/about-dependencies.assert.ts && tsx tests/dependency-manager.assert.ts && tsx tests/conversation-visuals.assert.ts && tsx tests/client-bundle.assert.ts && tsx tests/codex-suite.assert.ts"
|
|
56
|
+
"test": "tsx tests/permission-i18n.assert.ts && tsx tests/hover-tip.assert.ts && tsx tests/companion-slots.assert.ts && tsx tests/channel-api.assert.ts && tsx tests/schedule-sessions.assert.ts && tsx tests/session-tree.assert.ts && tsx tests/pinned-sessions.assert.ts && tsx tests/session-manager.assert.ts && tsx tests/workspace-browser.assert.ts && tsx tests/sidebar-search.assert.ts && tsx tests/settings-navigation.assert.ts && tsx tests/settings-nav-icons.assert.ts && tsx tests/locales.assert.ts && vitest run tests/client-runtime.integration.spec.ts && tsx tests/settings-integration.assert.ts && tsx tests/about-dependencies.assert.ts && tsx tests/dependency-manager.assert.ts && tsx tests/conversation-bubbles.assert.ts && tsx tests/conversation-header.assert.ts && tsx tests/conversation-visuals.assert.ts && tsdown && tsx tests/client-bundle.assert.ts && tsx tests/codex-suite.assert.ts"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@deepseek-ai/cordis": ">=4.0.1 <5.0.0",
|
|
@@ -71,8 +71,12 @@
|
|
|
71
71
|
"react": "^18.2.0"
|
|
72
72
|
},
|
|
73
73
|
"peerDependenciesMeta": {
|
|
74
|
-
"@michengai/dsh-agency-agents": {
|
|
75
|
-
|
|
74
|
+
"@michengai/dsh-agency-agents": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@michengai/dsh-skills-manager": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
76
80
|
},
|
|
77
81
|
"devDependencies": {
|
|
78
82
|
"@deepseek-ai/cordis": "4.0.1",
|
|
@@ -86,17 +90,15 @@
|
|
|
86
90
|
"@deepseek-ai/dsh-client-ui-settings-general": "0.1.0-rc.6",
|
|
87
91
|
"@deepseek-ai/dsh-client-ui-sidebar": "0.1.0-rc.6",
|
|
88
92
|
"@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6",
|
|
93
|
+
"@types/jsdom": "^30.0.0",
|
|
89
94
|
"@types/node": "^26.0.0",
|
|
90
95
|
"@types/react": "~18.3.1",
|
|
96
|
+
"jsdom": "^29.1.1",
|
|
91
97
|
"react": "^18.3.1",
|
|
92
98
|
"react-dom": "^18.3.1",
|
|
93
99
|
"tsdown": "^0.22.2",
|
|
94
100
|
"tsx": "^4.22.4",
|
|
95
101
|
"typescript": "^6.0.3",
|
|
96
|
-
"vitest": "^4.1.8"
|
|
97
|
-
"jsdom": "^29.1.1"
|
|
102
|
+
"vitest": "^4.1.8"
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|