dsh-superagent-console 0.1.2 → 0.1.3
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 +40 -63
- package/lib/client.js +429 -205
- package/lib/index.d.ts +11 -1
- package/lib/index.js +170 -7
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,78 +1,77 @@
|
|
|
1
1
|
# dsh-superagent-console
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
dsh-superagent 引擎的桌面控制台插件:同源代理 + 引擎状态检测 + 引导式在线安装 + 插件管理 + 模型供应商同步。引擎运行于独立的 superagent profile(默认 `127.0.0.1:3082`),本插件不承载任何引擎状态。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- **同源代理**:宿主 `/orchestrator/api` 转发至引擎(补 `x-orchestrator-request` 头),控制台自有 API 位于 `/superagent-console/api`
|
|
8
|
+
- **引擎检测**:L0–L3 分层探针,状态与动作一一映射
|
|
9
|
+
- **引导式在线安装**:八段流水线(`prereqs → checkout → build → profile → plugin → patch → start → verify`),每段幂等、支持断点续跑
|
|
10
|
+
- **插件管理**:面板内直接安装/移除/启停引擎 profile 的插件
|
|
11
|
+
- **模型供应商同步**:把宿主生效的 `llm-pi-ai.providers` 同步到引擎 profile
|
|
4
12
|
|
|
5
13
|
## 架构
|
|
6
14
|
|
|
7
15
|
```
|
|
8
16
|
桌面 App(desktop profile)
|
|
9
17
|
└─ dsh-superagent-console
|
|
10
|
-
├─ 宿主 /orchestrator/api →
|
|
18
|
+
├─ 宿主 /orchestrator/api → 同源代理转发引擎(补 x-orchestrator-request 头)
|
|
11
19
|
├─ 宿主 /superagent-console/api → 控制台自有 API(检测 / 安装 / 启动 / 重启 / 插件管理 / 供应商同步)
|
|
12
20
|
└─ 客户端 → 门控面板:引擎在线时挂官方面板(含「插件」管理 Tab),
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## 插件管理 Tab(2026-11-04)
|
|
16
|
-
|
|
17
|
-
官方面板内新增「插件」Tab(位于「设置」之后),管理 superagent 引擎 profile 的插件依赖:
|
|
18
|
-
|
|
19
|
-
- **列表**:profile package.json 的依赖清单 + 已安装版本/描述 + 是否声明 `dsh.bundle` + 是否在 `dsh.profile.bundles` 挂载集内;引擎核心 `dsh-superagent` 标记保护(不可移除/停用)。
|
|
20
|
-
- **安装 / 移除**:走官方 CLI 路径 `dsh plugin --profile superagent add|remove <spec>`(execFile argv 数组、单飞互斥、输出脱敏、audit.jsonl 审计),复用安装器的 bundle reconcile 语义;改动在**重启引擎后生效**,页面提供重启按钮。
|
|
21
|
-
- **启停(挂载集编辑)**:直接原子改写 profile package.json 的 `dsh.profile.bundles`(保留 0600 权限;无变更不写盘);与安装/移除共用同一互斥。
|
|
22
|
-
- API:`GET/POST /superagent-console/api/plugins[/install|/remove|/enable|/operation]`,实现见 `src/plugins.ts`(运行时验证:假 profile 16 项断言 + 真实 profile 只读 list)。
|
|
23
|
-
|
|
24
|
-
vendor 补丁(seams):`sync-client.mjs` 在每次 vendor 复制后,对 `client/route.ts`(TABS/Route/parseRoute)、`client/app.tsx`(路由分支 + import)、`client/locales.ts`(`tab.plugins` + `plugins.*` 双语词典)施加**精确锚点补丁**;上游锚点漂移会让 sync 显式失败,不会打出半接线的 Tab。页面本体在 `src/client/plugins-page.tsx`(自有文件,不受 vendor 覆盖)。
|
|
21
|
+
否则渲染安装引导状态卡
|
|
25
22
|
```
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
## 引擎检测(L0-L3 分层探针)
|
|
24
|
+
## 引擎检测(L0–L3 分层探针)
|
|
30
25
|
|
|
31
26
|
| 层 | 探测 | 判定 |
|
|
32
27
|
|---|---|---|
|
|
33
28
|
| L0 | 服务端 fetch `{target}/orchestrator/api/state`(带 `x-orchestrator-request: 1`) | 引擎可达 |
|
|
34
|
-
| L1 |
|
|
35
|
-
| L2 | `scripts/dsh-superagent status`(exit 0 健康 / 1 停止 / 2 组件错误 / 3 等待 setup) | 健康细分 |
|
|
29
|
+
| L1 | 引擎 profile 的 package.json 依赖含 `dsh-superagent` 且 checkout 存在 `lib/` 产物 | 服务已部署 |
|
|
30
|
+
| L2 | 引擎生命周期脚本 `scripts/dsh-superagent status`(exit 0 健康 / 1 停止 / 2 组件错误 / 3 等待 setup) | 健康细分 |
|
|
36
31
|
| L3 | dsh ≥0.2.1、node ^22.19\|\|≥24、pnpm、git、tmux、python3(缺项附补齐提示命令) | 前置依赖 |
|
|
37
32
|
|
|
38
|
-
状态 →
|
|
33
|
+
状态 → 动作映射:`running` → 官方面板;`installed-stopped` → 启动按钮;`installed-unhealthy` → 重启按钮;`not-installed` → 在线安装;`prereq-missing` → 列出缺项与补齐命令(不提供安装按钮)。
|
|
39
34
|
|
|
40
35
|
探测 PATH 由 `spawnPath` 组装:运行中 node 的 bin 目录 + 配置的 `extraPath` + **桌面 App 自带的 dsh CLI 目录**(从运行中二进制相对推导 `<App>.app/Contents/Resources/runtime/cli/bin`,存在才追加)。因此桌面用户无需自行安装 dsh;`extraPath` 中显式安装的用户版本优先于内置 CLI。
|
|
41
36
|
|
|
42
|
-
##
|
|
37
|
+
## 在线安装流水线
|
|
43
38
|
|
|
44
39
|
`prereqs → checkout → build → profile → plugin → patch → start → verify`
|
|
45
40
|
|
|
46
|
-
- **来源**:`git clone`
|
|
41
|
+
- **来源**:`git clone` 官方仓库(`dsh-superagent` 在 npm 上不存在且 `private: true`,GitHub 无 release;源码构建 + link 是官方唯一路径)。
|
|
47
42
|
- **自管目录**:默认 `~/.dsh/state/superagent-console/engine`(clone 与构建位置),随附 `setup.json`(断点续跑状态)与 `audit.jsonl`(审计日志)。
|
|
48
43
|
- **守卫**:
|
|
49
|
-
- `dsh <profile> --from-default-profile web`
|
|
50
|
-
- profile 的 `cordis.patch.yml`
|
|
44
|
+
- `dsh <profile> --from-default-profile web` 是覆盖式命令(源码确认:replace a fresh copy),profile 目录存在即拒绝执行;
|
|
45
|
+
- profile 的 `cordis.patch.yml` 已存在则不覆盖(其中含操作者手工维护的 Source 配置);
|
|
51
46
|
- clone 目标目录非空且非 git 仓库即拒绝;
|
|
52
47
|
- 单实例锁(进程内互斥 + pid 锁文件,跨桌面重启可检测陈锁);
|
|
53
|
-
- 全部命令走 `execFile` argv
|
|
54
|
-
-
|
|
48
|
+
- 全部命令走 `execFile` argv 数组(不经过 shell 拼接),超时必杀,审计脱敏 `user:pass@`。
|
|
49
|
+
- **每段幂等**:已完成段自动跳过,失败段重按「继续安装」即从断点续跑。
|
|
55
50
|
- 首次启动 exit 3(等待 Source setup)属预期,verify 段放行。
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
启动/重启复用引擎自带生命周期脚本 `scripts/dsh-superagent start|restart`(无头托管、拒绝重复启动、URL 冲突检测),`--profile/--url` 由插件配置注入。
|
|
58
53
|
|
|
59
|
-
|
|
54
|
+
## 插件管理 Tab
|
|
55
|
+
|
|
56
|
+
官方面板内新增「插件」Tab,管理 superagent 引擎 profile 的插件依赖:
|
|
57
|
+
|
|
58
|
+
- **列表**:profile package.json 依赖清单 + 已安装版本/描述 + 是否声明 `dsh.bundle` + 是否在 `dsh.profile.bundles` 挂载集内;引擎核心 `dsh-superagent` 标记保护(不可移除/停用)。
|
|
59
|
+
- **安装 / 移除**:走官方 CLI 路径 `dsh plugin --profile superagent add|remove <spec>`(单飞互斥、输出脱敏、审计日志),复用安装器的 bundle reconcile 语义;改动在**重启引擎后生效**,页面提供重启按钮。
|
|
60
|
+
- **启停(挂载集编辑)**:原子改写 profile package.json 的 `dsh.profile.bundles`(保留 0600 权限;无变更不写盘);与安装/移除共用同一互斥。
|
|
60
61
|
|
|
61
62
|
## 模型供应商同步
|
|
62
63
|
|
|
63
|
-
引擎的模型目录来自其所在 profile 的 `llm-pi-ai` 配置,且引擎管理 API
|
|
64
|
+
引擎的模型目录来自其所在 profile 的 `llm-pi-ai` 配置,且引擎管理 API 对供应商只读,因此引擎侧唯一写入面是 profile `cordis.patch.yml` 的 `llm-pi-ai` 条目。本插件把安装宿主当前生效的 `llm-pi-ai.providers`(settings 服务 `describe()` 读取)同步过去:
|
|
64
65
|
|
|
65
66
|
- **merge(默认)**:只把引擎缺少的 provider 追加进 `providers:`,已有条目一律不动(尊重手工维护);
|
|
66
67
|
- **mirror**:引擎 providers 全量替换为本端(减去排除清单),引擎侧多出的删除;
|
|
67
68
|
- **排除清单** `syncExclude`:其中的 provider id 既不复制也不删除;
|
|
68
69
|
- **凭据守卫**:provider 配置携带明文 `apiKey`/`apiToken` 字段时跳过并告警——只同步引用型凭据(`apiKeyEnv` 环境变量名),秘密值永不复制;
|
|
69
|
-
- **文件手术**:只改 `llm-pi-ai`
|
|
70
|
+
- **文件手术**:只改 `llm-pi-ai` 这一个条目,其余条目与全部注释逐字节保留;写前时间戳备份(保留最近 5 份)、临时文件原子写、写后 YAML 重解析验证(`llm-pi-ai` 条目不完整即拒绝落盘);
|
|
70
71
|
- **幂等**:输入不变时第二跑零写入;
|
|
71
72
|
- **生效**:写盘后需重启引擎(卡片提示一键重启);`autoSync` 开启时监听宿主 settings 变化(30s 轮询 diff),`autoRestart` 开启且引擎在运行时同步后自动重启。
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
## 配置项(默认值均为通用约定,不绑定具体机器)
|
|
74
|
+
## 配置项
|
|
76
75
|
|
|
77
76
|
| 键 | 默认 | 说明 |
|
|
78
77
|
|---|---|---|
|
|
@@ -86,40 +85,18 @@ API:`GET /superagent-console/api/providers`(两侧摘要 + diff + 同步状
|
|
|
86
85
|
| `autoSync` | `false` | 宿主 settings 变化时自动同步引擎 profile(30s 轮询 diff) |
|
|
87
86
|
| `autoRestart` | `false` | 自动同步后且引擎在运行时自动重启引擎 |
|
|
88
87
|
|
|
89
|
-
##
|
|
90
|
-
|
|
91
|
-
```sh
|
|
92
|
-
pnpm install
|
|
93
|
-
pnpm build # 先 sync-client(vendor 源码图),再 tsdown(node ESM + browser CJS 双产物)
|
|
94
|
-
pnpm check # typecheck + build
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
升级引擎面板:原型 checkout 更新后 `pnpm build && cd ../dsh-superagent-console && pnpm build`(vendor 重新拉取源码图)。若在 worktree 内构建,sync 脚本会经 `git rev-parse --git-common-dir` 定位主 checkout 的兄弟目录。
|
|
98
|
-
|
|
99
|
-
## 代理独立验证(不碰桌面进程)
|
|
88
|
+
## 安装
|
|
100
89
|
|
|
101
90
|
```sh
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
## 安装隔离演练(不碰现有 superagent profile)
|
|
106
|
-
|
|
107
|
-
```sh
|
|
108
|
-
pnpm test:install -- --status-only # 仅跑检测探针
|
|
109
|
-
pnpm test:install # 全流程:superagent-test profile + /tmp checkout + 3099 端口
|
|
110
|
-
```
|
|
91
|
+
# 从 npm 安装
|
|
92
|
+
dsh plugin --profile desktop add dsh-superagent-console
|
|
111
93
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
```sh
|
|
115
|
-
python3 /tmp/sa-console-rehearsal/engine/scripts/dsh-superagent stop --profile superagent-test --url http://127.0.0.1:3099
|
|
116
|
-
rm -rf /tmp/sa-console-rehearsal ~/.dsh/profiles/superagent-test ~/.dsh/state/superagent-test
|
|
94
|
+
# 或从本地源码安装(开发场景)
|
|
95
|
+
dsh plugin --profile desktop add "link:$PWD"
|
|
117
96
|
```
|
|
118
97
|
|
|
119
|
-
|
|
98
|
+
安装后桌面 App 需重载 profile 才会加载插件与客户端。
|
|
120
99
|
|
|
121
|
-
|
|
122
|
-
dsh plugin --profile desktop add "link:$PWD"
|
|
123
|
-
```
|
|
100
|
+
## License
|
|
124
101
|
|
|
125
|
-
|
|
102
|
+
MIT
|
package/lib/client.js
CHANGED
|
@@ -4,8 +4,31 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
// \0rolldown/runtime.js
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
16
|
+
key = keys[i];
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
18
|
+
get: ((k) => from[k]).bind(null, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
25
|
+
value: mod,
|
|
26
|
+
enumerable: true
|
|
27
|
+
}) : target, mod));
|
|
7
28
|
let react = require("react");
|
|
8
29
|
let react_dom = require("react-dom");
|
|
30
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
31
|
+
_deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
|
|
9
32
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
33
|
// src/vendor/navigation.ts
|
|
11
34
|
/**
|
|
@@ -1334,13 +1357,14 @@ window.__ModuleLoader__.load({
|
|
|
1334
1357
|
});
|
|
1335
1358
|
}
|
|
1336
1359
|
// src/client/plugins-page.tsx
|
|
1337
|
-
/**
|
|
1338
|
-
* Plugins tab for the official panel
|
|
1339
|
-
*
|
|
1340
|
-
*
|
|
1341
|
-
*
|
|
1342
|
-
*
|
|
1343
|
-
*
|
|
1360
|
+
/**
|
|
1361
|
+
* Plugins tab for the official panel. The install dialog is rebuilt on the
|
|
1362
|
+
* desktop client's own ui-primitives (required at runtime — kept external by
|
|
1363
|
+
* tsdown, resolved by the ModuleLoader registry exactly like the native
|
|
1364
|
+
* manager does), so the modal shares the native plugin dialog's typography,
|
|
1365
|
+
* colors, focus rings and animation. Page-level dialog rules are ported
|
|
1366
|
+
* verbatim from the native manager's CSS module under sgc-* class names
|
|
1367
|
+
* (widths, gaps, the safety footer, the anchored registry popover).
|
|
1344
1368
|
*
|
|
1345
1369
|
* Backend contract (src/plugins.ts): install/remove run
|
|
1346
1370
|
* dsh plugin --profile <name> add|remove server-side (single-flight,
|
|
@@ -1375,26 +1399,75 @@ window.__ModuleLoader__.load({
|
|
|
1375
1399
|
if (/\bENOTFOUND\b|\bECONNRESET\b|\bETIMEDOUT\b|\bECONNREFUSED\b|\bEAI_AGAIN\b|FETCH_ERROR|Could not resolve/i.test(log)) return "network";
|
|
1376
1400
|
return "generic";
|
|
1377
1401
|
}
|
|
1378
|
-
const
|
|
1402
|
+
const GUIDE_ITEMS = [
|
|
1379
1403
|
{
|
|
1380
1404
|
key: "Npm",
|
|
1381
|
-
example: "dsh-plugin-whale-pet"
|
|
1405
|
+
example: "dsh-plugin-whale-pet",
|
|
1406
|
+
titleKey: "plugins.installGuideNpm",
|
|
1407
|
+
hintKey: "plugins.installGuideNpmHint"
|
|
1382
1408
|
},
|
|
1383
1409
|
{
|
|
1384
1410
|
key: "Git",
|
|
1385
|
-
example: "github:owner/dsh-plugin-example"
|
|
1411
|
+
example: "github:owner/dsh-plugin-example",
|
|
1412
|
+
titleKey: "plugins.installGuideGit",
|
|
1413
|
+
hintKey: "plugins.installGuideGitHint"
|
|
1386
1414
|
},
|
|
1387
1415
|
{
|
|
1388
1416
|
key: "Path",
|
|
1389
|
-
example: "/Users/me/MyCode/dsh-plugin-example"
|
|
1417
|
+
example: "/Users/me/MyCode/dsh-plugin-example",
|
|
1418
|
+
titleKey: "plugins.installGuidePath",
|
|
1419
|
+
hintKey: "plugins.installGuidePathHint"
|
|
1390
1420
|
}
|
|
1391
1421
|
];
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1422
|
+
/**
|
|
1423
|
+
* Dialog rules ported from the native manager's PluginManagerPage CSS module
|
|
1424
|
+
* (values verbatim; class names renamed to the sgc- prefix so they cannot
|
|
1425
|
+
* collide with either the native or the engine-panel classes).
|
|
1426
|
+
*/
|
|
1427
|
+
const SGC_CSS = `
|
|
1428
|
+
.sgc-installDialog{width:min(560px,100%);max-height:min(800px,100%)}
|
|
1429
|
+
.sgc-installContent{min-height:0;overflow-y:auto}
|
|
1430
|
+
.sgc-installBody{flex-direction:column;gap:12px;min-width:0;display:flex}
|
|
1431
|
+
.sgc-installField{flex-direction:column;gap:6px;font-size:13px;display:flex}
|
|
1432
|
+
.sgc-installField input[type=text]{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-3);height:40px;font:inherit;color:var(--dsw-alias-label-primary);outline:none;padding:0 14px;font-size:13px}
|
|
1433
|
+
.sgc-installField input[type=text]:focus,.sgc-registryCustomField:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:inset 0 0 0 .5px var(--dsw-alias-state-business-primary)}
|
|
1434
|
+
.sgc-optionsRow{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px 12px;display:flex}
|
|
1435
|
+
.sgc-guideToggle{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;align-self:flex-start;align-items:center;gap:4px;margin-top:4px;padding:0;font-size:12.5px;display:inline-flex}
|
|
1436
|
+
.sgc-guideToggle:hover{color:var(--dsw-alias-label-primary)}
|
|
1437
|
+
.sgc-guideChevron{transition:transform .16s}
|
|
1438
|
+
.sgc-guideToggle[aria-expanded=true] .sgc-guideChevron,.sgc-registryToggle[aria-expanded=true] .sgc-guideChevron{transform:rotate(180deg)}
|
|
1439
|
+
.sgc-guide{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-layer-1);flex-direction:column;gap:10px;padding:8px 14px;display:flex}
|
|
1440
|
+
.sgc-guideList{flex-direction:column;gap:2px;margin:0;padding:0;list-style:none;display:flex}
|
|
1441
|
+
.sgc-guideItem{align-items:flex-start;gap:10px;padding:8px 0;display:flex}
|
|
1442
|
+
.sgc-guideItem>button{align-self:center}
|
|
1443
|
+
.sgc-guideMain{flex-direction:column;flex:1;gap:0;min-width:0;display:flex}
|
|
1444
|
+
.sgc-guideTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px}
|
|
1445
|
+
.sgc-guideHint{color:var(--dsw-alias-label-tertiary);margin:6px 0 0;font-size:12px;line-height:18px}
|
|
1446
|
+
.sgc-guideExample{color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere;font-size:12px;line-height:20px}
|
|
1447
|
+
.sgc-guideExample code{font-family:var(--ds-font-family-code)}
|
|
1448
|
+
.sgc-guideExampleLabel{color:var(--dsw-alias-label-tertiary)}
|
|
1449
|
+
.sgc-registryToggle{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-2);height:28px;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;align-items:center;gap:6px;padding:0 8px 0 10px;font-size:12.5px;display:inline-flex}
|
|
1450
|
+
.sgc-registryToggle:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
|
|
1451
|
+
.sgc-registryToggle:disabled{cursor:default;opacity:.6}
|
|
1452
|
+
.sgc-registryToggle[aria-expanded=true]{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-3)}
|
|
1453
|
+
.sgc-registryChosen{color:var(--dsw-alias-label-primary);font-weight:500}
|
|
1454
|
+
.sgc-registry{z-index:1100;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);border-radius:var(--dsw-radius-lg);width:min(440px,calc(100vw - 24px));box-shadow:var(--dsw-elevation-prominent);border:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:4px;min-width:0;margin:0;padding:8px;display:flex;position:fixed}
|
|
1455
|
+
.sgc-registryOption{border-radius:var(--dsw-radius-md);cursor:pointer;border:0;flex-direction:column;gap:4px;padding:8px 10px;display:flex}
|
|
1456
|
+
.sgc-registryOption:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
1457
|
+
label.sgc-registryOption{flex-direction:row;align-items:flex-start;gap:10px}
|
|
1458
|
+
.sgc-registryOption input[type=radio]{width:16px;height:16px;accent-color:var(--dsw-alias-brand-primary);flex:none;margin:2px 0 0}
|
|
1459
|
+
.sgc-registryTitle{color:var(--dsw-alias-label-primary);flex-wrap:wrap;align-items:center;gap:8px;font-size:13px;line-height:20px;display:flex}
|
|
1460
|
+
.sgc-registryHint{color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere;font-size:12px;line-height:18px}
|
|
1461
|
+
.sgc-registryCustomField{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-3);height:32px;font:inherit;color:var(--dsw-alias-label-primary);outline:none;margin-left:26px;padding:0 10px;font-size:13px}
|
|
1462
|
+
.sgc-registryCustomError{margin:0 0 0 26px}
|
|
1463
|
+
.sgc-installFooter{flex-direction:column;flex:1;gap:20px;min-width:0;display:flex}
|
|
1464
|
+
.sgc-installSafety{border-radius:var(--dsw-radius-md);background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-label-secondary);align-items:flex-start;gap:8px;margin:0;padding:10px 12px;font-size:12px;line-height:18px;display:flex}
|
|
1465
|
+
.sgc-installSafety>svg{color:var(--dsw-alias-state-warn-primary);flex:none;margin-top:2px}
|
|
1466
|
+
.sgc-installSafetyText{flex-direction:column;gap:4px;min-width:0;display:flex}
|
|
1467
|
+
.sgc-wide{border-radius:var(--dsw-radius-md);justify-content:center;width:100%;height:40px}
|
|
1468
|
+
.sgc-inputError{color:var(--dsw-alias-state-error-primary);margin:-4px 0 0;font-size:12px;line-height:18px}
|
|
1469
|
+
.sgc-dialogActions{display:flex;justify-content:flex-end;gap:10px}
|
|
1470
|
+
`;
|
|
1398
1471
|
function OutputBlock({ output }) {
|
|
1399
1472
|
if (!output) return null;
|
|
1400
1473
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
@@ -1406,15 +1479,37 @@ window.__ModuleLoader__.load({
|
|
|
1406
1479
|
children: output
|
|
1407
1480
|
});
|
|
1408
1481
|
}
|
|
1482
|
+
const REGISTRY_CHOICES = [
|
|
1483
|
+
{
|
|
1484
|
+
value: "",
|
|
1485
|
+
titleKey: "plugins.registryDefault"
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
value: "official",
|
|
1489
|
+
titleKey: "plugins.registryOfficial"
|
|
1490
|
+
},
|
|
1491
|
+
{
|
|
1492
|
+
value: "npmmirror",
|
|
1493
|
+
titleKey: "plugins.registryNpmmirror"
|
|
1494
|
+
},
|
|
1495
|
+
{
|
|
1496
|
+
value: "custom",
|
|
1497
|
+
titleKey: "plugins.registryCustom"
|
|
1498
|
+
}
|
|
1499
|
+
];
|
|
1409
1500
|
function InstallModal({ open, onClose, onSettled }) {
|
|
1410
1501
|
const { t, notify } = usePanel();
|
|
1411
1502
|
const [spec, setSpec] = (0, react.useState)("");
|
|
1412
1503
|
const [step, setStep] = (0, react.useState)({ kind: "editing" });
|
|
1413
1504
|
const [guideOpen, setGuideOpen] = (0, react.useState)(false);
|
|
1414
|
-
const [
|
|
1415
|
-
const [lastOp, setLastOp] = (0, react.useState)(null);
|
|
1505
|
+
const [registryOpen, setRegistryOpen] = (0, react.useState)(false);
|
|
1416
1506
|
const [registry, setRegistry] = (0, react.useState)("");
|
|
1417
1507
|
const [customRegistry, setCustomRegistry] = (0, react.useState)("");
|
|
1508
|
+
const [customInvalid, setCustomInvalid] = (0, react.useState)(false);
|
|
1509
|
+
const [detailsOpen, setDetailsOpen] = (0, react.useState)(false);
|
|
1510
|
+
const [lastOp, setLastOp] = (0, react.useState)(null);
|
|
1511
|
+
const registryToggleRef = (0, react.useRef)(null);
|
|
1512
|
+
const [registryPos, setRegistryPos] = (0, react.useState)(null);
|
|
1418
1513
|
const settled = (0, react.useRef)(onSettled);
|
|
1419
1514
|
settled.current = onSettled;
|
|
1420
1515
|
(0, react.useEffect)(() => {
|
|
@@ -1432,26 +1527,79 @@ window.__ModuleLoader__.load({
|
|
|
1432
1527
|
};
|
|
1433
1528
|
const timer = setInterval(() => {
|
|
1434
1529
|
tick();
|
|
1435
|
-
},
|
|
1530
|
+
}, 800);
|
|
1436
1531
|
tick();
|
|
1437
1532
|
return () => {
|
|
1438
1533
|
active = false;
|
|
1439
1534
|
clearInterval(timer);
|
|
1440
1535
|
};
|
|
1441
1536
|
}, [step.kind]);
|
|
1537
|
+
(0, react.useEffect)(() => {
|
|
1538
|
+
if (!registryOpen) return;
|
|
1539
|
+
const place = () => {
|
|
1540
|
+
const el = registryToggleRef.current;
|
|
1541
|
+
if (!el) return;
|
|
1542
|
+
const rect = el.getBoundingClientRect();
|
|
1543
|
+
const width = Math.min(440, window.innerWidth - 24);
|
|
1544
|
+
const left = Math.max(12, Math.min(rect.left, window.innerWidth - width - 12));
|
|
1545
|
+
const top = Math.min(rect.bottom + 6, window.innerHeight - 200);
|
|
1546
|
+
setRegistryPos({
|
|
1547
|
+
left,
|
|
1548
|
+
top
|
|
1549
|
+
});
|
|
1550
|
+
};
|
|
1551
|
+
place();
|
|
1552
|
+
window.addEventListener("resize", place);
|
|
1553
|
+
window.addEventListener("scroll", place, true);
|
|
1554
|
+
return () => {
|
|
1555
|
+
window.removeEventListener("resize", place);
|
|
1556
|
+
window.removeEventListener("scroll", place, true);
|
|
1557
|
+
};
|
|
1558
|
+
}, [registryOpen]);
|
|
1559
|
+
(0, react.useEffect)(() => {
|
|
1560
|
+
if (!registryOpen) return;
|
|
1561
|
+
const onDoc = (event) => {
|
|
1562
|
+
const target = event.target;
|
|
1563
|
+
if (registryToggleRef.current?.contains(target)) return;
|
|
1564
|
+
if (target instanceof Element && target.closest("[data-sgc-registry]")) return;
|
|
1565
|
+
setRegistryOpen(false);
|
|
1566
|
+
};
|
|
1567
|
+
const onKey = (event) => {
|
|
1568
|
+
if (event.key === "Escape") setRegistryOpen(false);
|
|
1569
|
+
};
|
|
1570
|
+
document.addEventListener("mousedown", onDoc);
|
|
1571
|
+
document.addEventListener("keydown", onKey);
|
|
1572
|
+
return () => {
|
|
1573
|
+
document.removeEventListener("mousedown", onDoc);
|
|
1574
|
+
document.removeEventListener("keydown", onKey);
|
|
1575
|
+
};
|
|
1576
|
+
}, [registryOpen]);
|
|
1442
1577
|
const run = async () => {
|
|
1443
1578
|
let chosen = null;
|
|
1444
1579
|
if (registry === "official") chosen = "https://registry.npmjs.org/";
|
|
1445
1580
|
else if (registry === "npmmirror") chosen = "https://registry.npmmirror.com/";
|
|
1446
1581
|
else if (registry === "custom") {
|
|
1447
|
-
|
|
1448
|
-
if (!/^https?:\/\/\S+$/.test(
|
|
1449
|
-
|
|
1582
|
+
const url = customRegistry.trim();
|
|
1583
|
+
if (!/^https?:\/\/\S+$/.test(url)) {
|
|
1584
|
+
setCustomInvalid(true);
|
|
1450
1585
|
return;
|
|
1451
1586
|
}
|
|
1587
|
+
chosen = url;
|
|
1452
1588
|
}
|
|
1589
|
+
setRegistryOpen(false);
|
|
1453
1590
|
setDetailsOpen(false);
|
|
1454
1591
|
setLastOp(null);
|
|
1592
|
+
setStep({ kind: "checking" });
|
|
1593
|
+
try {
|
|
1594
|
+
const { problems } = await post("/superagent-console/api/plugins/check", { spec: spec.trim() });
|
|
1595
|
+
if (problems.length > 0) {
|
|
1596
|
+
setStep({
|
|
1597
|
+
kind: "refused",
|
|
1598
|
+
problems
|
|
1599
|
+
});
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
} catch {}
|
|
1455
1603
|
setStep({ kind: "running" });
|
|
1456
1604
|
try {
|
|
1457
1605
|
await post("/superagent-console/api/plugins/install", {
|
|
@@ -1463,224 +1611,283 @@ window.__ModuleLoader__.load({
|
|
|
1463
1611
|
setStep({ kind: "editing" });
|
|
1464
1612
|
}
|
|
1465
1613
|
};
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1614
|
+
/** 立即启用: mount every bundle the add introduced, then surface the restart notice. */
|
|
1615
|
+
const enableNow = async () => {
|
|
1616
|
+
const bundles = lastOp?.installed?.filter((item) => item.bundle) ?? [];
|
|
1617
|
+
for (const item of bundles) try {
|
|
1618
|
+
await post("/superagent-console/api/plugins/enable", {
|
|
1619
|
+
name: item.name,
|
|
1620
|
+
enabled: true
|
|
1621
|
+
});
|
|
1622
|
+
} catch (failure) {
|
|
1623
|
+
notify(errorText(failure));
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
settled.current(true);
|
|
1627
|
+
onClose();
|
|
1469
1628
|
};
|
|
1470
|
-
|
|
1629
|
+
const editing = step.kind === "editing";
|
|
1630
|
+
const enableTargets = step.kind === "done" && (lastOp?.installed?.some((item) => item.bundle) ?? false);
|
|
1631
|
+
const chosenTitle = t((REGISTRY_CHOICES.find((choice) => choice.value === registry) ?? REGISTRY_CHOICES[0]).titleKey);
|
|
1632
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: SGC_CSS }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
1471
1633
|
open,
|
|
1472
1634
|
onClose,
|
|
1473
|
-
className: "
|
|
1635
|
+
className: "sgc-installDialog",
|
|
1636
|
+
contentClassName: "sgc-installContent",
|
|
1474
1637
|
title: step.kind === "running" ? t("plugins.installingTitle") : step.kind === "failed" ? t("plugins.installFailedTitle") : t("plugins.installTitle"),
|
|
1475
1638
|
closeLabel: t("plugins.installClose"),
|
|
1476
|
-
description:
|
|
1477
|
-
footer:
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1639
|
+
description: editing ? t("plugins.installDescription") : void 0,
|
|
1640
|
+
footer: editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1641
|
+
className: "sgc-installFooter",
|
|
1642
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1643
|
+
className: "sgc-installSafety",
|
|
1644
|
+
role: "note",
|
|
1645
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {
|
|
1646
|
+
size: 14,
|
|
1647
|
+
"aria-hidden": "true"
|
|
1648
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1649
|
+
className: "sgc-installSafetyText",
|
|
1650
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("plugins.installGuideSafety") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("plugins.installUpgradeNotice") })]
|
|
1651
|
+
})]
|
|
1652
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1489
1653
|
variant: "primary",
|
|
1654
|
+
className: "sgc-wide",
|
|
1655
|
+
disabled: !spec.trim(),
|
|
1490
1656
|
onClick: () => {
|
|
1491
1657
|
run();
|
|
1492
1658
|
},
|
|
1493
|
-
children: t("plugins.
|
|
1494
|
-
})
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1659
|
+
children: t("plugins.installRun")
|
|
1660
|
+
})]
|
|
1661
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1662
|
+
className: "sgc-dialogActions",
|
|
1663
|
+
children: [
|
|
1664
|
+
(step.kind === "running" || step.kind === "failed") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1665
|
+
variant: "ghost",
|
|
1666
|
+
onClick: onClose,
|
|
1667
|
+
children: t("plugins.installClose")
|
|
1668
|
+
}),
|
|
1669
|
+
step.kind === "refused" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1670
|
+
variant: "primary",
|
|
1671
|
+
onClick: () => setStep({ kind: "editing" }),
|
|
1672
|
+
children: t("plugins.installEdit")
|
|
1673
|
+
}),
|
|
1674
|
+
step.kind === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1675
|
+
variant: "ghost",
|
|
1676
|
+
onClick: () => setStep({ kind: "editing" }),
|
|
1677
|
+
children: t("plugins.installChangeRegistry")
|
|
1678
|
+
}),
|
|
1679
|
+
step.kind === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1680
|
+
variant: "primary",
|
|
1681
|
+
onClick: () => {
|
|
1682
|
+
run();
|
|
1683
|
+
},
|
|
1684
|
+
children: t("plugins.installRetry")
|
|
1685
|
+
}),
|
|
1686
|
+
step.kind === "done" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1687
|
+
variant: "ghost",
|
|
1688
|
+
onClick: onClose,
|
|
1689
|
+
children: t("plugins.installClose")
|
|
1690
|
+
}),
|
|
1691
|
+
step.kind === "done" && enableTargets && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1692
|
+
variant: "primary",
|
|
1693
|
+
onClick: () => {
|
|
1694
|
+
enableNow();
|
|
1695
|
+
},
|
|
1696
|
+
children: t("plugins.installEnableNow")
|
|
1697
|
+
})
|
|
1698
|
+
]
|
|
1699
|
+
}),
|
|
1501
1700
|
children: [
|
|
1502
|
-
|
|
1503
|
-
|
|
1701
|
+
editing && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1702
|
+
className: "sgc-installBody",
|
|
1504
1703
|
children: [
|
|
1505
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1704
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1705
|
+
className: "sgc-installField",
|
|
1706
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1707
|
+
type: "text",
|
|
1708
|
+
autoFocus: true,
|
|
1709
|
+
"data-modal-autofocus": true,
|
|
1710
|
+
value: spec,
|
|
1711
|
+
placeholder: t("plugins.installSpecPlaceholder"),
|
|
1712
|
+
"aria-label": t("plugins.installSpecLabel"),
|
|
1713
|
+
onChange: (event) => {
|
|
1714
|
+
setSpec(event.target.value);
|
|
1715
|
+
},
|
|
1716
|
+
onKeyDown: (event) => {
|
|
1717
|
+
if (event.nativeEvent.isComposing) return;
|
|
1718
|
+
if (event.key === "Enter" && spec.trim()) {
|
|
1719
|
+
event.preventDefault();
|
|
1720
|
+
run();
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
})
|
|
1514
1724
|
}),
|
|
1515
1725
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1516
|
-
|
|
1517
|
-
display: "flex",
|
|
1518
|
-
alignItems: "center",
|
|
1519
|
-
justifyContent: "space-between",
|
|
1520
|
-
gap: 12,
|
|
1521
|
-
marginTop: 12
|
|
1522
|
-
},
|
|
1726
|
+
className: "sgc-optionsRow",
|
|
1523
1727
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1524
1728
|
type: "button",
|
|
1525
|
-
className: "
|
|
1729
|
+
className: "sgc-guideToggle",
|
|
1526
1730
|
"aria-expanded": guideOpen,
|
|
1527
|
-
style: {
|
|
1528
|
-
display: "inline-flex",
|
|
1529
|
-
alignItems: "center",
|
|
1530
|
-
gap: 4
|
|
1531
|
-
},
|
|
1532
1731
|
onClick: () => setGuideOpen(!guideOpen),
|
|
1533
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1534
|
-
|
|
1535
|
-
size:
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
background: "transparent",
|
|
1559
|
-
width: "auto",
|
|
1560
|
-
height: "auto",
|
|
1561
|
-
padding: 0
|
|
1562
|
-
},
|
|
1563
|
-
"aria-label": t("plugins.registryTitle"),
|
|
1564
|
-
value: registry,
|
|
1565
|
-
onChange: (event) => setRegistry(event.target.value),
|
|
1566
|
-
children: [
|
|
1567
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1568
|
-
value: "",
|
|
1569
|
-
children: t("plugins.registryDefault")
|
|
1570
|
-
}),
|
|
1571
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1572
|
-
value: "official",
|
|
1573
|
-
children: t("plugins.registryOfficial")
|
|
1574
|
-
}),
|
|
1575
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1576
|
-
value: "npmmirror",
|
|
1577
|
-
children: t("plugins.registryNpmmirror")
|
|
1578
|
-
}),
|
|
1579
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1580
|
-
value: "custom",
|
|
1581
|
-
children: t("plugins.registryCustom")
|
|
1582
|
-
})
|
|
1583
|
-
]
|
|
1584
|
-
})]
|
|
1732
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
|
|
1733
|
+
className: "sgc-guideChevron",
|
|
1734
|
+
size: 13,
|
|
1735
|
+
"aria-hidden": "true"
|
|
1736
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: guideOpen ? t("plugins.installGuideHide") : t("plugins.installGuideToggle") })]
|
|
1737
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1738
|
+
ref: registryToggleRef,
|
|
1739
|
+
type: "button",
|
|
1740
|
+
className: "sgc-registryToggle",
|
|
1741
|
+
"aria-expanded": registryOpen,
|
|
1742
|
+
"aria-haspopup": "dialog",
|
|
1743
|
+
onClick: () => setRegistryOpen(!registryOpen),
|
|
1744
|
+
children: [
|
|
1745
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("plugins.registryTitle") }),
|
|
1746
|
+
" ",
|
|
1747
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1748
|
+
className: "sgc-registryChosen",
|
|
1749
|
+
children: chosenTitle
|
|
1750
|
+
}),
|
|
1751
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
|
|
1752
|
+
className: "sgc-guideChevron",
|
|
1753
|
+
size: 12,
|
|
1754
|
+
"aria-hidden": "true"
|
|
1755
|
+
})
|
|
1756
|
+
]
|
|
1585
1757
|
})]
|
|
1586
1758
|
}),
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
children:
|
|
1759
|
+
guideOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1760
|
+
className: "sgc-guide",
|
|
1761
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
1762
|
+
className: "sgc-guideList",
|
|
1763
|
+
children: GUIDE_ITEMS.map(({ key, example, titleKey, hintKey }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1764
|
+
className: "sgc-guideItem",
|
|
1765
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1766
|
+
className: "sgc-guideMain",
|
|
1767
|
+
children: [
|
|
1768
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1769
|
+
className: "sgc-guideTitle",
|
|
1770
|
+
children: t(titleKey)
|
|
1771
|
+
}),
|
|
1772
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1773
|
+
className: "sgc-guideHint",
|
|
1774
|
+
children: t(hintKey)
|
|
1775
|
+
}),
|
|
1776
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1777
|
+
className: "sgc-guideExample",
|
|
1778
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1779
|
+
className: "sgc-guideExampleLabel",
|
|
1780
|
+
children: t("plugins.installGuideExampleLabel")
|
|
1781
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: example })]
|
|
1782
|
+
})
|
|
1783
|
+
]
|
|
1784
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1785
|
+
variant: "outline",
|
|
1786
|
+
size: "sm",
|
|
1787
|
+
"aria-label": t("plugins.fillExample", { example }),
|
|
1788
|
+
onClick: () => setSpec(example),
|
|
1789
|
+
children: t("plugins.installGuideFill")
|
|
1790
|
+
})]
|
|
1791
|
+
}, key))
|
|
1792
|
+
})
|
|
1793
|
+
}),
|
|
1794
|
+
registryOpen && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
1795
|
+
className: "sgc-registry",
|
|
1796
|
+
"data-sgc-registry": true,
|
|
1797
|
+
"aria-label": t("plugins.registryLegend"),
|
|
1798
|
+
style: registryPos ?? {
|
|
1799
|
+
visibility: "hidden",
|
|
1800
|
+
left: 0,
|
|
1801
|
+
top: 0
|
|
1802
|
+
},
|
|
1803
|
+
children: [REGISTRY_CHOICES.map(({ value, titleKey }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1804
|
+
className: "sgc-registryOption",
|
|
1805
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1806
|
+
type: "radio",
|
|
1807
|
+
name: "sgc-registry-choice",
|
|
1808
|
+
checked: registry === value,
|
|
1809
|
+
onChange: () => {
|
|
1810
|
+
setRegistry(value);
|
|
1811
|
+
if (value !== "custom") setCustomInvalid(false);
|
|
1812
|
+
}
|
|
1813
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1814
|
+
className: "sgc-registryTitle",
|
|
1815
|
+
children: t(titleKey)
|
|
1816
|
+
})]
|
|
1817
|
+
}, value || "default")), registry === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1590
1818
|
type: "text",
|
|
1819
|
+
className: "sgc-registryCustomField",
|
|
1591
1820
|
value: customRegistry,
|
|
1592
1821
|
placeholder: t("plugins.registryCustomPlaceholder"),
|
|
1822
|
+
"aria-invalid": customInvalid || void 0,
|
|
1593
1823
|
"aria-label": t("plugins.registryCustom"),
|
|
1594
|
-
style: { width: "100%" },
|
|
1595
1824
|
onChange: (event) => {
|
|
1596
1825
|
setCustomRegistry(event.target.value);
|
|
1826
|
+
setCustomInvalid(false);
|
|
1597
1827
|
}
|
|
1598
1828
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1599
|
-
className: "
|
|
1600
|
-
children: t("plugins.registryCustomHint")
|
|
1601
|
-
})]
|
|
1602
|
-
}),
|
|
1603
|
-
guideOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1604
|
-
className: "dt-disclosure-body",
|
|
1605
|
-
style: { marginTop: 12 },
|
|
1606
|
-
children: GUIDE_EXAMPLES.map(({ key, example }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1607
|
-
className: "dt-row",
|
|
1608
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, { state: "idle" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1609
|
-
className: "dt-row-main",
|
|
1610
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1611
|
-
className: "dt-row-title dt-line-text",
|
|
1612
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GuideText, { kind: key })
|
|
1613
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1614
|
-
className: "dt-row-meta",
|
|
1615
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1616
|
-
className: "dt-code",
|
|
1617
|
-
children: example
|
|
1618
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1619
|
-
type: "button",
|
|
1620
|
-
className: "dt-textlink",
|
|
1621
|
-
"aria-label": t("plugins.fillExample", { example }),
|
|
1622
|
-
onClick: () => setSpec(example),
|
|
1623
|
-
children: t("plugins.fill")
|
|
1624
|
-
})]
|
|
1625
|
-
})]
|
|
1626
|
-
})]
|
|
1627
|
-
}, key))
|
|
1628
|
-
}),
|
|
1629
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1630
|
-
className: "dt-banner",
|
|
1631
|
-
style: { marginTop: 12 },
|
|
1632
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1633
|
-
style: {
|
|
1634
|
-
display: "inline-flex",
|
|
1635
|
-
alignItems: "flex-start",
|
|
1636
|
-
gap: 6
|
|
1637
|
-
},
|
|
1638
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1639
|
-
name: "alert",
|
|
1640
|
-
size: 14
|
|
1641
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("plugins.installGuideSafety") })]
|
|
1642
|
-
})
|
|
1643
|
-
}),
|
|
1644
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button$1, {
|
|
1645
|
-
variant: "primary",
|
|
1646
|
-
style: {
|
|
1647
|
-
width: "100%",
|
|
1648
|
-
marginTop: 14,
|
|
1649
|
-
display: "flex",
|
|
1650
|
-
alignItems: "center",
|
|
1651
|
-
justifyContent: "center"
|
|
1652
|
-
},
|
|
1653
|
-
disabled: !spec.trim(),
|
|
1654
|
-
onClick: () => {
|
|
1655
|
-
run();
|
|
1656
|
-
},
|
|
1657
|
-
children: t("plugins.installRun")
|
|
1658
|
-
})
|
|
1829
|
+
className: customInvalid ? "sgc-inputError sgc-registryCustomError" : "sgc-registryHint sgc-registryCustomError",
|
|
1830
|
+
children: customInvalid ? t("plugins.registryCustomInvalid") : t("plugins.registryCustomHint")
|
|
1831
|
+
})] })]
|
|
1832
|
+
}), document.body)
|
|
1659
1833
|
]
|
|
1660
1834
|
}),
|
|
1835
|
+
step.kind === "checking" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1836
|
+
className: "dt-row",
|
|
1837
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, { state: "ongoing" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1838
|
+
className: "dt-row-main",
|
|
1839
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1840
|
+
className: "dt-row-title",
|
|
1841
|
+
children: t("plugins.installChecking")
|
|
1842
|
+
})
|
|
1843
|
+
})]
|
|
1844
|
+
}),
|
|
1845
|
+
step.kind === "refused" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1846
|
+
className: "dt-row",
|
|
1847
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, { state: "error" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1848
|
+
className: "dt-row-main",
|
|
1849
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1850
|
+
className: "dt-row-title",
|
|
1851
|
+
children: t("plugins.installConfirmedRefused")
|
|
1852
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1853
|
+
className: "dt-row-meta",
|
|
1854
|
+
children: step.problems.map((problem, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1855
|
+
problem.kind === "invalid" && t("plugins.installProblemInvalid", { reason: problem.detail ?? "" }),
|
|
1856
|
+
problem.kind === "installed" && t("plugins.installProblemInstalled"),
|
|
1857
|
+
problem.kind === "links" && t("plugins.problemLinks")
|
|
1858
|
+
] }, index))
|
|
1859
|
+
})]
|
|
1860
|
+
})]
|
|
1861
|
+
}) }),
|
|
1661
1862
|
step.kind === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1662
1863
|
className: "dt-row",
|
|
1663
1864
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, { state: "ongoing" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1664
1865
|
className: "dt-row-main",
|
|
1665
1866
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1666
1867
|
className: "dt-row-title",
|
|
1667
|
-
children: t("plugins.installingTitle")
|
|
1868
|
+
children: (lastOp?.output ?? "") === "" ? t("plugins.installStarting") : t("plugins.installingTitle")
|
|
1668
1869
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1669
1870
|
className: "dt-row-meta",
|
|
1670
1871
|
children: t("plugins.installBackground")
|
|
1671
1872
|
})]
|
|
1672
1873
|
})]
|
|
1673
1874
|
}), lastOp?.output && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutputBlock, { output: lastOp.output })] }),
|
|
1674
|
-
step.kind === "done" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1875
|
+
step.kind === "done" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1675
1876
|
className: "dt-row",
|
|
1676
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, { state: "done" }), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1877
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, { state: "done" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1677
1878
|
className: "dt-row-main",
|
|
1678
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1879
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1679
1880
|
className: "dt-row-title",
|
|
1680
|
-
children: t("plugins.
|
|
1681
|
-
})
|
|
1881
|
+
children: t("plugins.installedTitle")
|
|
1882
|
+
}), (lastOp?.installed?.length ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1883
|
+
className: "dt-row-meta",
|
|
1884
|
+
children: lastOp.installed.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [item.name, item.version ? " · " + t("plugins.installVersion", { version: item.version }) : ""] }, item.name))
|
|
1885
|
+
})]
|
|
1682
1886
|
})]
|
|
1683
|
-
}),
|
|
1887
|
+
}), lastOp?.profileDir && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1888
|
+
className: "dt-row-meta",
|
|
1889
|
+
children: t("plugins.installLocation", { dir: lastOp.profileDir })
|
|
1890
|
+
})] }),
|
|
1684
1891
|
step.kind === "failed" && lastOp && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1685
1892
|
className: "dt-row",
|
|
1686
1893
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, { state: "error" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1705,7 +1912,7 @@ window.__ModuleLoader__.load({
|
|
|
1705
1912
|
})]
|
|
1706
1913
|
}) })
|
|
1707
1914
|
]
|
|
1708
|
-
});
|
|
1915
|
+
})] });
|
|
1709
1916
|
}
|
|
1710
1917
|
function PluginItem({ row, busy, onUninstall, onToggle }) {
|
|
1711
1918
|
const { t } = usePanel();
|
|
@@ -1810,7 +2017,6 @@ window.__ModuleLoader__.load({
|
|
|
1810
2017
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1811
2018
|
className: "dt-form",
|
|
1812
2019
|
children: [
|
|
1813
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: ".dt-plugins-modal.dt-modal { width: min(680px, 100%); }" }),
|
|
1814
2020
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1815
2021
|
className: "dt-status-head",
|
|
1816
2022
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
|
|
@@ -4895,7 +5101,7 @@ window.__ModuleLoader__.load({
|
|
|
4895
5101
|
"plugins.uninstallTitle": ["卸载「{name}」?", "Uninstall \"{name}\"?"],
|
|
4896
5102
|
"plugins.uninstallDescription": ["卸载后它提供的功能会消失,重启引擎后生效。", "Its features disappear; the change applies when the engine restarts."],
|
|
4897
5103
|
"plugins.installTitle": ["添加插件", "Add plugin"],
|
|
4898
|
-
"plugins.installDescription": ["输入插件的包名、
|
|
5104
|
+
"plugins.installDescription": ["输入插件的包名、GitHub 仓库地址或本地目录路径。", "Enter the plugin's package name, GitHub repository address, or local directory path."],
|
|
4899
5105
|
"plugins.installSpecLabel": ["包名或地址", "Package or address"],
|
|
4900
5106
|
"plugins.installSpecPlaceholder": ["例如 dsh-plugin-whale-pet", "e.g. dsh-plugin-whale-pet"],
|
|
4901
5107
|
"plugins.registryTitle": ["安装源", "Registry"],
|
|
@@ -4904,16 +5110,22 @@ window.__ModuleLoader__.load({
|
|
|
4904
5110
|
"plugins.registryNpmmirror": ["中国大陆镜像源", "China mirror registry"],
|
|
4905
5111
|
"plugins.registryCustom": ["自定义地址", "Custom URL"],
|
|
4906
5112
|
"plugins.registryCustomPlaceholder": ["https://npm.example.com/", "https://npm.example.com/"],
|
|
4907
|
-
"plugins.registryCustomHint": ["
|
|
5113
|
+
"plugins.registryCustomHint": ["填写内网或私有 npm 源地址,以 http:// 或 https:// 开头。若为需要登录的源,请把凭据放在本机的 ~/.npmrc 里。", "Enter an internal or private npm registry address starting with http:// or https://. If it requires a login, store the credentials in ~/.npmrc on this machine."],
|
|
4908
5114
|
"plugins.registryCustomInvalid": ["请输入以 http:// 或 https:// 开头的地址", "Enter an address starting with http:// or https://"],
|
|
4909
5115
|
"plugins.installGuideToggle": ["插件安装引导和示例", "Install guide and examples"],
|
|
4910
5116
|
"plugins.installGuideHide": ["收起引导", "Hide guide"],
|
|
4911
|
-
"plugins.installGuideNpm": ["npm
|
|
4912
|
-
"plugins.
|
|
4913
|
-
"plugins.
|
|
4914
|
-
"plugins.
|
|
5117
|
+
"plugins.installGuideNpm": ["npm 包名", "npm package name"],
|
|
5118
|
+
"plugins.installGuideNpmHint": ["插件包名即 npm 包名(如 dsh-xxx 或 @作者/插件名)。", "The plugin name is its npm name (dsh-xxx or @author/plugin)."],
|
|
5119
|
+
"plugins.installGuideGit": ["Git 仓库", "Git repository"],
|
|
5120
|
+
"plugins.installGuideGitHint": ["github:owner/repo、git URL 或 .git 地址。", "github:owner/repo, a git URL, or a .git address."],
|
|
5121
|
+
"plugins.installGuidePath": ["本地目录", "Local directory"],
|
|
5122
|
+
"plugins.installGuidePathHint": ["本机插件 checkout 的绝对路径(以 link 方式安装)。", "An absolute path to a local checkout (installed as a link)."],
|
|
5123
|
+
"plugins.installGuideExampleLabel": ["示例:", "Example:"],
|
|
5124
|
+
"plugins.installGuideFill": ["填入示例", "Use example"],
|
|
4915
5125
|
"plugins.fillExample": ["填入示例 {example}", "Fill in the example {example}"],
|
|
4916
|
-
"plugins.installGuideSafety": ["
|
|
5126
|
+
"plugins.installGuideSafety": ["请确认插件来源可信。插件在本机以你的权限运行,来源不明的插件可能损坏 DeepSeek Harness,或读取和泄露你的数据。", "Install only plugins you trust: they run with your permissions and can damage DeepSeek Harness or leak your data."],
|
|
5127
|
+
"plugins.installUpgradeNotice": ["按需安装的官方插件会在版本与 DSH 不同时提供“更新”。其他插件请先卸载再安装新版。", "On-demand Official plugins offer Update when their version differs from DSH. To update other plugins, uninstall and reinstall them."],
|
|
5128
|
+
"plugins.registryLegend": ["从哪个 npm 源下载插件", "The npm registry the plugin is downloaded from"],
|
|
4917
5129
|
"plugins.installRun": ["安装", "Install"],
|
|
4918
5130
|
"plugins.installingTitle": ["插件安装中…", "Installing plugin…"],
|
|
4919
5131
|
"plugins.installBackground": ["可以关闭此窗口,安装会在后台继续;进度显示在列表上方的横幅里。", "You can close this dialog; the install continues in the background and its progress shows in the banner above the list."],
|
|
@@ -4923,6 +5135,18 @@ window.__ModuleLoader__.load({
|
|
|
4923
5135
|
"plugins.installDetailsHide": ["收起安装详情", "Hide install details"],
|
|
4924
5136
|
"plugins.installRetry": ["重试", "Retry"],
|
|
4925
5137
|
"plugins.installClose": ["完成", "Done"],
|
|
5138
|
+
"plugins.installChecking": ["正在检查…", "Checking…"],
|
|
5139
|
+
"plugins.installStarting": ["正在准备安装…", "Preparing installation…"],
|
|
5140
|
+
"plugins.installedTitle": ["已安装", "Installed"],
|
|
5141
|
+
"plugins.installEnableNow": ["立即启用", "Enable now"],
|
|
5142
|
+
"plugins.installVersion": ["版本 {version}", "Version {version}"],
|
|
5143
|
+
"plugins.installLocation": ["安装位置:{dir}", "Installs into {dir}"],
|
|
5144
|
+
"plugins.installChangeRegistry": ["更换安装源", "Change registry"],
|
|
5145
|
+
"plugins.installConfirmedRefused": ["无法安装此插件", "This plugin cannot be installed"],
|
|
5146
|
+
"plugins.installProblemInstalled": ["该插件已安装。如需升级,请卸载后重新安装", "This plugin is already installed. To upgrade it, uninstall it and install it again"],
|
|
5147
|
+
"plugins.installProblemInvalid": ["无法识别这个包名或地址:{reason}", "This is not a package name or address that can be installed: {reason}"],
|
|
5148
|
+
"plugins.installEdit": ["编辑", "Edit"],
|
|
5149
|
+
"plugins.problemLinks": ["引擎 profile 的 link: 依赖目标不存在,请先修复后再安装。", "A link dependency of the engine profile points to a missing target; repair it before installing."],
|
|
4926
5150
|
"plugins.failure.network": ["网络连接失败,请检查网络或代理后重试", "Network failed; check connectivity or proxy, then retry"],
|
|
4927
5151
|
"plugins.failure.notFound": ["未找到相关插件", "Plugin not found"],
|
|
4928
5152
|
"plugins.failure.version": ["没有匹配的版本", "No matching version"],
|
package/lib/index.d.ts
CHANGED
|
@@ -88,6 +88,16 @@ interface EngineActionResult {
|
|
|
88
88
|
}
|
|
89
89
|
/** Run the lifecycle script's start/restart action and wait for its verdict. */
|
|
90
90
|
declare function engineAction(config: ResolvedConsoleConfig, action: 'start' | 'restart', env?: InstallEnv): Promise<EngineActionResult>;
|
|
91
|
+
/**
|
|
92
|
+
* Collect `link:` dependencies whose target no longer exists. The official
|
|
93
|
+
* CLI sails through the package operation itself (pnpm trusts the lockfile's
|
|
94
|
+
* recorded link resolution without checking the target) but its post-install
|
|
95
|
+
* bundle reconciliation probes `node_modules/<name>/package.json` and exits 1
|
|
96
|
+
* with an opaque "cannot resolve profile bundle" error — the shape seen after
|
|
97
|
+
* a hosting checkout is moved and only the manifest specifiers are rewritten.
|
|
98
|
+
* Refusing up front keeps the failure readable and names the repair.
|
|
99
|
+
*/
|
|
100
|
+
declare function danglingLinks(config: ResolvedConsoleConfig): string[];
|
|
91
101
|
/** Minimal structural view of the settings service the sync reads through. */
|
|
92
102
|
interface SettingsFace {
|
|
93
103
|
describe(): Array<{
|
|
@@ -165,4 +175,4 @@ declare function proxyHandler(options: ProxyOptions): (req: IncomingMessage, res
|
|
|
165
175
|
declare const name: string;
|
|
166
176
|
declare const inject: string[];
|
|
167
177
|
declare function apply(ctx: Context, config?: Partial<ResolvedConsoleConfig>): void;
|
|
168
|
-
export { Config, type ConsoleState, type DetectResult, type Prereq, type ProviderDiff, type ResolvedConsoleConfig, type SettingsFace, type SetupState, type StageId, type SyncMode, type SyncReport, apply, applyProviderSync, detect, diffProviders, engineAction, inject, name, proxyHandler, readEngineProviders, readHostProviders, readSetupState, runInstall };
|
|
178
|
+
export { Config, type ConsoleState, type DetectResult, type Prereq, type ProviderDiff, type ResolvedConsoleConfig, type SettingsFace, type SetupState, type StageId, type SyncMode, type SyncReport, apply, applyProviderSync, danglingLinks, detect, diffProviders, engineAction, inject, name, proxyHandler, readEngineProviders, readHostProviders, readSetupState, runInstall };
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import os from "node:os";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
import fs, { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { execFile } from "node:child_process";
|
|
5
|
+
import { execFile, spawn } from "node:child_process";
|
|
6
6
|
import { parse, stringify } from "yaml";
|
|
7
7
|
import { Readable } from "node:stream";
|
|
8
8
|
// src/config.ts
|
|
@@ -92,6 +92,73 @@ function run(argv, options = {}) {
|
|
|
92
92
|
});
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Spawn like {@link run} but deliver output as it arrives: the plugins API
|
|
97
|
+
* mirrors each chunk into the operation state so the dialog can stream the
|
|
98
|
+
* install log the way the native manager does. Output stays bounded by the
|
|
99
|
+
* consumer (it keeps a tail); this runner only concatenates what it saw.
|
|
100
|
+
*/
|
|
101
|
+
function runStreaming(argv, options = {}, onChunk) {
|
|
102
|
+
const [file, ...args] = argv;
|
|
103
|
+
if (!file) throw new Error("runStreaming() needs a command");
|
|
104
|
+
const { cwd, timeoutMs = 3e4, extraPath } = options;
|
|
105
|
+
const child = spawn(file, args, {
|
|
106
|
+
cwd,
|
|
107
|
+
stdio: [
|
|
108
|
+
"ignore",
|
|
109
|
+
"pipe",
|
|
110
|
+
"pipe"
|
|
111
|
+
],
|
|
112
|
+
env: {
|
|
113
|
+
...process.env,
|
|
114
|
+
PATH: spawnPath(extraPath)
|
|
115
|
+
},
|
|
116
|
+
windowsHide: true
|
|
117
|
+
});
|
|
118
|
+
let stdout = "";
|
|
119
|
+
let stderr = "";
|
|
120
|
+
let timedOut = false;
|
|
121
|
+
const timer = setTimeout(() => {
|
|
122
|
+
timedOut = true;
|
|
123
|
+
child.kill("SIGKILL");
|
|
124
|
+
}, timeoutMs);
|
|
125
|
+
child.stdout?.on("data", (chunk) => {
|
|
126
|
+
stdout += chunk.toString();
|
|
127
|
+
onChunk?.(chunk.toString());
|
|
128
|
+
});
|
|
129
|
+
child.stderr?.on("data", (chunk) => {
|
|
130
|
+
stderr += chunk.toString();
|
|
131
|
+
onChunk?.(chunk.toString());
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
promise: new Promise((resolve) => {
|
|
135
|
+
const startedAt = Date.now();
|
|
136
|
+
child.on("error", (error) => {
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
resolve({
|
|
139
|
+
code: -1,
|
|
140
|
+
stdout,
|
|
141
|
+
stderr: stderr + String(error),
|
|
142
|
+
timedOut
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
child.on("close", (code) => {
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
const hitTimeout = timedOut && Date.now() - startedAt >= timeoutMs - 1;
|
|
148
|
+
resolve({
|
|
149
|
+
code: code ?? (timedOut || hitTimeout ? -1 : 0),
|
|
150
|
+
stdout,
|
|
151
|
+
stderr,
|
|
152
|
+
timedOut: timedOut || hitTimeout
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}),
|
|
156
|
+
kill: () => {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
child.kill("SIGKILL");
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
95
162
|
/** Truncate captured output for audit logging. */
|
|
96
163
|
function tail$1(text, max = 2e3) {
|
|
97
164
|
const value = text.trim();
|
|
@@ -328,6 +395,11 @@ async function detect(config, env = {}) {
|
|
|
328
395
|
* own host profile);
|
|
329
396
|
* - install specs must be non-empty, option-free single tokens; remove
|
|
330
397
|
* names must already be profile dependencies;
|
|
398
|
+
* - before any install/remove, `link:` dependencies are realpath-checked;
|
|
399
|
+
* a dangling target (checkout moved, manifest rewritten without
|
|
400
|
+
* reinstalling) makes the official CLI's post-install bundle
|
|
401
|
+
* reconciliation exit 1 with an opaque resolve error, so the operation
|
|
402
|
+
* refuses up front and names the repair;
|
|
331
403
|
* - the engine core package (`dsh-superagent`) can be neither removed
|
|
332
404
|
* nor toggled — without it the console has no subject;
|
|
333
405
|
* - operations are single-flight in-process: install, remove, and
|
|
@@ -433,6 +505,71 @@ function validateRegistry(raw) {
|
|
|
433
505
|
if (!/^https?:\/\/\S+$/.test(registry) || registry.length > 256) throw new PluginRequestError("invalid registry: an http(s) URL is required");
|
|
434
506
|
return registry;
|
|
435
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* Collect `link:` dependencies whose target no longer exists. The official
|
|
510
|
+
* CLI sails through the package operation itself (pnpm trusts the lockfile's
|
|
511
|
+
* recorded link resolution without checking the target) but its post-install
|
|
512
|
+
* bundle reconciliation probes `node_modules/<name>/package.json` and exits 1
|
|
513
|
+
* with an opaque "cannot resolve profile bundle" error — the shape seen after
|
|
514
|
+
* a hosting checkout is moved and only the manifest specifiers are rewritten.
|
|
515
|
+
* Refusing up front keeps the failure readable and names the repair.
|
|
516
|
+
*/
|
|
517
|
+
function danglingLinks(config) {
|
|
518
|
+
const manifest = readManifest(config.profileName);
|
|
519
|
+
if (!manifest) return [];
|
|
520
|
+
const dir = profileDir(config.profileName);
|
|
521
|
+
const broken = [];
|
|
522
|
+
for (const [name, spec] of Object.entries(manifest.dependencies ?? {})) {
|
|
523
|
+
if (!spec.startsWith("link:")) continue;
|
|
524
|
+
const target = spec.slice(5);
|
|
525
|
+
const resolved = path.isAbsolute(target) ? target : path.resolve(dir, target);
|
|
526
|
+
if (!existsSync(resolved)) broken.push(`${name} (${spec})`);
|
|
527
|
+
}
|
|
528
|
+
return broken;
|
|
529
|
+
}
|
|
530
|
+
/** Package-name reading of an install spec, when derivable offline. */
|
|
531
|
+
function specPackageName(config, spec) {
|
|
532
|
+
const trimmed = spec.trim();
|
|
533
|
+
if (trimmed.startsWith("link:") || trimmed.startsWith("file:")) {
|
|
534
|
+
const target = trimmed.slice(trimmed.indexOf(":") + 1);
|
|
535
|
+
const resolved = path.isAbsolute(target) ? target : path.resolve(profileDir(config.profileName), target);
|
|
536
|
+
try {
|
|
537
|
+
const manifest = JSON.parse(readFileSync(path.join(resolved, "package.json"), "utf8"));
|
|
538
|
+
return typeof manifest.name === "string" ? manifest.name : null;
|
|
539
|
+
} catch {
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (/^https?:\/\//.test(trimmed) || trimmed.startsWith("git")) return null;
|
|
544
|
+
if (trimmed.startsWith("@")) {
|
|
545
|
+
const at = trimmed.indexOf("@", 1);
|
|
546
|
+
return at === -1 ? trimmed : trimmed.slice(0, at);
|
|
547
|
+
}
|
|
548
|
+
const at = trimmed.indexOf("@");
|
|
549
|
+
return at === -1 ? trimmed : trimmed.slice(0, at);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Refuse installs before they start when the spec is malformed, already a
|
|
553
|
+
* profile dependency, or the profile carries dangling link: dependencies
|
|
554
|
+
* (the official CLI's post-install reconciliation exits 1 on those). Mirrors
|
|
555
|
+
* the native manager's checking phase and its refusal screen.
|
|
556
|
+
*/
|
|
557
|
+
function checkProblems(config, raw) {
|
|
558
|
+
const spec = typeof raw === "string" ? raw.trim() : "";
|
|
559
|
+
if (!spec || spec.length > 512 || /[\r\n]/.test(spec) || spec.startsWith("-")) return [{
|
|
560
|
+
kind: "invalid",
|
|
561
|
+
detail: "a non-empty package name, git URL, tarball, or absolute path is required"
|
|
562
|
+
}];
|
|
563
|
+
const problems = [];
|
|
564
|
+
const broken = danglingLinks(config);
|
|
565
|
+
if (broken.length > 0) problems.push({
|
|
566
|
+
kind: "links",
|
|
567
|
+
detail: broken.join(", ")
|
|
568
|
+
});
|
|
569
|
+
const name = specPackageName(config, spec);
|
|
570
|
+
if (name && Object.hasOwn(readManifest(config.profileName)?.dependencies ?? {}, name)) problems.push({ kind: "installed" });
|
|
571
|
+
return problems;
|
|
572
|
+
}
|
|
436
573
|
/** Validate a remove/enable target: an existing profile dependency, not the engine core. */
|
|
437
574
|
function validateName(config, raw, allowProtected = false) {
|
|
438
575
|
const name = typeof raw === "string" ? raw.trim() : "";
|
|
@@ -460,12 +597,29 @@ async function runCliOperation(config, env, kind, target, registryUrl) {
|
|
|
460
597
|
target: redact$1(target),
|
|
461
598
|
...registryUrl ? { registry: registryUrl } : {}
|
|
462
599
|
});
|
|
463
|
-
const
|
|
600
|
+
const before = Object.keys(readManifest(config.profileName)?.dependencies ?? {});
|
|
601
|
+
let output = "";
|
|
602
|
+
const result = await runStreaming(argv, {
|
|
464
603
|
timeoutMs: kind === "install" ? 9e5 : 6e5,
|
|
465
604
|
extraPath: env.extraPath
|
|
466
|
-
})
|
|
467
|
-
|
|
605
|
+
}, (chunk) => {
|
|
606
|
+
output = (output + chunk).slice(-4e3);
|
|
607
|
+
last = {
|
|
608
|
+
...last,
|
|
609
|
+
output: redact$1(output.trim())
|
|
610
|
+
};
|
|
611
|
+
}).promise;
|
|
468
612
|
const failed = result.code !== 0;
|
|
613
|
+
let installed;
|
|
614
|
+
if (!failed && kind === "install") {
|
|
615
|
+
const after = readManifest(config.profileName)?.dependencies ?? {};
|
|
616
|
+
installed = Object.entries(after).filter(([name]) => !before.includes(name)).map(([name, spec]) => ({
|
|
617
|
+
name,
|
|
618
|
+
spec,
|
|
619
|
+
version: readInstalledManifest(config.profileName, name)?.version ?? null,
|
|
620
|
+
bundle: readInstalledManifest(config.profileName, name)?.dsh?.bundle !== void 0
|
|
621
|
+
}));
|
|
622
|
+
}
|
|
469
623
|
const state = {
|
|
470
624
|
running: false,
|
|
471
625
|
kind,
|
|
@@ -474,8 +628,10 @@ async function runCliOperation(config, env, kind, target, registryUrl) {
|
|
|
474
628
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
475
629
|
exitCode: result.code,
|
|
476
630
|
timedOut: result.timedOut,
|
|
477
|
-
output,
|
|
478
|
-
error: failed ? result.timedOut ? "command timed out" : `exit ${result.code}` : null
|
|
631
|
+
output: tail$1(redact$1((result.stdout + "\n" + result.stderr).trim()), 4e3),
|
|
632
|
+
error: failed ? result.timedOut ? "command timed out" : `exit ${result.code}` : null,
|
|
633
|
+
installed,
|
|
634
|
+
profileDir: profileDir(config.profileName)
|
|
479
635
|
};
|
|
480
636
|
last = state;
|
|
481
637
|
audit$1(config, {
|
|
@@ -496,6 +652,8 @@ async function runCliOperation(config, env, kind, target, registryUrl) {
|
|
|
496
652
|
function runPluginOperation(config, env, kind, target, registry) {
|
|
497
653
|
if (config.profileName === "desktop") return Promise.reject(new PluginRequestError("refusing to manage plugins of the desktop profile"));
|
|
498
654
|
if (inFlight$1) return Promise.reject(new PluginRequestError("another plugin operation is already running"));
|
|
655
|
+
const broken = danglingLinks(config);
|
|
656
|
+
if (broken.length > 0) return Promise.reject(new PluginRequestError(`dangling link dependencies in the ${config.profileName} profile — the official installer's bundle reconciliation would fail; repair them first:\n ` + broken.join("\n ") + `\nFix: correct the targets in package.json and the pnpm-lock version fields, then run 'dsh plugin --profile ${config.profileName} install'.`));
|
|
499
657
|
const prepared = kind === "install" ? validateSpec(target) : validateName(config, target);
|
|
500
658
|
const registryUrl = kind === "install" ? validateRegistry(registry) : null;
|
|
501
659
|
last = {
|
|
@@ -1530,6 +1688,11 @@ function consoleApiHandler(options) {
|
|
|
1530
1688
|
case "GET /superagent-console/api/plugins/operation":
|
|
1531
1689
|
sendJson(res, 200, pluginOperation());
|
|
1532
1690
|
return;
|
|
1691
|
+
case "POST /superagent-console/api/plugins/check": {
|
|
1692
|
+
const body = await readJson(req);
|
|
1693
|
+
sendJson(res, 200, { problems: checkProblems(config, body.spec) });
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1533
1696
|
case "POST /superagent-console/api/plugins/install": {
|
|
1534
1697
|
const body = await readJson(req);
|
|
1535
1698
|
if (pluginBusy()) {
|
|
@@ -1756,4 +1919,4 @@ function apply(ctx, config) {
|
|
|
1756
1919
|
ctx.logger?.info?.("dsh-superagent-console: provider sync source attached (autoSync %s, mode %s)", resolved.autoSync ? "on" : "off", resolved.syncMode);
|
|
1757
1920
|
});
|
|
1758
1921
|
}
|
|
1759
|
-
export { Config, apply, applyProviderSync, detect, diffProviders, engineAction, inject, name, proxyHandler, readEngineProviders, readHostProviders, readSetupState, runInstall };
|
|
1922
|
+
export { Config, apply, applyProviderSync, danglingLinks, detect, diffProviders, engineAction, inject, name, proxyHandler, readEngineProviders, readHostProviders, readSetupState, runInstall };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-superagent-console",
|
|
3
|
-
"description": "Desktop console for the
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"description": "Desktop console for the dsh-superagent engine: same-origin proxied panel with engine detection, guided online install, plugin management, and model-provider sync.",
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Shuai-S",
|
|
7
7
|
"type": "module",
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"types": "./lib/index.d.ts",
|
|
13
13
|
"default": "./lib/index.js"
|
|
14
14
|
},
|
|
15
|
-
"./client": "./lib/client.js"
|
|
15
|
+
"./client": "./lib/client.js",
|
|
16
|
+
"./locale/*": "./locale/*",
|
|
17
|
+
"./package.json": "./package.json"
|
|
16
18
|
},
|
|
17
19
|
"files": [
|
|
18
20
|
"lib",
|
|
@@ -71,6 +73,7 @@
|
|
|
71
73
|
"@deepseek-ai/dsh-client-locale": "0.2.1-alpha.2",
|
|
72
74
|
"@deepseek-ai/dsh-client-ui-layout": "0.2.1-alpha.2",
|
|
73
75
|
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.1-alpha.2",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.5",
|
|
74
77
|
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.2",
|
|
75
78
|
"@deepseek-ai/dsh-client-ui-settings": "0.2.1-alpha.2",
|
|
76
79
|
"@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-alpha.2",
|