dsh-plugin-marketplace 0.2.5 → 0.2.7
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/LICENSE +21 -21
- package/README.md +4 -7
- package/README.zh.md +3 -5
- package/client.js +13 -10
- package/index.js +14 -11
- package/package.json +4 -4
- package/vendor/dsh-settings-expose.js +0 -106
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Scorp1o117
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Scorp1o117
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -50,13 +50,10 @@ Manual mount in `$DSH_HOME/profiles/web/cordis.patch.yml`:
|
|
|
50
50
|
Then restart `dsh web` (new client plugins require a process restart to be
|
|
51
51
|
scanned into the browser roster) and open **Settings → Plugin Marketplace**.
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
`dsh web` is restarted. Restart once; if the error persists, check the boot
|
|
58
|
-
log for `[settings-expose]` lines (the fix line names the file to edit —
|
|
59
|
-
`WEB_SETTINGS_NAMESPACES` in `dsh-host-apiproxy/lib/index.js`).
|
|
53
|
+
DSH `0.1.1-rc.1` and newer expose every registered settings namespace, so the
|
|
54
|
+
marketplace works without patching official files. Older hosts that return
|
|
55
|
+
`settings-not-exposed` should be upgraded instead of modifying
|
|
56
|
+
`dsh-host-apiproxy` in `node_modules`.
|
|
60
57
|
|
|
61
58
|
## How it works
|
|
62
59
|
|
package/README.zh.md
CHANGED
|
@@ -41,11 +41,9 @@ dsh plugin --profile web add dsh-plugin-marketplace
|
|
|
41
41
|
|
|
42
42
|
然后重启 `dsh web`(新客户端插件需要重启进程才会被扫描进浏览器清单),打开 **设置 → 插件市场**。
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
重启一次即可;若重启后仍报错,看启动日志里的 `[settings-expose]` 提示(它会指出要手动
|
|
48
|
-
编辑的文件:`dsh-host-apiproxy/lib/index.js` 的 `WEB_SETTINGS_NAMESPACES`)。
|
|
44
|
+
DSH `0.1.1-rc.1` 起会公开全部已注册的 settings 命名空间,插件市场无需再修改官方文件。
|
|
45
|
+
旧宿主若返回 `settings-not-exposed`,请升级 DSH,不要手动修改 `node_modules` 中的
|
|
46
|
+
`dsh-host-apiproxy`。
|
|
49
47
|
|
|
50
48
|
## 实现方式
|
|
51
49
|
|
package/client.js
CHANGED
|
@@ -82,7 +82,7 @@ window.__ModuleLoader__.load({
|
|
|
82
82
|
aiExplain: "🤖 AI 解释",
|
|
83
83
|
aiExplaining: "AI 解释中,请稍候…",
|
|
84
84
|
aiExplainErr: "AI 解释失败:{msg}",
|
|
85
|
-
notExposed: "
|
|
85
|
+
notExposed: "当前 DSH 版本没有公开插件市场的设置通道;请升级到 DSH 0.1.1-rc.1 或更高版本并重启 dsh web。"
|
|
86
86
|
};
|
|
87
87
|
var en = {
|
|
88
88
|
nav: "Plugin Marketplace",
|
|
@@ -113,7 +113,7 @@ window.__ModuleLoader__.load({
|
|
|
113
113
|
aiExplain: "🤖 AI Explain",
|
|
114
114
|
aiExplaining: "AI is explaining…",
|
|
115
115
|
aiExplainErr: "AI explain failed: {msg}",
|
|
116
|
-
notExposed: "
|
|
116
|
+
notExposed: "This DSH version does not expose the plugin-marketplace settings channel. Upgrade to DSH 0.1.1-rc.1 or newer and restart dsh web."
|
|
117
117
|
};
|
|
118
118
|
|
|
119
119
|
// ── GitHub API ────────────────────────────────────────────────────────
|
|
@@ -207,7 +207,10 @@ window.__ModuleLoader__.load({
|
|
|
207
207
|
var st = props.installState; // {status, message, pkg} from the settings scope
|
|
208
208
|
var active = st && st.pkg === installName;
|
|
209
209
|
var status = active ? st.status : "idle";
|
|
210
|
-
var explainState = props.explainState; // {status, text} from the settings scope
|
|
210
|
+
var explainState = props.explainState; // {status, text, repo} from the settings scope
|
|
211
|
+
// The settings field is namespace-global and persists across sessions,
|
|
212
|
+
// so only show it when it belongs to the plugin currently open.
|
|
213
|
+
var explainMine = !!explainState && explainState.repo === p.fullName;
|
|
211
214
|
var [confirming, setConfirming] = react.useState(false);
|
|
212
215
|
var statusNode = null;
|
|
213
216
|
if (status === "running") {
|
|
@@ -264,14 +267,14 @@ window.__ModuleLoader__.load({
|
|
|
264
267
|
h("div", { className: "__mp_translateRow" },
|
|
265
268
|
h("button", { type: "button", className: "__mp_translateBtn",
|
|
266
269
|
style: { marginTop: "8px", padding: "4px 10px", fontSize: "12px", cursor: "pointer", background: "var(--dsw-alias-bg-hover, #21262d)", color: "var(--dsw-alias-label-primary, #e6edf3)", border: "1px solid var(--dsw-alias-border, #30363d)", borderRadius: "6px" },
|
|
267
|
-
disabled:
|
|
270
|
+
disabled: explainMine && explainState.status === "running",
|
|
268
271
|
onClick: function () { props.onExplain(p.fullName, p.desc, props.readme || ""); }
|
|
269
272
|
}, props.t("aiExplain")),
|
|
270
273
|
h("div", { id: "__mp_translateOut", style: { fontSize: "12px", lineHeight: "1.6", color: "var(--dsw-alias-label-secondary, #8b949e)", whiteSpace: "pre-wrap", wordBreak: "break-word", marginTop: "8px", borderTop: "1px solid var(--dsw-alias-border, #21262d)", paddingTop: "8px" } },
|
|
271
|
-
props.explainError ? h("span", { style: { color: "var(--dsw-alias-state-danger-text, #f85149)" } }, props.explainError)
|
|
272
|
-
:
|
|
273
|
-
:
|
|
274
|
-
:
|
|
274
|
+
explainMine && props.explainError ? h("span", { style: { color: "var(--dsw-alias-state-danger-text, #f85149)" } }, props.explainError)
|
|
275
|
+
: explainMine && explainState.status === "running" ? props.t("aiExplaining")
|
|
276
|
+
: explainMine && explainState.status === "error" ? props.t("aiExplainErr").replace("{msg}", explainState.text || "unknown")
|
|
277
|
+
: explainMine && explainState.status === "ok" ? explainState.text
|
|
275
278
|
: ""
|
|
276
279
|
)
|
|
277
280
|
)
|
|
@@ -294,8 +297,8 @@ window.__ModuleLoader__.load({
|
|
|
294
297
|
var un = typeof scope.subscribe === "function" ? scope.subscribe(sync) : null;
|
|
295
298
|
return function () { alive = false; if (un) un(); if (scope.dispose) scope.dispose(); };
|
|
296
299
|
}, [scope]);
|
|
297
|
-
// settings-not-exposed
|
|
298
|
-
//
|
|
300
|
+
// Older hosts can still return settings-not-exposed. Recommend upgrading
|
|
301
|
+
// instead of mutating the installed DSH package on disk.
|
|
299
302
|
var mutateError = function (detail, fallback) {
|
|
300
303
|
if (detail && detail.code === "settings-not-exposed") return t("notExposed");
|
|
301
304
|
return String(detail && (detail.message || detail.code) || fallback);
|
package/index.js
CHANGED
|
@@ -17,7 +17,6 @@ import os from "node:os";
|
|
|
17
17
|
import z from "@deepseek-ai/schemastery";
|
|
18
18
|
import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
19
19
|
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
20
|
-
import { ensureSettingsNamespaceExposed } from "./vendor/dsh-settings-expose.js";
|
|
21
20
|
|
|
22
21
|
/** Cordis plugin name. */
|
|
23
22
|
const name = "plugin-marketplace";
|
|
@@ -49,6 +48,8 @@ const Config = z.object({
|
|
|
49
48
|
aiExplainResult: z.object({
|
|
50
49
|
status: z.string().default("idle"), // idle | running | ok | error
|
|
51
50
|
text: z.string().default(""),
|
|
51
|
+
/** repo this result belongs to ("owner/name"); client gates display on it. */
|
|
52
|
+
repo: z.string().default(""),
|
|
52
53
|
ts: z.number().default(0),
|
|
53
54
|
}),
|
|
54
55
|
});
|
|
@@ -76,8 +77,6 @@ function apply(ctx, config) {
|
|
|
76
77
|
|
|
77
78
|
// dsh-host-apiproxy hard-codes which namespaces the Web client may touch;
|
|
78
79
|
// patch the allowlist idempotently so the client can write install requests.
|
|
79
|
-
ensureSettingsNamespaceExposed(ctx, "plugin-marketplace", ctx.logger);
|
|
80
|
-
|
|
81
80
|
/** Resolved current config (settings layer over the entry). */
|
|
82
81
|
function current() {
|
|
83
82
|
return sourceGetter ? sourceGetter() : config;
|
|
@@ -212,7 +211,10 @@ function apply(ctx, config) {
|
|
|
212
211
|
await report("running", `installing ${pkg}…`, pkg);
|
|
213
212
|
const result = await runInstall(pkg, profile);
|
|
214
213
|
if (result.code !== 0) {
|
|
215
|
-
const
|
|
214
|
+
const output = (result.stderr || result.stdout || "").trim();
|
|
215
|
+
const tail = output.includes("ERR_PNPM_IGNORED_BUILDS")
|
|
216
|
+
? "dependency build scripts were blocked by pnpm; the plugin author must make native build dependencies opt-in"
|
|
217
|
+
: output.split("\n").slice(-3).join(" ");
|
|
216
218
|
ctx.logger.warn(`[plugin-marketplace] install failed (${result.code}): ${tail}`);
|
|
217
219
|
await report("error", `install failed: ${tail || "unknown error"}`, pkg);
|
|
218
220
|
return;
|
|
@@ -227,12 +229,12 @@ function apply(ctx, config) {
|
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
/** Write the AI-explain result report (and clear the consumed request). */
|
|
230
|
-
async function reportExplain(status, text) {
|
|
232
|
+
async function reportExplain(status, text, repo) {
|
|
231
233
|
const settings = ctx.get("settings");
|
|
232
234
|
if (!settings) return;
|
|
233
235
|
const ts = Date.now();
|
|
234
236
|
await settings.update(NS, {
|
|
235
|
-
aiExplainResult: { status, text, ts },
|
|
237
|
+
aiExplainResult: { status, text, repo: repo || "", ts },
|
|
236
238
|
aiExplain: { repo: "", desc: "", readme: "", ts: 0 },
|
|
237
239
|
}).catch((error) => {
|
|
238
240
|
ctx.logger.warn(`[plugin-marketplace] explain state write failed: ${String(error)}`);
|
|
@@ -245,14 +247,16 @@ function apply(ctx, config) {
|
|
|
245
247
|
* settings-backed message channel as the install flow.
|
|
246
248
|
*/
|
|
247
249
|
async function maybeRunExplain() {
|
|
250
|
+
let repo = "";
|
|
248
251
|
try {
|
|
249
252
|
const cfg = current();
|
|
250
253
|
const req = cfg?.aiExplain;
|
|
251
254
|
if (!req || !req.repo || req.ts === lastExplainTs || req.ts === 0) return;
|
|
252
255
|
lastExplainTs = req.ts;
|
|
256
|
+
repo = String(req.repo);
|
|
253
257
|
const llm = ctx.llm;
|
|
254
258
|
if (!llm) {
|
|
255
|
-
await reportExplain("error", "LLM service unavailable; configure a model in Settings → Models.");
|
|
259
|
+
await reportExplain("error", "LLM service unavailable; configure a model in Settings → Models.", repo);
|
|
256
260
|
return;
|
|
257
261
|
}
|
|
258
262
|
// Route through the deployment's default model when one is configured.
|
|
@@ -261,7 +265,6 @@ function apply(ctx, config) {
|
|
|
261
265
|
const defaults = ctx.agentDefaultModel?.currentSelection?.();
|
|
262
266
|
if (defaults?.provider && defaults?.model) route = defaults;
|
|
263
267
|
} catch { /* no default model service; fall back to the adapter default */ }
|
|
264
|
-
const repo = String(req.repo);
|
|
265
268
|
const desc = String(req.desc || "").trim();
|
|
266
269
|
const readme = String(req.readme || "").trim().slice(0, 1500);
|
|
267
270
|
const prompt =
|
|
@@ -269,7 +272,7 @@ function apply(ctx, config) {
|
|
|
269
272
|
(desc ? `简介:${desc}\n` : "") +
|
|
270
273
|
(readme ? `README 摘要:\n${readme}\n` : "") +
|
|
271
274
|
`\n请用简体中文、3~5 句话直接告诉我这个插件大概是干嘛的(核心用途、解决什么问题、适合谁)。不要复述仓库名,不要罗列安装步骤。`;
|
|
272
|
-
await reportExplain("running", "");
|
|
275
|
+
await reportExplain("running", "", repo);
|
|
273
276
|
ctx.logger.info(`[plugin-marketplace] explaining ${repo} (model=${route ? `${route.provider}/${route.model}` : "default"})…`);
|
|
274
277
|
const messages = [createUserMessage({
|
|
275
278
|
content: [{ type: "text", text: prompt }],
|
|
@@ -293,10 +296,10 @@ function apply(ctx, config) {
|
|
|
293
296
|
.join(" ")
|
|
294
297
|
.trim();
|
|
295
298
|
if (!text) throw new Error("model produced no text");
|
|
296
|
-
await reportExplain("ok", text);
|
|
299
|
+
await reportExplain("ok", text, repo);
|
|
297
300
|
} catch (error) {
|
|
298
301
|
ctx.logger.warn(`[plugin-marketplace] explain flow failed: ${String(error)}`);
|
|
299
|
-
await reportExplain("error", String(error instanceof Error ? error.message : error));
|
|
302
|
+
await reportExplain("error", String(error instanceof Error ? error.message : error), repo);
|
|
300
303
|
}
|
|
301
304
|
}
|
|
302
305
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-marketplace",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "DeepSeek Harness Web UI 内置插件市场:在设置页直接浏览 github.com/topics/dsh-plugin,支持搜索、按 Star 排序、查看安装方式。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": "^18.2.0",
|
|
50
|
-
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
51
|
-
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6"
|
|
50
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6 || ^0.1.1-rc.1",
|
|
51
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.1"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-host-apiproxy exposes only namespaces listed in its hard-coded
|
|
3
|
-
* WEB_SETTINGS_NAMESPACES allowlist to the Web settings client. A namespace
|
|
4
|
-
* registered by a third-party plugin answers `settings-not-exposed` even
|
|
5
|
-
* though it is registered — upstream explicitly defers letting plugins
|
|
6
|
-
* expose their own configuration.
|
|
7
|
-
*
|
|
8
|
-
* This module idempotently patches that allowlist in the dsh installation
|
|
9
|
-
* actually loaded by the host process, so the plugin's settings section
|
|
10
|
-
* becomes visible in the Web UI without manual edits. A dsh update
|
|
11
|
-
* overwrites the file; the next plugin start re-patches it.
|
|
12
|
-
*
|
|
13
|
-
* NOTE: dsh-host-apiproxy is an ESM package, so it never appears in the
|
|
14
|
-
* CommonJS `Module._cache` (lookup 1 only helps CJS hosts). Lookup 2 anchors
|
|
15
|
-
* on the running dsh bin, which always resolves the apiproxy as the app's
|
|
16
|
-
* own dependency; lookup 3 walks up from `@deepseek-ai/dsh-settings` for
|
|
17
|
-
* other npm layouts. pnpm-installed hosts resolve into an immutable store —
|
|
18
|
-
* the write then fails and the manual instruction below applies.
|
|
19
|
-
*
|
|
20
|
-
* Also note that patching the file only affects the NEXT process start: the
|
|
21
|
-
* running apiproxy already evaluated its allowlist at module load, so a
|
|
22
|
-
* settings channel opened mid-session still answers `settings-not-exposed`
|
|
23
|
-
* until `dsh web` is restarted.
|
|
24
|
-
*/
|
|
25
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
26
|
-
import { createRequire } from "node:module";
|
|
27
|
-
import { dirname, join, sep } from "node:path";
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Ensure `nsName` is present in dsh-host-apiproxy's WEB_SETTINGS_NAMESPACES.
|
|
31
|
-
* No-op when already exposed or when the file cannot be located/patched.
|
|
32
|
-
* @param {import("cordis").Context} ctx
|
|
33
|
-
* @param {string} nsName - settings namespace short name (e.g. "tdai-memory").
|
|
34
|
-
* @param {{info?: Function, warn?: Function}} logger - dsh logger.
|
|
35
|
-
*/
|
|
36
|
-
export function ensureSettingsNamespaceExposed(ctx, nsName, logger) {
|
|
37
|
-
void ctx;
|
|
38
|
-
try {
|
|
39
|
-
const target = findApiproxyIndex();
|
|
40
|
-
if (!target) {
|
|
41
|
-
logger?.warn?.(`[settings-expose] could not locate dsh-host-apiproxy in this dsh install; the "${nsName}" settings channel will answer "not exposed". Fix: add "${nsName}" to WEB_SETTINGS_NAMESPACES in <dsh>/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/index.js and restart dsh web`);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
let src;
|
|
45
|
-
try {
|
|
46
|
-
src = readFileSync(target, "utf8");
|
|
47
|
-
} catch (error) {
|
|
48
|
-
logger?.warn?.(`[settings-expose] cannot read ${target}: ${String(error)}`);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const body = src.match(/const WEB_SETTINGS_NAMESPACES = \[([\s\S]*?)\];/)?.[1] ?? "";
|
|
52
|
-
if (body.includes(`"${nsName}"`)) return; // already exposed (manual or previous patch)
|
|
53
|
-
const patched = src.replace(/(const WEB_SETTINGS_NAMESPACES = \[[\s\S]*?)(\n\s*\];)/, (_, pre, post) => {
|
|
54
|
-
const trailingComma = /,\s*$/.test(pre) ? "" : ",";
|
|
55
|
-
const sep2 = pre.trimEnd().endsWith("[") ? "" : trailingComma;
|
|
56
|
-
return `${pre}${sep2}\n\t"${nsName}"${post}`;
|
|
57
|
-
});
|
|
58
|
-
if (patched === src) {
|
|
59
|
-
logger?.warn?.(`[settings-expose] allowlist pattern not found in ${target}; add "${nsName}" to WEB_SETTINGS_NAMESPACES manually`);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
writeFileSync(target, patched, "utf8");
|
|
63
|
-
logger?.info?.(`[settings-expose] added "${nsName}" to WEB_SETTINGS_NAMESPACES (${target}) — restart dsh web: the settings channel opens only in the NEXT process start`);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
logger?.warn?.(`[settings-expose] failed to expose "${nsName}" (${String(error)}); add it to WEB_SETTINGS_NAMESPACES in dsh-host-apiproxy/lib/index.js manually, then restart dsh web`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function findApiproxyIndex() {
|
|
70
|
-
// 1) The host process has already loaded dsh-host-apiproxy: read the real
|
|
71
|
-
// module path from the CommonJS module cache (any install layout; ESM
|
|
72
|
-
// hosts — every modern dsh — skip this).
|
|
73
|
-
try {
|
|
74
|
-
const Module = createRequire(import.meta.url)("module");
|
|
75
|
-
const cache = Module._cache ?? {};
|
|
76
|
-
for (const key of Object.keys(cache)) {
|
|
77
|
-
if (key.includes(`${sep}dsh-host-apiproxy${sep}`) && key.endsWith(`${sep}index.js`)) return key;
|
|
78
|
-
}
|
|
79
|
-
} catch { /* fall through */ }
|
|
80
|
-
// 2) Resolve the apiproxy as a dependency of the running dsh app: the bin
|
|
81
|
-
// (`node <dsh>/lib/bin.js`) anchors the app root in both shipped and
|
|
82
|
-
// source layouts, and dsh-host-apiproxy is always one of its deps.
|
|
83
|
-
try {
|
|
84
|
-
const appRoot = dirname(dirname(process.argv[1] ?? ""));
|
|
85
|
-
const requireFromApp = createRequire(join(appRoot, "package.json"));
|
|
86
|
-
const entry = requireFromApp.resolve("@deepseek-ai/dsh-host-apiproxy");
|
|
87
|
-
if (entry.endsWith(`${sep}index.js`)) return entry;
|
|
88
|
-
} catch { /* fall through */ }
|
|
89
|
-
// 3) Walk up from @deepseek-ai/dsh-settings: the apiproxy is a scoped
|
|
90
|
-
// sibling in the same dependency tree (flat/global and nested layouts).
|
|
91
|
-
try {
|
|
92
|
-
const require = createRequire(import.meta.url);
|
|
93
|
-
const settingsEntry = require.resolve("@deepseek-ai/dsh-settings");
|
|
94
|
-
let dir = dirname(settingsEntry);
|
|
95
|
-
while (true) {
|
|
96
|
-
const scoped = join(dir, "@deepseek-ai", "dsh-host-apiproxy", "lib", "index.js");
|
|
97
|
-
if (existsSync(scoped)) return scoped;
|
|
98
|
-
const bare = join(dir, "dsh-host-apiproxy", "lib", "index.js");
|
|
99
|
-
if (existsSync(bare)) return bare;
|
|
100
|
-
const parent = dirname(dir);
|
|
101
|
-
if (parent === dir) break;
|
|
102
|
-
dir = parent;
|
|
103
|
-
}
|
|
104
|
-
} catch { /* fall through */ }
|
|
105
|
-
return "";
|
|
106
|
-
}
|