@zaimokuza/dsh-plugin-hub 0.2.0 → 0.2.1
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 -2
- package/lib/client.js +108 -13
- package/package.json +1 -1
- package/src/experiment-definitions.js +6 -0
- package/src/experiments.js +6 -4
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# DSH Plugin Hub
|
|
2
2
|
|
|
3
|
-
DSH 原生资源面板,当前版本 **0.2.
|
|
3
|
+
DSH 原生资源面板,当前版本 **0.2.1**。支持 Web 与 Desktop、中英文界面;从侧边栏打开,保留当前会话。
|
|
4
4
|
|
|
5
5
|
- **Skill**:搜索、按工作区筛选、启停、打开目录,使用 DSH 原生 Markdown 组件预览正文。
|
|
6
6
|
- **MCP**:JSON/YAML 配置编辑与格式化、环境变量引用、字段帮助、启停、重连、独立连接测试和删除。
|
|
7
7
|
- **Plugin**:查看当前 profile 的非官方原生插件,展示版本与来源;桌面端提供原生管理窗口入口说明。
|
|
8
|
-
- **实验性功能**:管理 Agent Teams;Web
|
|
8
|
+
- **实验性功能**:管理 Agent Teams;Web 支持安装与启停,支持实验性功能接口的 Desktop 可直接在 Hub 启停。
|
|
9
9
|
|
|
10
10
|
## 安装
|
|
11
11
|
|
|
@@ -23,6 +23,8 @@ dsh plugin --profile <当前-profile> add @zaimokuza/dsh-plugin-hub
|
|
|
23
23
|
|
|
24
24
|
MCP 的描述、版本和图标来自服务初始化信息。宿主未公开持续连接状态时显示“未知”,独立连接测试单独显示结果。建议在启动 DSH 时设置凭据环境变量,在 YAML 中通过 `!!js process.env.MCP_TOKEN` 引用;JSON 等价写法为 `{ "__jsExpr": "process.env.MCP_TOKEN" }`。
|
|
25
25
|
|
|
26
|
+
Desktop 实验性功能通过宿主的 `window.dshDesktop.experiments` v1 接口执行,宿主完成空闲检查、配置事务和后端重启。缺少接口的 Desktop 显示不支持管理。
|
|
27
|
+
|
|
26
28
|
目录操作作用于运行 DSH 的电脑。Desktop 原生管理窗口使用 macOS `Cmd+,`、Windows `Ctrl+,` 或系统菜单 **Desktop Plugins**。更多实现边界见 [原生资源说明](https://github.com/zaimokuza-yoshiteru/dsh-plugin-hub/blob/main/docs/native-resources.md)。
|
|
27
29
|
|
|
28
30
|
## 开发与发布
|
package/lib/client.js
CHANGED
|
@@ -267,7 +267,7 @@ function SkillPreview({ file, t, close }) {
|
|
|
267
267
|
}, [file]);
|
|
268
268
|
return /* @__PURE__ */ import_react.default.createElement("aside", { className: "hub-skill-view", ref: view, "aria-label": title }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-card-heading" }, /* @__PURE__ */ import_react.default.createElement("h2", { title }, title), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", onClick: close, icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconCloseOutline16, null) }, t("\u5173\u95ED"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-skill-markdown" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.MarkdownText, { text: body, labels: { code: { copyLabel: t("\u590D\u5236"), copiedLabel: t("\u5DF2\u590D\u5236") }, footnotes: t("\u811A\u6CE8") } })));
|
|
269
269
|
}
|
|
270
|
-
function ResourceHub({ ctx, request, t, brand, panelId }) {
|
|
270
|
+
function ResourceHub({ ctx, request, experiments, t, brand, panelId }) {
|
|
271
271
|
(0, import_react.useSyncExternalStore)((fn) => ctx.locale.subscribe(fn), () => ctx.locale.getSnapshot().active);
|
|
272
272
|
const [tab, setTab] = (0, import_react.useState)("skills");
|
|
273
273
|
const [scope, setScope] = (0, import_react.useState)("");
|
|
@@ -291,9 +291,14 @@ function ResourceHub({ ctx, request, t, brand, panelId }) {
|
|
|
291
291
|
const refresh = async (signal) => {
|
|
292
292
|
const token = ++generation.current;
|
|
293
293
|
try {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
294
|
+
const result = await request("resources", { workspaceId: scope }, { signal });
|
|
295
|
+
try {
|
|
296
|
+
result.experiments = await experiments.list(result.profile, { signal });
|
|
297
|
+
} catch (error2) {
|
|
298
|
+
result.experiments = [];
|
|
299
|
+
result.experimentsError = error2.message;
|
|
300
|
+
}
|
|
301
|
+
if (mounted.current && token === generation.current && !signal?.aborted) {
|
|
297
302
|
setData(result);
|
|
298
303
|
setError("");
|
|
299
304
|
}
|
|
@@ -314,7 +319,7 @@ function ResourceHub({ ctx, request, t, brand, panelId }) {
|
|
|
314
319
|
const result = await request(route, { ...body, workspaceId: scope, profile: data.profile.directory });
|
|
315
320
|
if (mounted.current && result.skills) {
|
|
316
321
|
generation.current++;
|
|
317
|
-
setData((previous) => ({ ...result, experiments: previous.experiments }));
|
|
322
|
+
setData((previous) => ({ ...result, experiments: previous.experiments, experimentsError: previous.experimentsError }));
|
|
318
323
|
}
|
|
319
324
|
return result;
|
|
320
325
|
} catch (error2) {
|
|
@@ -324,6 +329,30 @@ function ResourceHub({ ctx, request, t, brand, panelId }) {
|
|
|
324
329
|
if (mounted.current) setBusy(false);
|
|
325
330
|
}
|
|
326
331
|
};
|
|
332
|
+
const changeExperiment = async (row, enabled, operation = "toggle") => {
|
|
333
|
+
setBusy(true);
|
|
334
|
+
setError("");
|
|
335
|
+
generation.current++;
|
|
336
|
+
let failure;
|
|
337
|
+
try {
|
|
338
|
+
await experiments.change(data.profile, row, enabled, operation);
|
|
339
|
+
} catch (error2) {
|
|
340
|
+
failure = error2.message;
|
|
341
|
+
} finally {
|
|
342
|
+
if (mounted.current) {
|
|
343
|
+
try {
|
|
344
|
+
const latest = await experiments.list(data.profile);
|
|
345
|
+
if (mounted.current) setData((previous) => ({ ...previous, experiments: latest, experimentsError: void 0 }));
|
|
346
|
+
} catch (error2) {
|
|
347
|
+
failure ??= error2.message;
|
|
348
|
+
}
|
|
349
|
+
if (mounted.current) {
|
|
350
|
+
if (failure) setError(failure);
|
|
351
|
+
setBusy(false);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
327
356
|
const run = (body, route) => {
|
|
328
357
|
void action(body, route).catch(() => {
|
|
329
358
|
});
|
|
@@ -348,13 +377,11 @@ function ResourceHub({ ctx, request, t, brand, panelId }) {
|
|
|
348
377
|
void refresh().finally(() => {
|
|
349
378
|
if (mounted.current) setBusy(false);
|
|
350
379
|
});
|
|
351
|
-
}, icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconRefreshOutline16, null) }, t("\u5237\u65B0")), tab === "plugins" && data?.profile.installation === "desktop" && /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { onClick: () => setDesktopHelp(true) }, t("\u684C\u9762\u63D2\u4EF6\u7BA1\u7406")), tab === "mcps" && /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "md", variant: "primary", disabled: !data || busy, onClick: () => setEditor({}) }, t("\u6DFB\u52A0 MCP"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-results" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("\u5F53\u524D\u5217\u8868"), " ", /* @__PURE__ */ import_react.default.createElement("b", null, rows.length)), /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-resource-scope-note" }, t(tab === "skills" ? "\u5F00\u5173\u53EA\u5F71\u54CD\u5F53\u524D profile\uFF1BSkill \u6E90\u6587\u4EF6\u4FDD\u6301\u539F\u6837\u3002" : tab === "mcps" ? "MCP \u4E3A\u5F53\u524D profile \u7684\u5168\u5C40\u670D\u52A1\uFF1B\u5DE5\u4F5C\u533A\u8303\u56F4\u53EA\u7B5B\u9009 Skill\u3002" : tab === "plugins" ? "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684\u975E\u5B98\u65B9\u63D2\u4EF6" : "\u5B9E\u9A8C\u6027\u529F\u80FD"))), error && /* @__PURE__ */ import_react.default.createElement("p", { role: "alert" }, t(error)), !data && !error && /* @__PURE__ */ import_react.default.createElement("p", { role: "status" }, t("\u6B63\u5728\u8BFB\u53D6\u8D44\u6E90")), data && tab === "skills" && !data.skillsComplete && /* @__PURE__ */ import_react.default.createElement("p", { role: "status" }, t("\u8D44\u6E90\u5C1A\u672A\u5B8C\u6574\u52A0\u8F7D\uFF0C\u8BF7\u5237\u65B0\u91CD\u8BD5")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-grid hub-resource-grid" }, rows.map((row) => /* @__PURE__ */ import_react.default.createElement("article", { key: row.id ?? row.name, className: "hub-card hub-resource-card" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-card-heading" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-identity" }, /* @__PURE__ */ import_react.default.createElement(ItemIcon, { row }), /* @__PURE__ */ import_react.default.createElement("h2", { className: "hub-resource-card-name", title: row.name }, row.name, (tab === "mcps" || tab === "plugins") && /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-resource-version" }, "@", row.version || t("\u672A\u77E5")))), tab !== "plugins" && tab !== "experiments" ? /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Switch, { checked: Boolean(row.enabled), label: `${t(row.enabled ? "\u7981\u7528" : "\u542F\u7528")} ${row.name}`, disabled: busy || tab === "skills" && (!row.canToggle || !row.enabled && !row.managed), title: tab === "skills" && !row.canToggle ? t("\u6B64\u5F00\u5173\u7EE7\u627F\u81EA\u5176\u5B83\u8303\u56F4\uFF0C\u8BF7\u5207\u6362\u8303\u56F4\u4FEE\u6539") : void 0, onChange: (enabled) => run({ action: tab === "skills" ? "skill-toggle" : "mcp-toggle", id: row.id, revision: row.revision, enabled }) }) : tab === "experiments" ? /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Switch, { checked: Boolean(row.enabled), label: `${t(row.enabled ? "\u7981\u7528" : "\u542F\u7528")} ${row.name}`, disabled: busy || !row.canToggle, onChange: (enabled) => {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
void request("experiment-mutate", { id: row.id, action: "install", profile: data.profile.directory }).then(() => refresh()).catch((error2) => setError(error2.message)).finally(() => setBusy(false));
|
|
357
|
-
} }, t("\u5B89\u88C5 Agent Teams")), data.profile.installation === "desktop" && /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", onClick: () => setDesktopHelp(true) }, t("\u684C\u9762\u63D2\u4EF6\u7BA1\u7406"))), tab !== "experiments" && /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-card-footer" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-actions" }, tab === "plugins" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: busy, icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconFolderOpenOutline16, null), onClick: () => run({ id: row.name, kind: "plugin" }, "open-directory") }, t("\u6253\u5F00\u76EE\u5F55"))), tab === "skills" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: !row.directory || busy, onClick: () => {
|
|
380
|
+
}, icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconRefreshOutline16, null) }, t("\u5237\u65B0")), tab === "plugins" && data?.profile.installation === "desktop" && /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { onClick: () => setDesktopHelp(true) }, t("\u684C\u9762\u63D2\u4EF6\u7BA1\u7406")), tab === "mcps" && /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "md", variant: "primary", disabled: !data || busy, onClick: () => setEditor({}) }, t("\u6DFB\u52A0 MCP"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-results" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("\u5F53\u524D\u5217\u8868"), " ", /* @__PURE__ */ import_react.default.createElement("b", null, rows.length)), /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-resource-scope-note" }, t(tab === "skills" ? "\u5F00\u5173\u53EA\u5F71\u54CD\u5F53\u524D profile\uFF1BSkill \u6E90\u6587\u4EF6\u4FDD\u6301\u539F\u6837\u3002" : tab === "mcps" ? "MCP \u4E3A\u5F53\u524D profile \u7684\u5168\u5C40\u670D\u52A1\uFF1B\u5DE5\u4F5C\u533A\u8303\u56F4\u53EA\u7B5B\u9009 Skill\u3002" : tab === "plugins" ? "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684\u975E\u5B98\u65B9\u63D2\u4EF6" : "\u5B9E\u9A8C\u6027\u529F\u80FD"))), error && /* @__PURE__ */ import_react.default.createElement("p", { role: "alert" }, t(error)), tab === "experiments" && data?.experimentsError && /* @__PURE__ */ import_react.default.createElement("p", { role: "alert" }, t(data.experimentsError)), !data && !error && /* @__PURE__ */ import_react.default.createElement("p", { role: "status" }, t("\u6B63\u5728\u8BFB\u53D6\u8D44\u6E90")), data && tab === "skills" && !data.skillsComplete && /* @__PURE__ */ import_react.default.createElement("p", { role: "status" }, t("\u8D44\u6E90\u5C1A\u672A\u5B8C\u6574\u52A0\u8F7D\uFF0C\u8BF7\u5237\u65B0\u91CD\u8BD5")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-grid hub-resource-grid" }, rows.map((row) => /* @__PURE__ */ import_react.default.createElement("article", { key: row.id ?? row.name, className: "hub-card hub-resource-card" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-card-heading" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-identity" }, /* @__PURE__ */ import_react.default.createElement(ItemIcon, { row }), /* @__PURE__ */ import_react.default.createElement("h2", { className: "hub-resource-card-name", title: row.name }, row.name, (tab === "mcps" || tab === "plugins") && /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-resource-version" }, "@", row.version || t("\u672A\u77E5")))), tab !== "plugins" && tab !== "experiments" ? /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Switch, { checked: Boolean(row.enabled), label: `${t(row.enabled ? "\u7981\u7528" : "\u542F\u7528")} ${row.name}`, disabled: busy || tab === "skills" && (!row.canToggle || !row.enabled && !row.managed), title: tab === "skills" && !row.canToggle ? t("\u6B64\u5F00\u5173\u7EE7\u627F\u81EA\u5176\u5B83\u8303\u56F4\uFF0C\u8BF7\u5207\u6362\u8303\u56F4\u4FEE\u6539") : void 0, onChange: (enabled) => run({ action: tab === "skills" ? "skill-toggle" : "mcp-toggle", id: row.id, revision: row.revision, enabled }) }) : tab === "experiments" ? /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Switch, { checked: Boolean(row.enabled), label: `${t(row.enabled ? "\u7981\u7528" : "\u542F\u7528")} ${row.name}`, disabled: busy || row.busy || !row.canToggle, onChange: (enabled) => {
|
|
381
|
+
void changeExperiment(row, enabled);
|
|
382
|
+
} }) : null), tab === "mcps" && /* @__PURE__ */ import_react.default.createElement(McpSummary, { row, t, activation }), /* @__PURE__ */ import_react.default.createElement("p", { className: "hub-description", title: tab === "experiments" ? t(row.description) : row.description }, (tab === "experiments" ? t(row.description) : row.description) || t(tab === "mcps" ? "\u670D\u52A1\u5C1A\u672A\u63D0\u4F9B\u63CF\u8FF0\uFF0C\u53EF\u6D4B\u8BD5\u8FDE\u63A5\u8BFB\u53D6\u3002" : "\u6682\u65E0\u63CF\u8FF0")), tab === "mcps" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, row.probe && /* @__PURE__ */ import_react.default.createElement("p", { className: "hub-resource-meta" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Pill, null, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.StateDot, { state: row.probe.status === "success" ? "done" : "error" }), " ", t(row.probe.status === "success" ? "\u6D4B\u8BD5\u6210\u529F" : "\u6D4B\u8BD5\u5931\u8D25"), " \xB7 ", new Date(row.probe.at).toLocaleTimeString(), row.probe.tools !== void 0 && ` \xB7 ${t("\u5DE5\u5177\u6570")} ${row.probe.tools}`))), tab === "experiments" && /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-actions" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Pill, null, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.StateDot, { state: row.pendingRestart ? "warning" : row.activeEnabled ? "done" : "idle" }), t(row.busy || busy ? "\u5B9E\u9A8C\u6027\u529F\u80FD\u6B63\u5728\u66F4\u65B0" : row.pendingRestart ? "\u91CD\u542F DSH \u540E\u751F\u6548" : row.activeEnabled === null ? "\u8FD0\u884C\u72B6\u6001\u672A\u77E5" : row.activeEnabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u7981\u7528")), row.pendingRestart && /* @__PURE__ */ import_react.default.createElement("span", null, t(row.enabled ? "\u914D\u7F6E\u5DF2\u542F\u7528" : "\u914D\u7F6E\u5DF2\u7981\u7528")), !row.supported && /* @__PURE__ */ import_react.default.createElement("span", null, t("\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u7BA1\u7406\u6B64\u5B9E\u9A8C\u6027\u529F\u80FD")), !row.installed && row.canInstall && /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: busy || row.busy, onClick: () => {
|
|
383
|
+
void changeExperiment(row, row.enabled, "install");
|
|
384
|
+
} }, t("\u5B89\u88C5 Agent Teams"))), tab !== "experiments" && /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-card-footer" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-actions" }, tab === "plugins" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: busy, icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconFolderOpenOutline16, null), onClick: () => run({ id: row.name, kind: "plugin" }, "open-directory") }, t("\u6253\u5F00\u76EE\u5F55"))), tab === "skills" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: !row.directory || busy, onClick: () => {
|
|
358
385
|
setBusy(true);
|
|
359
386
|
void action({ id: row.id }, "skill-file").then(setFile).catch(() => {
|
|
360
387
|
});
|
|
@@ -373,6 +400,17 @@ function HubSidebarButton({ wide, usePanelInfo, ctx, panelId, brand }) {
|
|
|
373
400
|
|
|
374
401
|
// src/client/locale.js
|
|
375
402
|
var resourceWords = {
|
|
403
|
+
"\u65E0\u6548\u7684\u5B9E\u9A8C\u6027\u529F\u80FD\u8BF7\u6C42": "Invalid experiment request.",
|
|
404
|
+
"\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u7BA1\u7406\u6B64\u5B9E\u9A8C\u6027\u529F\u80FD": "This host does not support managing this experiment.",
|
|
405
|
+
"\u5B9E\u9A8C\u6027\u529F\u80FD\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5": "The experiment state has changed. Refresh and try again.",
|
|
406
|
+
"\u4ECD\u6709\u4EFB\u52A1\u6216\u8BF7\u6C42\u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\u6216\u505C\u6B62\u540E\u518D\u5207\u6362": "Tasks or requests are still running. Wait for them to finish or stop them before switching.",
|
|
407
|
+
"\u5B9E\u9A8C\u6027\u529F\u80FD\u5207\u6362\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0\u786E\u8BA4\u5F53\u524D\u72B6\u6001": "The experiment change failed. Refresh to check its current state.",
|
|
408
|
+
"\u5BBF\u4E3B\u5B9E\u9A8C\u6027\u529F\u80FD\u63A5\u53E3\u8FD4\u56DE\u4E86\u65E0\u6548\u72B6\u6001": "The host experiment API returned an invalid state.",
|
|
409
|
+
"Desktop \u5B9E\u9A8C\u6027\u529F\u80FD\u9700\u901A\u8FC7\u5BBF\u4E3B\u63A5\u53E3\u64CD\u4F5C": "Desktop experiments must be managed through the host API.",
|
|
410
|
+
"\u8FD0\u884C\u72B6\u6001\u672A\u77E5": "Runtime state unknown",
|
|
411
|
+
"\u914D\u7F6E\u5DF2\u542F\u7528": "Configured to enable",
|
|
412
|
+
"\u914D\u7F6E\u5DF2\u7981\u7528": "Configured to disable",
|
|
413
|
+
"\u5B9E\u9A8C\u6027\u591A Agent \u534F\u4F5C\u5DE5\u5177\u4E0E\u56E2\u961F\u9762\u677F\u3002Web \u5207\u6362\u540E\u9700\u91CD\u542F DSH\uFF1B\u652F\u6301\u7BA1\u7406\u7684 Desktop \u4F1A\u81EA\u52A8\u91CD\u542F\u540E\u7AEF\uFF0C\u4F1A\u8BDD\u6570\u636E\u4FDD\u7559\u3002": "Experimental multi-agent tools and team panel. Restart DSH after Web changes; supported Desktop hosts restart the backend automatically. Conversations are retained.",
|
|
376
414
|
"HTTP \u5FC5\u586B": "Required for HTTP",
|
|
377
415
|
"HTTP \u8BF7\u6C42\u5934\uFF0C\u53EF\u5F15\u7528\u73AF\u5883\u53D8\u91CF\u3002": "HTTP request headers; environment references are supported.",
|
|
378
416
|
"Hub profile \u914D\u7F6E\u4E0E\u5F53\u524D\u5B9E\u4F8B\u4E0D\u4E00\u81F4": "Hub's configured profile does not match this instance.",
|
|
@@ -545,6 +583,62 @@ function connectionRequest(connection, marketId) {
|
|
|
545
583
|
};
|
|
546
584
|
}
|
|
547
585
|
|
|
586
|
+
// src/experiment-definitions.js
|
|
587
|
+
var agentTeams = Object.freeze({
|
|
588
|
+
id: "agent-teams",
|
|
589
|
+
name: "Agent Teams",
|
|
590
|
+
description: "\u5B9E\u9A8C\u6027\u591A Agent \u534F\u4F5C\u5DE5\u5177\u4E0E\u56E2\u961F\u9762\u677F\u3002Web \u5207\u6362\u540E\u9700\u91CD\u542F DSH\uFF1B\u652F\u6301\u7BA1\u7406\u7684 Desktop \u4F1A\u81EA\u52A8\u91CD\u542F\u540E\u7AEF\uFF0C\u4F1A\u8BDD\u6570\u636E\u4FDD\u7559\u3002"
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// src/client/experiments.js
|
|
594
|
+
var errors = {
|
|
595
|
+
"invalid-request": "\u65E0\u6548\u7684\u5B9E\u9A8C\u6027\u529F\u80FD\u8BF7\u6C42",
|
|
596
|
+
"profile-changed": "\u5B9E\u4F8B profile \u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0",
|
|
597
|
+
unsupported: "\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u7BA1\u7406\u6B64\u5B9E\u9A8C\u6027\u529F\u80FD",
|
|
598
|
+
busy: "\u5B9E\u9A8C\u6027\u529F\u80FD\u6B63\u5728\u66F4\u65B0",
|
|
599
|
+
"state-changed": "\u5B9E\u9A8C\u6027\u529F\u80FD\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5",
|
|
600
|
+
"tasks-running": "\u4ECD\u6709\u4EFB\u52A1\u6216\u8BF7\u6C42\u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\u6216\u505C\u6B62\u540E\u518D\u5207\u6362",
|
|
601
|
+
failed: "\u5B9E\u9A8C\u6027\u529F\u80FD\u5207\u6362\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0\u786E\u8BA4\u5F53\u524D\u72B6\u6001"
|
|
602
|
+
};
|
|
603
|
+
function desktopBridge(desktop) {
|
|
604
|
+
const bridge = desktop?.experiments;
|
|
605
|
+
return bridge?.version === 1 && typeof bridge.list === "function" && typeof bridge.setEnabled === "function" ? bridge : null;
|
|
606
|
+
}
|
|
607
|
+
function validateSnapshot(snapshot, profile) {
|
|
608
|
+
if (snapshot?.profile !== profile.directory) throw new Error("\u5B9E\u4F8B profile \u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0");
|
|
609
|
+
if (!Array.isArray(snapshot.features)) throw new Error("\u5BBF\u4E3B\u5B9E\u9A8C\u6027\u529F\u80FD\u63A5\u53E3\u8FD4\u56DE\u4E86\u65E0\u6548\u72B6\u6001");
|
|
610
|
+
const rows = snapshot.features.filter((row) => row?.id === "agent-teams");
|
|
611
|
+
if (rows.length > 1 || rows.some((row) => typeof row.enabled !== "boolean" || ![true, false, null].includes(row.activeEnabled) || ["installed", "canToggle", "busy"].some((key) => typeof row[key] !== "boolean"))) throw new Error("\u5BBF\u4E3B\u5B9E\u9A8C\u6027\u529F\u80FD\u63A5\u53E3\u8FD4\u56DE\u4E86\u65E0\u6548\u72B6\u6001");
|
|
612
|
+
return rows;
|
|
613
|
+
}
|
|
614
|
+
function experimentClient(request, desktop, reload) {
|
|
615
|
+
return {
|
|
616
|
+
async list(profile, options = {}) {
|
|
617
|
+
if (profile.installation !== "desktop") return request("experiments", {}, options);
|
|
618
|
+
const baseline = [{ ...agentTeams, enabled: null, activeEnabled: null, installed: false, busy: false, pendingRestart: false }];
|
|
619
|
+
const bridge = desktopBridge(desktop);
|
|
620
|
+
const native = bridge ? validateSnapshot(await bridge.list(), profile) : [];
|
|
621
|
+
return baseline.map((row) => {
|
|
622
|
+
const feature = native.find((item) => item.id === row.id);
|
|
623
|
+
return feature ? { ...row, ...feature, canInstall: false, supported: true, pendingRestart: feature.activeEnabled !== null && feature.enabled !== feature.activeEnabled } : { ...row, canToggle: false, canInstall: false, supported: false };
|
|
624
|
+
});
|
|
625
|
+
},
|
|
626
|
+
async change(profile, row, enabled, action = "toggle") {
|
|
627
|
+
if (profile.installation !== "desktop") return request("experiment-mutate", { profile: profile.directory, id: row.id, action, enabled, expectedEnabled: row.enabled });
|
|
628
|
+
const bridge = desktopBridge(desktop);
|
|
629
|
+
if (!bridge || !row.supported || !row.canToggle || action !== "toggle") throw new Error("\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u7BA1\u7406\u6B64\u5B9E\u9A8C\u6027\u529F\u80FD");
|
|
630
|
+
const result = await bridge.setEnabled({ profile: profile.directory, id: row.id, enabled, expectedEnabled: row.enabled });
|
|
631
|
+
if (result?.ok === false) {
|
|
632
|
+
throw new Error(errors[result.error?.code] ?? errors.failed);
|
|
633
|
+
}
|
|
634
|
+
if (result?.ok !== true || typeof result.reloadRequired !== "boolean") throw new Error("\u5BBF\u4E3B\u5B9E\u9A8C\u6027\u529F\u80FD\u63A5\u53E3\u8FD4\u56DE\u4E86\u65E0\u6548\u72B6\u6001");
|
|
635
|
+
validateSnapshot(result.value, profile);
|
|
636
|
+
if (result.reloadRequired) reload();
|
|
637
|
+
return result.value;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
548
642
|
// src/client/index.jsx
|
|
549
643
|
var NS = "@zaimokuza/dsh-plugin-hub";
|
|
550
644
|
var MARKET_ID = "hub";
|
|
@@ -556,8 +650,9 @@ function apply(ctx) {
|
|
|
556
650
|
ctx.effect(() => ctx.locale.register(NS, words), "Plugin Hub: dictionaries");
|
|
557
651
|
const t = translate(ctx.locale.bind(NS));
|
|
558
652
|
const request = connectionRequest(ctx.connection, MARKET_ID);
|
|
653
|
+
const experiments = experimentClient(request, window.dshDesktop, () => window.location.reload());
|
|
559
654
|
const panelId = "plugin-hub-" + MARKET_ID;
|
|
560
|
-
ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: panelId, locale: NS }, () => /* @__PURE__ */ import_react2.default.createElement(ResourceHub, { ctx, request, t, brand: BRAND, panelId })));
|
|
655
|
+
ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: panelId, locale: NS }, () => /* @__PURE__ */ import_react2.default.createElement(ResourceHub, { ctx, request, experiments, t, brand: BRAND, panelId })));
|
|
561
656
|
ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({ name: "sidebar.footer.action", id: panelId, order: 39, locale: NS }, (props) => /* @__PURE__ */ import_react2.default.createElement(HubSidebarButton, { ...props, ctx, panelId, brand: BRAND })));
|
|
562
657
|
}
|
|
563
658
|
return module.exports; } });
|
package/package.json
CHANGED
package/src/experiments.js
CHANGED
|
@@ -3,9 +3,10 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { atomicWrite } from './profile-files.js';
|
|
6
|
+
import { agentTeams } from './experiment-definitions.js';
|
|
6
7
|
const bundles = ['@deepseek-ai/dsh-experimental-agent-team-profile', '@deepseek-ai/dsh-experimental-agent-team-web-profile'];
|
|
7
8
|
export class Experiments {
|
|
8
|
-
constructor(environment) { this.environment = environment; this.file = join(environment.profileDir, 'package.json'); this.initial =
|
|
9
|
+
constructor(environment) { this.environment = environment; this.file = join(environment.profileDir, 'package.json'); this.initial = undefined; this.busy = false; }
|
|
9
10
|
async snapshot() {
|
|
10
11
|
const manifest = JSON.parse(await readFile(this.file, 'utf8'));
|
|
11
12
|
const configuredBundles = manifest.dsh?.profile?.bundles;
|
|
@@ -13,17 +14,18 @@ export class Experiments {
|
|
|
13
14
|
const enabled = this.environment.installation === 'desktop'
|
|
14
15
|
? (typeof desktopSetting === 'boolean' ? desktopSetting : null)
|
|
15
16
|
: (Array.isArray(configuredBundles) ? bundles.every(name => configuredBundles.includes(name)) : null);
|
|
16
|
-
this.initial
|
|
17
|
+
if (this.initial === undefined) this.initial = enabled;
|
|
17
18
|
const resolver = createRequire(this.file);
|
|
18
19
|
const installed = bundles.every(name => { try { resolver.resolve(name + '/package.json'); return true; } catch { return false; } });
|
|
19
|
-
return [{
|
|
20
|
+
return [{ ...agentTeams, installed, enabled, activeEnabled: this.environment.installation === 'cli' ? this.initial : null, supported: this.environment.installation === 'cli', pendingRestart: this.environment.installation === 'cli' && this.initial !== null && this.initial !== enabled, canToggle: this.environment.installation === 'cli' && Array.isArray(configuredBundles) && installed, canInstall: this.environment.installation === 'cli', busy: this.busy }];
|
|
20
21
|
}
|
|
21
22
|
async mutate(data) {
|
|
22
23
|
if (data.profile !== this.environment.profileDir || data.id !== 'agent-teams') throw new Error('实例或功能已变化,请刷新');
|
|
23
24
|
if (this.busy) throw new Error('实验性功能正在更新');
|
|
24
|
-
if (this.environment.installation !== 'cli') throw new Error('
|
|
25
|
+
if (this.environment.installation !== 'cli') throw new Error('Desktop 实验性功能需通过宿主接口操作');
|
|
25
26
|
this.busy = true;
|
|
26
27
|
try {
|
|
28
|
+
if (data.expectedEnabled !== undefined && data.expectedEnabled !== (await this.snapshot())[0].enabled) throw new Error('实验性功能状态已变化,请刷新后重试');
|
|
27
29
|
if (data.action === 'install') {
|
|
28
30
|
for (const name of bundles) await new Promise((resolve, reject) => {
|
|
29
31
|
const child = spawn(process.execPath, [this.environment.cli, 'plugin', '--profile', this.environment.profile, 'add', `${name}@${this.environment.host.dsh}`, '--save-exact'], { cwd: this.environment.profileDir, env: { ...process.env, DSH_HOME: this.environment.home }, stdio: 'ignore', windowsHide: true, shell: false });
|