dsh-armory 1.10.0 → 2.0.0
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.en.md +57 -13
- package/README.md +70 -21
- package/docs/mcp-market-design.md +62 -0
- package/docs/prd-awesome-dsh-plugins.md +105 -0
- package/docs/prd-workbuddy-parity.md +128 -0
- package/docs/system-design-awesome.md +269 -0
- package/docs/system_design.md +530 -0
- package/docs/workbuddy-market-gap-analysis.md +197 -0
- package/lib/client.js +1631 -136
- package/lib/expert-market.js +13 -2
- package/lib/index.js +356 -7
- package/lib/mcp-oauth.js +806 -0
- package/lib/typert.js +24 -0
- package/lib/wb-auth-rules.js +128 -0
- package/lib/wb-awesome-market.js +453 -0
- package/lib/wb-install-state.js +151 -0
- package/lib/wb-market.js +213 -8
- package/lib/wb-plugin-market.js +291 -0
- package/lib/wb-registry.js +224 -0
- package/lib/whats-new.js +37 -0
- package/package.json +2 -2
package/lib/expert-market.js
CHANGED
|
@@ -790,11 +790,22 @@ export async function listExpertRecent() {
|
|
|
790
790
|
// expert 记录与召唤预设对账:预设已摘除(送还)的记录不再展示,也顺手清掉。
|
|
791
791
|
let patch = null;
|
|
792
792
|
try {
|
|
793
|
-
|
|
793
|
+
// 注意必须 await:漏 await 会拿到 Promise(truthy 且非字符串),后续
|
|
794
|
+
// parsePatchDoc 直接抛 TypeError,导致整条 listExpertRecent 恒失败。
|
|
795
|
+
patch = await readFile(join(homedir(), ".dsh", "profiles", "desktop", "cordis.patch.yml"), "utf8");
|
|
794
796
|
} catch {
|
|
795
797
|
patch = null;
|
|
796
798
|
}
|
|
797
|
-
|
|
799
|
+
// 对账用的 YAML 解析要单独兜底:patch 文件被用户改坏(yaml 抛 TypeError)只影响
|
|
800
|
+
// 「预设还活着吗」这一个判断,回落 live=null 即可(= 不清记录、全部展示);
|
|
801
|
+
// 若让它抛出去,整个 listExpertRecent → wbExpertRecent RPC 会恒失败,
|
|
802
|
+
// 客户端拿不到任何 kinds,「最近使用」排序整条链路失效——得不偿失。
|
|
803
|
+
let live = null;
|
|
804
|
+
try {
|
|
805
|
+
live = patch ? new Set(readExpertPresets(patch).map((row) => row.plugin)) : null;
|
|
806
|
+
} catch {
|
|
807
|
+
live = null;
|
|
808
|
+
}
|
|
798
809
|
kinds[key] = live ? list.filter((item) => live.has(String(item?.plugin || ""))) : list;
|
|
799
810
|
if (live && kinds[key].length !== list.length) {
|
|
800
811
|
data.kinds[key] = kinds[key];
|
package/lib/index.js
CHANGED
|
@@ -13,7 +13,24 @@ import { collectImportLocations, missingEnvNames, resolveImportLocation } from "
|
|
|
13
13
|
import { deriveMcpPhase, formatMcpLog, sanitizeMcpLog } from "./mcp-observability.js";
|
|
14
14
|
import { allMcpEntries, entriesForName, projectToolSchema, revisionOf, toolInventory } from "./mcp-registry.js";
|
|
15
15
|
import { WHATS_NEW } from "./whats-new.js";
|
|
16
|
-
import { wbConnectorCatalog, wbConnectorCachedCount, wbConnectorPreview, wbSkillCatalog, wbSkillDetail, wbSkillInstall, wbSkillInstalled, wbSkillUninstall } from "./wb-market.js";
|
|
16
|
+
import { probeMcpServersAuthorization, wbConnectorCatalog, wbConnectorCachedCount, wbConnectorPreview, wbSkillCatalog, wbSkillDetail, wbSkillInstall, wbSkillInstalled, wbSkillUninstall } from "./wb-market.js";
|
|
17
|
+
// T03:多市场注册表(R4)与 codebuddy 插件市场(R5)的宿主侧数据层。
|
|
18
|
+
import { readRegistry, upsertMarketplace } from "./wb-registry.js";
|
|
19
|
+
import { wbPluginCatalog, wbPluginDetail, wbPluginUpdateCheck } from "./wb-plugin-market.js";
|
|
20
|
+
// T06/Awesome:DSH 社区插件市场(awesome-dsh-plugins)的宿主侧数据层。
|
|
21
|
+
import { wbAwesomeCatalog, wbAwesomeDetail, wbAwesomeUpdateCheck } from "./wb-awesome-market.js";
|
|
22
|
+
import { readState as readInstallState, record as recordInstallState, forget as forgetInstallState } from "./wb-install-state.js";
|
|
23
|
+
import { applyTokens } from "./wb-auth-rules.js";
|
|
24
|
+
import {
|
|
25
|
+
authorizationOverview as oauthOverview,
|
|
26
|
+
authorizationStatus as oauthStatus,
|
|
27
|
+
beginAuthorization as oauthBegin,
|
|
28
|
+
cancelAuthorization as oauthCancel,
|
|
29
|
+
clearTokenRecord as oauthClearToken,
|
|
30
|
+
ensureAccessToken as oauthEnsureToken,
|
|
31
|
+
saveManualToken as oauthSaveManualToken,
|
|
32
|
+
submitCallback as oauthSubmitCallback,
|
|
33
|
+
} from "./mcp-oauth.js";
|
|
17
34
|
import { hasTeamBridge, installExpertSkills, listExpertRecent, listExpertSkills, prepareExpertPersona, readExpertPresets, recordExpertRecent, removeExpertPreset, uninstallExpertSkills, uninstallTeamMemberPersonas, wbExpertCatalog, wbExpertDetail, writeExpertPreset } from "./expert-market.js";
|
|
18
35
|
import { attachPersona, detachPersona, listPersonas, reconcilePersonas } from "./session-persona.js";
|
|
19
36
|
// OPT-19:clearTextCache 一并引入——toolTailor 的可选动作要回收解压文本缓存。
|
|
@@ -64,6 +81,118 @@ const ROOT_INCLUDE_ID = "include";
|
|
|
64
81
|
const REDACTED_VALUE = "__DSH_MCP_REDACTED__";
|
|
65
82
|
const REVEAL_REVISION_KEY = randomBytes(32);
|
|
66
83
|
const patchWriteQueues = new WeakMap();
|
|
84
|
+
// ── R8:连接器 MCP 的 OAuth 授权接线 ─────────────────────────────────────
|
|
85
|
+
// 所有网络与落盘动作都在 lib/mcp-oauth.js;本文件只做三件事:
|
|
86
|
+
// ① 从 mcpServers 里取出某个 server 的 url / headers(授权的靶点);
|
|
87
|
+
// ② 安装前把已授权 server 的 Bearer token 注进 headers(否则探测永远 401);
|
|
88
|
+
// ③ 把授权会话包装成 RPC 结果给面板轮询。
|
|
89
|
+
const OAUTH_HTTP_TRANSPORT = new Set(["streamable-http", "streamablehttp", "http"]);
|
|
90
|
+
|
|
91
|
+
/** 归一化一个 mcp.json server 条目:transport 可能是对象(nested)也可能是裸字段。 */
|
|
92
|
+
function nestedServerSpec(rawServer) {
|
|
93
|
+
const nested = rawServer && typeof rawServer.transport === "object" ? rawServer.transport : rawServer || {};
|
|
94
|
+
const transport = String(nested?.type ?? rawServer?.type ?? "").toLowerCase();
|
|
95
|
+
const url = String(nested?.url ?? rawServer?.url ?? "").trim();
|
|
96
|
+
const headers = {};
|
|
97
|
+
for (const [key, value] of Object.entries(nested?.headers || {})) headers[String(key).toLowerCase()] = String(value);
|
|
98
|
+
return { nested, transport, url, headers };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 只有带 url 的 HTTP 族 server 才可能要走 OAuth;stdio 型永远不需要。 */
|
|
102
|
+
function isOAuthCandidate(spec) {
|
|
103
|
+
return Boolean(spec.url) && OAUTH_HTTP_TRANSPORT.has(spec.transport || "streamable-http");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** 保留原 headers,覆盖 authorization —— 这是 mcp-client 唯一能带出去的凭据通道。 */
|
|
107
|
+
function withBearerHeader(rawServer, token) {
|
|
108
|
+
const spec = nestedServerSpec(rawServer);
|
|
109
|
+
const headers = { ...spec.headers, authorization: `Bearer ${token}` };
|
|
110
|
+
if (rawServer && typeof rawServer.transport === "object") return { ...rawServer, transport: { ...rawServer.transport, headers } };
|
|
111
|
+
return { ...rawServer, headers };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 安装前把已落盘的 access_token 注进对应 server 的 headers。
|
|
116
|
+
* 读不到 / 过期且刷不动就原样返回,交给 probe 去判定「需要授权」——
|
|
117
|
+
* 绝不因为令牌库有问题而阻断一次本来能成功的安装。
|
|
118
|
+
*/
|
|
119
|
+
async function injectStoredOAuthTokens(mcpServers) {
|
|
120
|
+
if (!mcpServers || typeof mcpServers !== "object") return mcpServers;
|
|
121
|
+
const out = {};
|
|
122
|
+
for (const [serverName, rawServer] of Object.entries(mcpServers)) {
|
|
123
|
+
const spec = nestedServerSpec(rawServer);
|
|
124
|
+
if (!isOAuthCandidate(spec) || spec.headers.authorization) {
|
|
125
|
+
out[serverName] = rawServer;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
let token = "";
|
|
129
|
+
try {
|
|
130
|
+
token = String((await oauthEnsureToken(spec.url)) || "");
|
|
131
|
+
} catch {
|
|
132
|
+
token = "";
|
|
133
|
+
}
|
|
134
|
+
out[serverName] = token ? withBearerHeader(rawServer, token) : rawServer;
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** 解析某连接器里某 server 的授权靶点(url + 现有 headers)。 */
|
|
140
|
+
async function resolveOAuthTarget(connectorId, serverName) {
|
|
141
|
+
const preview = await wbConnectorPreview(connectorId);
|
|
142
|
+
const servers = preview?.mcpServers && typeof preview.mcpServers === "object" ? preview.mcpServers : {};
|
|
143
|
+
const name = String(serverName || "").trim() || Object.keys(servers)[0] || "";
|
|
144
|
+
const rawServer = servers[name];
|
|
145
|
+
if (!rawServer) throw new Error(`连接器 ${connectorId} 里没有 server「${name || "(空)"}」`);
|
|
146
|
+
const spec = nestedServerSpec(rawServer);
|
|
147
|
+
if (!spec.url) throw new Error(`server「${name}」没有可授权的 URL(stdio 型不需要 OAuth 授权)`);
|
|
148
|
+
return { serverName: name, url: spec.url, headers: spec.headers, transport: spec.transport };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** 宿主 shell 服务的几种可能落点,逐个试;拿不到就返回 undefined 走系统兜底。 */
|
|
152
|
+
function resolveShellService(ctx) {
|
|
153
|
+
if (typeof ctx?.get !== "function") return undefined;
|
|
154
|
+
for (const id of ["shell", "electron", "appShell", "desktop"]) {
|
|
155
|
+
try {
|
|
156
|
+
const value = ctx.get(id);
|
|
157
|
+
if (!value) continue;
|
|
158
|
+
if (typeof value.openExternal === "function") return value;
|
|
159
|
+
if (typeof value?.shell?.openExternal === "function") return value.shell;
|
|
160
|
+
} catch {
|
|
161
|
+
/* 该服务不存在,继续试下一个 */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** 用宿主能力打开授权页;没有 shell 就退回系统默认浏览器。 */
|
|
168
|
+
async function openExternalUrl(ctx, url) {
|
|
169
|
+
const target = String(url || "").trim();
|
|
170
|
+
if (!target) return { opened: false, reason: "empty-url" };
|
|
171
|
+
const shell = resolveShellService(ctx);
|
|
172
|
+
if (shell && typeof shell.openExternal === "function") {
|
|
173
|
+
try {
|
|
174
|
+
await shell.openExternal(target);
|
|
175
|
+
return { opened: true, via: "shell" };
|
|
176
|
+
} catch {
|
|
177
|
+
/* 落到系统兜底 */
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const { spawn } = await import("node:child_process");
|
|
182
|
+
if (process.platform === "win32") {
|
|
183
|
+
// 绝不能用 `cmd /c start "$url"`:Node 只在参数含空格时才给参数加引号,而 URL 不含空格,
|
|
184
|
+
// cmd 于是把 URL 里的 & 当成命令分隔符,授权地址被截断成 `?response_type=code`,
|
|
185
|
+
// 服务器随后回 `invalid_request: client_id is required`。
|
|
186
|
+
// rundll32 走 ShellExecute 且参数直传,不经 cmd 解析。
|
|
187
|
+
spawn("rundll32", ["url.dll,FileProtocolHandler", target], { detached: true, stdio: "ignore" }).unref();
|
|
188
|
+
} else {
|
|
189
|
+
spawn(process.platform === "darwin" ? "open" : "xdg-open", [target], { detached: true, stdio: "ignore" }).unref();
|
|
190
|
+
}
|
|
191
|
+
return { opened: true, via: "spawn" };
|
|
192
|
+
} catch (error) {
|
|
193
|
+
return { opened: false, reason: String(error?.message || error) };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
67
196
|
const PHASE_TEXT = { 0: "waiting", 1: "loading", 2: "connected", 3: "failed", 4: "stopped", 5: "unloading" };
|
|
68
197
|
|
|
69
198
|
// mcp-client 至今不暴露任何可订阅的状态面(只导出 name/inject/Config/apply,无事件、无服务),
|
|
@@ -860,7 +989,7 @@ async function listMcpServers(ctx) {
|
|
|
860
989
|
let McpManagerGateway = (() => {
|
|
861
990
|
let _classSuper = TypertRemoteService;
|
|
862
991
|
let _instanceExtraInitializers = [];
|
|
863
|
-
let _list_decorators, _status_decorators, _enable_decorators, _disable_decorators, _reconnect_decorators, _tools_decorators, _builtins_decorators, _installBuiltins_decorators, _add_decorators, _update_decorators, _remove_decorators, _reveal_decorators, _previewImport_decorators, _importJson_decorators, _listWorkspaces_decorators, _getWorkspaceView_decorators, _addWorkspaceServer_decorators, _updateWorkspaceServer_decorators, _removeWorkspaceServer_decorators, _setWorkspaceExclude_decorators, _installWorkspaceBuiltins_decorators, _previewWorkspaceImport_decorators, _importWorkspaceJson_decorators, _revealWorkspaceServer_decorators, _projectConnections_decorators, _reconnectWorkspaceServer_decorators, _updateHint_decorators, _whatsNew_decorators, _scanImportSources_decorators, _previewImportSource_decorators, _importSource_decorators, _toolTailor_decorators, _wbConnectorCatalog_decorators, _wbConnectorCachedCount_decorators, _wbConnectorPreview_decorators, _wbConnectorInstall_decorators, _wbSkillCatalog_decorators, _wbSkillDetail_decorators, _wbSkillInstall_decorators, _wbSkillUninstall_decorators, _wbSkillInstalled_decorators;
|
|
992
|
+
let _list_decorators, _status_decorators, _enable_decorators, _disable_decorators, _reconnect_decorators, _tools_decorators, _builtins_decorators, _installBuiltins_decorators, _add_decorators, _update_decorators, _remove_decorators, _reveal_decorators, _previewImport_decorators, _importJson_decorators, _listWorkspaces_decorators, _getWorkspaceView_decorators, _addWorkspaceServer_decorators, _updateWorkspaceServer_decorators, _removeWorkspaceServer_decorators, _setWorkspaceExclude_decorators, _installWorkspaceBuiltins_decorators, _previewWorkspaceImport_decorators, _importWorkspaceJson_decorators, _revealWorkspaceServer_decorators, _projectConnections_decorators, _reconnectWorkspaceServer_decorators, _updateHint_decorators, _whatsNew_decorators, _scanImportSources_decorators, _previewImportSource_decorators, _importSource_decorators, _toolTailor_decorators, _wbConnectorCatalog_decorators, _wbConnectorCachedCount_decorators, _wbConnectorPreview_decorators, _wbConnectorInstall_decorators, _wbOAuthBegin_decorators, _wbOAuthPoll_decorators, _wbOAuthCancel_decorators, _wbOAuthToken_decorators, _wbOAuthOverview_decorators, _wbOAuthOpen_decorators, _wbSkillCatalog_decorators, _wbSkillDetail_decorators, _wbSkillInstall_decorators, _wbSkillUninstall_decorators, _wbSkillInstalled_decorators, _wbMarketRegistry_decorators, _wbMarketRegistryUpsert_decorators, _wbPluginCatalog_decorators, _wbPluginDetail_decorators, _wbInstallState_decorators, _wbInstallStateRecord_decorators, _wbInstallStateForget_decorators, _wbPluginUpdateCheck_decorators, _wbAwesomeCatalog_decorators, _wbAwesomeDetail_decorators, _wbAwesomeUpdateCheck_decorators;
|
|
864
993
|
return class McpManagerGateway extends _classSuper {
|
|
865
994
|
static {
|
|
866
995
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
@@ -936,6 +1065,18 @@ let McpManagerGateway = (() => {
|
|
|
936
1065
|
__esDecorate(this, null, _wbConnectorPreview_decorators, { kind: "method", name: "wbConnectorPreview", static: false, private: false, access: { has: (obj) => "wbConnectorPreview" in obj, get: (obj) => obj.wbConnectorPreview }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
937
1066
|
_wbConnectorInstall_decorators = [Remote("wbConnectorInstall")];
|
|
938
1067
|
__esDecorate(this, null, _wbConnectorInstall_decorators, { kind: "method", name: "wbConnectorInstall", static: false, private: false, access: { has: (obj) => "wbConnectorInstall" in obj, get: (obj) => obj.wbConnectorInstall }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1068
|
+
_wbOAuthBegin_decorators = [Remote("wbOAuthBegin")];
|
|
1069
|
+
__esDecorate(this, null, _wbOAuthBegin_decorators, { kind: "method", name: "wbOAuthBegin", static: false, private: false, access: { has: (obj) => "wbOAuthBegin" in obj, get: (obj) => obj.wbOAuthBegin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1070
|
+
_wbOAuthPoll_decorators = [Remote("wbOAuthPoll")];
|
|
1071
|
+
__esDecorate(this, null, _wbOAuthPoll_decorators, { kind: "method", name: "wbOAuthPoll", static: false, private: false, access: { has: (obj) => "wbOAuthPoll" in obj, get: (obj) => obj.wbOAuthPoll }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1072
|
+
_wbOAuthCancel_decorators = [Remote("wbOAuthCancel")];
|
|
1073
|
+
__esDecorate(this, null, _wbOAuthCancel_decorators, { kind: "method", name: "wbOAuthCancel", static: false, private: false, access: { has: (obj) => "wbOAuthCancel" in obj, get: (obj) => obj.wbOAuthCancel }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1074
|
+
_wbOAuthToken_decorators = [Remote("wbOAuthToken")];
|
|
1075
|
+
__esDecorate(this, null, _wbOAuthToken_decorators, { kind: "method", name: "wbOAuthToken", static: false, private: false, access: { has: (obj) => "wbOAuthToken" in obj, get: (obj) => obj.wbOAuthToken }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1076
|
+
_wbOAuthOverview_decorators = [Remote("wbOAuthOverview")];
|
|
1077
|
+
__esDecorate(this, null, _wbOAuthOverview_decorators, { kind: "method", name: "wbOAuthOverview", static: false, private: false, access: { has: (obj) => "wbOAuthOverview" in obj, get: (obj) => obj.wbOAuthOverview }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1078
|
+
_wbOAuthOpen_decorators = [Remote("wbOAuthOpen")];
|
|
1079
|
+
__esDecorate(this, null, _wbOAuthOpen_decorators, { kind: "method", name: "wbOAuthOpen", static: false, private: false, access: { has: (obj) => "wbOAuthOpen" in obj, get: (obj) => obj.wbOAuthOpen }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
939
1080
|
_wbSkillCatalog_decorators = [Remote("wbSkillCatalog")];
|
|
940
1081
|
__esDecorate(this, null, _wbSkillCatalog_decorators, { kind: "method", name: "wbSkillCatalog", static: false, private: false, access: { has: (obj) => "wbSkillCatalog" in obj, get: (obj) => obj.wbSkillCatalog }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
941
1082
|
_wbSkillDetail_decorators = [Remote("wbSkillDetail")];
|
|
@@ -946,6 +1087,32 @@ let McpManagerGateway = (() => {
|
|
|
946
1087
|
__esDecorate(this, null, _wbSkillUninstall_decorators, { kind: "method", name: "wbSkillUninstall", static: false, private: false, access: { has: (obj) => "wbSkillUninstall" in obj, get: (obj) => obj.wbSkillUninstall }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
947
1088
|
_wbSkillInstalled_decorators = [Remote("wbSkillInstalled")];
|
|
948
1089
|
__esDecorate(this, null, _wbSkillInstalled_decorators, { kind: "method", name: "wbSkillInstalled", static: false, private: false, access: { has: (obj) => "wbSkillInstalled" in obj, get: (obj) => obj.wbSkillInstalled }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1090
|
+
// T03/R4:多市场注册表(读 / 增改)。
|
|
1091
|
+
_wbMarketRegistry_decorators = [Remote("wbMarketRegistry")];
|
|
1092
|
+
__esDecorate(this, null, _wbMarketRegistry_decorators, { kind: "method", name: "wbMarketRegistry", static: false, private: false, access: { has: (obj) => "wbMarketRegistry" in obj, get: (obj) => obj.wbMarketRegistry }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1093
|
+
_wbMarketRegistryUpsert_decorators = [Remote("wbMarketRegistryUpsert")];
|
|
1094
|
+
__esDecorate(this, null, _wbMarketRegistryUpsert_decorators, { kind: "method", name: "wbMarketRegistryUpsert", static: false, private: false, access: { has: (obj) => "wbMarketRegistryUpsert" in obj, get: (obj) => obj.wbMarketRegistryUpsert }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1095
|
+
// T03/R5:codebuddy 插件市场(列表 / 详情)。
|
|
1096
|
+
_wbPluginCatalog_decorators = [Remote("wbPluginCatalog")];
|
|
1097
|
+
__esDecorate(this, null, _wbPluginCatalog_decorators, { kind: "method", name: "wbPluginCatalog", static: false, private: false, access: { has: (obj) => "wbPluginCatalog" in obj, get: (obj) => obj.wbPluginCatalog }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1098
|
+
_wbPluginDetail_decorators = [Remote("wbPluginDetail")];
|
|
1099
|
+
__esDecorate(this, null, _wbPluginDetail_decorators, { kind: "method", name: "wbPluginDetail", static: false, private: false, access: { has: (obj) => "wbPluginDetail" in obj, get: (obj) => obj.wbPluginDetail }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1100
|
+
// T04/R8:安装状态登记(connector/skill/plugin 三类安装的统一账本)。
|
|
1101
|
+
_wbInstallState_decorators = [Remote("wbInstallState")];
|
|
1102
|
+
__esDecorate(this, null, _wbInstallState_decorators, { kind: "method", name: "wbInstallState", static: false, private: false, access: { has: (obj) => "wbInstallState" in obj, get: (obj) => obj.wbInstallState }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1103
|
+
_wbInstallStateRecord_decorators = [Remote("wbInstallStateRecord")];
|
|
1104
|
+
__esDecorate(this, null, _wbInstallStateRecord_decorators, { kind: "method", name: "wbInstallStateRecord", static: false, private: false, access: { has: (obj) => "wbInstallStateRecord" in obj, get: (obj) => obj.wbInstallStateRecord }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1105
|
+
_wbInstallStateForget_decorators = [Remote("wbInstallStateForget")];
|
|
1106
|
+
__esDecorate(this, null, _wbInstallStateForget_decorators, { kind: "method", name: "wbInstallStateForget", static: false, private: false, access: { has: (obj) => "wbInstallStateForget" in obj, get: (obj) => obj.wbInstallStateForget }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1107
|
+
_wbPluginUpdateCheck_decorators = [Remote("wbPluginUpdateCheck")];
|
|
1108
|
+
__esDecorate(this, null, _wbPluginUpdateCheck_decorators, { kind: "method", name: "wbPluginUpdateCheck", static: false, private: false, access: { has: (obj) => "wbPluginUpdateCheck" in obj, get: (obj) => obj.wbPluginUpdateCheck }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1109
|
+
// T06/Awesome:DSH 社区插件市场(awesome-dsh-plugins)。
|
|
1110
|
+
_wbAwesomeCatalog_decorators = [Remote("wbAwesomeCatalog")];
|
|
1111
|
+
__esDecorate(this, null, _wbAwesomeCatalog_decorators, { kind: "method", name: "wbAwesomeCatalog", static: false, private: false, access: { has: (obj) => "wbAwesomeCatalog" in obj, get: (obj) => obj.wbAwesomeCatalog }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1112
|
+
_wbAwesomeDetail_decorators = [Remote("wbAwesomeDetail")];
|
|
1113
|
+
__esDecorate(this, null, _wbAwesomeDetail_decorators, { kind: "method", name: "wbAwesomeDetail", static: false, private: false, access: { has: (obj) => "wbAwesomeDetail" in obj, get: (obj) => obj.wbAwesomeDetail }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1114
|
+
_wbAwesomeUpdateCheck_decorators = [Remote("wbAwesomeUpdateCheck")];
|
|
1115
|
+
__esDecorate(this, null, _wbAwesomeUpdateCheck_decorators, { kind: "method", name: "wbAwesomeUpdateCheck", static: false, private: false, access: { has: (obj) => "wbAwesomeUpdateCheck" in obj, get: (obj) => obj.wbAwesomeUpdateCheck }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
949
1116
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
950
1117
|
}
|
|
951
1118
|
static inject = ["loader", "tools"];
|
|
@@ -1468,11 +1635,126 @@ let McpManagerGateway = (() => {
|
|
|
1468
1635
|
if (!connectorId) throw new Error("缺少 connectorId");
|
|
1469
1636
|
const mode = payload?.mode === "replace" ? "replace" : "merge";
|
|
1470
1637
|
const wsPath = payload?.wsPath ? canonicalWorkspacePath(String(payload.wsPath)) : "";
|
|
1638
|
+
const tokens = payload?.tokens && typeof payload.tokens === "object" ? payload.tokens : {};
|
|
1471
1639
|
const preview = await wbConnectorPreview(connectorId);
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1640
|
+
// T04/R7:把面板收集到的 token 填进 ${...} 占位符。只填用户明确提供的槽位,
|
|
1641
|
+
// 未填的保持原样并把槽位名回传面板(missingSlots)——绝不代填、绝不猜值。
|
|
1642
|
+
const { mcpServers, missingSlots } = applyTokens(preview.mcpServers, tokens);
|
|
1643
|
+
// R8:先把已授权的 OAuth token 注进 headers 再探测——否则授权过的连接器每次重装
|
|
1644
|
+
// 都会被判成「需要授权」,用户得反复点授权。
|
|
1645
|
+
const authorized = await injectStoredOAuthTokens(mcpServers);
|
|
1646
|
+
// 装前 401 探测:索引 auth_mode 会漏标鉴权(tec-do 裸 URL 装完必 401)。断言从
|
|
1647
|
+
// 「任一不就整体中止」改为「回传需授权清单」:面板据此弹浏览器授权页,授权完成
|
|
1648
|
+
// 后重装即可,绝不再抛「DSH 无法自动注入」把用户挡在门外。
|
|
1649
|
+
const probe = await probeMcpServersAuthorization(authorized);
|
|
1650
|
+
if (probe?.needsAuth?.length) {
|
|
1651
|
+
return {
|
|
1652
|
+
ok: false,
|
|
1653
|
+
connectorId,
|
|
1654
|
+
missingSlots,
|
|
1655
|
+
needsAuth: probe.needsAuth,
|
|
1656
|
+
error: `连接器需要授权:${probe.needsAuth.map((item) => item.serverName).join("、")}`,
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
const json = { mcpServers: authorized };
|
|
1660
|
+
const result = wsPath
|
|
1661
|
+
? await applyWorkspaceImport(this.ctx, wsPath, admittedImport(json), mode)
|
|
1662
|
+
: await applyManagedImport(this.ctx, admittedImport(json), mode);
|
|
1663
|
+
// T04/R8:装成功后登记本地状态(供「已安装」全量筛选与来源锁)。登记失败只记
|
|
1664
|
+
// 日志、绝不影响安装结果——状态文件坏了不该让用户白装一次。
|
|
1665
|
+
try {
|
|
1666
|
+
await recordInstallState({
|
|
1667
|
+
kind: "connector",
|
|
1668
|
+
key: connectorId,
|
|
1669
|
+
name: connectorId,
|
|
1670
|
+
scope: wsPath ? "workspace" : "managed",
|
|
1671
|
+
installPath: wsPath || "",
|
|
1672
|
+
source: { kind: "marketplace", marketplace: "workbuddy-connectors" },
|
|
1673
|
+
});
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
this.ctx.logger?.warn?.(`wb-market: 连接器 ${connectorId} 安装状态登记失败(不影响安装结果):${error?.message || error}`);
|
|
1676
|
+
}
|
|
1677
|
+
return { ...result, missingSlots };
|
|
1678
|
+
}
|
|
1679
|
+
// ── R8:连接器 MCP 的浏览器授权(RFC 9728 / 8414 / 7591 / 7636)──────────
|
|
1680
|
+
// 面板只负责展示与点击:安装前探测到 401 → 这里拿到 authorizeUrl 交给系统浏览器,
|
|
1681
|
+
// 浏览器回来后面板轮询 wbOAuthPoll,token 落在 ~/.dsh/armory-oauth/。
|
|
1682
|
+
async wbOAuthBegin(payload) {
|
|
1683
|
+
const connectorId = String(payload?.connectorId || "").trim();
|
|
1684
|
+
if (!connectorId) throw new Error("缺少 connectorId");
|
|
1685
|
+
const target = await resolveOAuthTarget(connectorId, payload?.serverName);
|
|
1686
|
+
const started = await oauthBegin({
|
|
1687
|
+
serverName: target.serverName,
|
|
1688
|
+
serverUrl: target.url,
|
|
1689
|
+
headers: target.headers,
|
|
1690
|
+
clientName: "DSH Armory",
|
|
1691
|
+
});
|
|
1692
|
+
if (!started?.ok) {
|
|
1693
|
+
return {
|
|
1694
|
+
ok: false,
|
|
1695
|
+
connectorId,
|
|
1696
|
+
serverName: target.serverName,
|
|
1697
|
+
error: started?.error || "授权流程启动失败",
|
|
1698
|
+
mode: started?.mode || "",
|
|
1699
|
+
redirectUri: started?.redirectUri || "",
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
// 默认由宿主直接把授权页推给系统浏览器;open:false 留给面板自己渲染链接。
|
|
1703
|
+
const opened = payload?.open === false ? { opened: false } : await openExternalUrl(this.ctx, started.authorizeUrl);
|
|
1704
|
+
return {
|
|
1705
|
+
ok: true,
|
|
1706
|
+
connectorId,
|
|
1707
|
+
serverName: target.serverName,
|
|
1708
|
+
sessionId: started.sessionId,
|
|
1709
|
+
authorizeUrl: started.authorizeUrl,
|
|
1710
|
+
mode: started.mode,
|
|
1711
|
+
redirectUri: started.redirectUri,
|
|
1712
|
+
hint: started.hint || "",
|
|
1713
|
+
opened: opened.opened === true,
|
|
1714
|
+
openVia: opened.via || (opened.opened ? "" : String(opened.reason || "")),
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
async wbOAuthPoll(payload) {
|
|
1718
|
+
const sessionId = String(payload?.sessionId || "").trim();
|
|
1719
|
+
if (!sessionId) throw new Error("缺少 sessionId");
|
|
1720
|
+
const callbackUrl = String(payload?.callbackUrl || "").trim();
|
|
1721
|
+
// 自定义 scheme 回调(如 workbuddy://…)浏览器打不开,面板让用户把整条回调
|
|
1722
|
+
// URL 粘回来,这里顺手换成 code 再去换 token。
|
|
1723
|
+
if (callbackUrl) {
|
|
1724
|
+
const submitted = await oauthSubmitCallback(sessionId, callbackUrl);
|
|
1725
|
+
if (submitted && submitted.ok === false && submitted.status !== "authorized") return submitted;
|
|
1726
|
+
}
|
|
1727
|
+
return oauthStatus(sessionId);
|
|
1728
|
+
}
|
|
1729
|
+
async wbOAuthCancel(payload) {
|
|
1730
|
+
const sessionId = String(payload?.sessionId || "").trim();
|
|
1731
|
+
if (sessionId) oauthCancel(sessionId);
|
|
1732
|
+
return { ok: true };
|
|
1733
|
+
}
|
|
1734
|
+
async wbOAuthToken(payload) {
|
|
1735
|
+
const connectorId = String(payload?.connectorId || "").trim();
|
|
1736
|
+
if (!connectorId) throw new Error("缺少 connectorId");
|
|
1737
|
+
const target = await resolveOAuthTarget(connectorId, payload?.serverName);
|
|
1738
|
+
const token = String(payload?.token || "").trim();
|
|
1739
|
+
if (!token) {
|
|
1740
|
+
// 空 token = 撤销授权(清掉本地记录),面板的「撤销授权」按钮走这条路。
|
|
1741
|
+
await oauthClearToken(target.url);
|
|
1742
|
+
return { ok: true, cleared: true, connectorId, serverName: target.serverName };
|
|
1743
|
+
}
|
|
1744
|
+
const saved = await oauthSaveManualToken({ serverUrl: target.url, serverName: target.serverName, token });
|
|
1745
|
+
return { ok: saved?.ok !== false, connectorId, serverName: target.serverName, ...(saved || {}) };
|
|
1746
|
+
}
|
|
1747
|
+
async wbOAuthOverview(payload) {
|
|
1748
|
+
const connectorId = String(payload?.connectorId || "").trim();
|
|
1749
|
+
if (!connectorId) throw new Error("缺少 connectorId");
|
|
1750
|
+
const target = await resolveOAuthTarget(connectorId, payload?.serverName).catch(() => null);
|
|
1751
|
+
if (!target) return { ok: true, authorized: false, connectorId, serverName: "" };
|
|
1752
|
+
const overview = await oauthOverview(target.url).catch(() => null);
|
|
1753
|
+
return { ok: true, connectorId, serverName: target.serverName, ...(overview || {}) };
|
|
1754
|
+
}
|
|
1755
|
+
async wbOAuthOpen(payload) {
|
|
1756
|
+
const opened = await openExternalUrl(this.ctx, payload?.url);
|
|
1757
|
+
return { ok: opened.opened === true, ...opened };
|
|
1476
1758
|
}
|
|
1477
1759
|
async wbSkillCatalog(payload) {
|
|
1478
1760
|
return wbSkillCatalog(payload || {});
|
|
@@ -1486,6 +1768,22 @@ let McpManagerGateway = (() => {
|
|
|
1486
1768
|
const result = await wbSkillInstall(payload || {}, {
|
|
1487
1769
|
onProgress: (progress) => this.ctx.logger?.debug?.(`wb-market: ${progress.phase} ${progress.index}/${progress.total}`),
|
|
1488
1770
|
});
|
|
1771
|
+
// T04/R8:装成功后登记本地状态。key 取不到就跳过(record 对空 key 会抛错)。
|
|
1772
|
+
const key = result?.name || payload?.slug || payload?.name || "";
|
|
1773
|
+
if (key) {
|
|
1774
|
+
try {
|
|
1775
|
+
await recordInstallState({
|
|
1776
|
+
kind: "skill",
|
|
1777
|
+
key,
|
|
1778
|
+
name: result?.name || payload?.name || key,
|
|
1779
|
+
version: result?.version || "",
|
|
1780
|
+
installPath: result?.dir || "",
|
|
1781
|
+
source: { kind: "marketplace", marketplace: "workbuddy-skills" },
|
|
1782
|
+
});
|
|
1783
|
+
} catch (error) {
|
|
1784
|
+
this.ctx.logger?.warn?.(`wb-market: 技能 ${key} 安装状态登记失败(不影响安装结果):${error?.message || error}`);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1489
1787
|
return result;
|
|
1490
1788
|
}
|
|
1491
1789
|
async wbSkillUninstall(payload) {
|
|
@@ -1494,6 +1792,57 @@ let McpManagerGateway = (() => {
|
|
|
1494
1792
|
async wbSkillInstalled() {
|
|
1495
1793
|
return wbSkillInstalled();
|
|
1496
1794
|
}
|
|
1795
|
+
// ── T03/R4:多市场注册表 ────────────────────────────────────────────
|
|
1796
|
+
// 数据层在 lib/wb-registry.js(T01);这里只做 RPC 转发。读 = 合并投影(内置默认
|
|
1797
|
+
// 永远在场);增改 = upsert 一条后返回新的合并投影。缺 id 时 wb-registry 抛错。
|
|
1798
|
+
async wbMarketRegistry() {
|
|
1799
|
+
return readRegistry();
|
|
1800
|
+
}
|
|
1801
|
+
async wbMarketRegistryUpsert(payload) {
|
|
1802
|
+
return upsertMarketplace(payload || {});
|
|
1803
|
+
}
|
|
1804
|
+
// ── T03/R5:codebuddy 插件市场 ──────────────────────────────────────
|
|
1805
|
+
// 数据层在 lib/wb-plugin-market.js;这里只做 RPC 转发(下载/缓存/解析都在那边)。
|
|
1806
|
+
async wbPluginCatalog() {
|
|
1807
|
+
return wbPluginCatalog();
|
|
1808
|
+
}
|
|
1809
|
+
async wbPluginDetail(payload) {
|
|
1810
|
+
return wbPluginDetail(payload?.name);
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
// ── T04/R8:安装状态账本(connector / skill / plugin 三类安装的统一登记) ──
|
|
1814
|
+
async wbInstallState() {
|
|
1815
|
+
return readInstallState();
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
async wbInstallStateRecord(payload) {
|
|
1819
|
+
if (!payload || typeof payload !== "object") throw new Error("缺少安装状态条目");
|
|
1820
|
+
await recordInstallState(payload);
|
|
1821
|
+
return { note: `已登记安装状态:${payload.kind || "connector"}/${payload.key}` };
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
async wbInstallStateForget(payload) {
|
|
1825
|
+
const kind = String(payload?.kind || "").trim();
|
|
1826
|
+
const key = String(payload?.key || "").trim();
|
|
1827
|
+
if (!kind || !key) throw new Error("缺少 kind 或 key");
|
|
1828
|
+
await forgetInstallState(kind, key);
|
|
1829
|
+
return { note: `已移除安装状态:${kind}/${key}` };
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
async wbPluginUpdateCheck() {
|
|
1833
|
+
return wbPluginUpdateCheck();
|
|
1834
|
+
}
|
|
1835
|
+
// ── T06/Awesome:DSH 社区插件市场(awesome-dsh-plugins)──────────────
|
|
1836
|
+
// 数据层在 lib/wb-awesome-market.js;这里只做 RPC 转发。
|
|
1837
|
+
async wbAwesomeCatalog(payload) {
|
|
1838
|
+
return wbAwesomeCatalog(payload || {});
|
|
1839
|
+
}
|
|
1840
|
+
async wbAwesomeDetail(payload) {
|
|
1841
|
+
return wbAwesomeDetail(payload?.name);
|
|
1842
|
+
}
|
|
1843
|
+
async wbAwesomeUpdateCheck() {
|
|
1844
|
+
return wbAwesomeUpdateCheck();
|
|
1845
|
+
}
|
|
1497
1846
|
// ── WorkBuddy 专家直连(召唤预设 + 捆绑技能)────────────────────────
|
|
1498
1847
|
// 数据与安装逻辑都在 lib/expert-market.js;这里只做 RPC 转发与 patch 写盘收尾。
|
|
1499
1848
|
// 召唤 = 往 profile patch 插入一条独立 @deepseek-ai/dsh-agent-preset 行(persona.prefix
|
|
@@ -1650,4 +1999,4 @@ let McpManagerGateway = (() => {
|
|
|
1650
1999
|
};
|
|
1651
2000
|
})();
|
|
1652
2001
|
|
|
1653
|
-
export { checkForUpdate, McpManagerGateway, McpManagerGateway as default, summarizeWorkspaceRow };
|
|
2002
|
+
export { checkForUpdate, McpManagerGateway, McpManagerGateway as default, summarizeWorkspaceRow };
|