dsh-m 0.4.3 → 0.5.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.en.md +12 -0
- package/README.md +12 -0
- package/docs/DESIGN.md +30 -0
- package/lib/cli.js +94 -20
- package/lib/client.js +282 -17
- package/lib/core/community-adapter.js +181 -0
- package/lib/core/community.js +433 -0
- package/lib/core/host-api.js +53 -19
- package/lib/core/httpx.js +38 -0
- package/lib/core/install-guard.js +308 -0
- package/lib/core/market.js +707 -57
- package/lib/core/profile-transaction.js +238 -1
- package/lib/core/registry-controller.js +8 -0
- package/lib/core/registry.js +3 -3
- package/lib/core/settings-compat.js +4 -0
- package/lib/core/toggle.js +6 -1
- package/lib/core/versions.js +149 -25
- package/lib/host.js +2 -0
- package/lib/tools.js +139 -33
- package/package.json +1 -1
- package/registry.json +19 -0
package/lib/tools.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
2
|
import { installTimeoutMs } from './core/env.js';
|
|
3
|
-
import { installFromRegistry, listInstalledWithMeta, listMarket, uninstallPlugin, upgradePlugin, } from './core/market.js';
|
|
3
|
+
import { installFromRegistry, listInstalledWithMeta, listMarket, uninstallPlugin, upgradePlugin, InstallGuardError, } from './core/market.js';
|
|
4
4
|
import { appExitFromContext, scheduleRestart } from './core/restart.js';
|
|
5
5
|
import { togglePlugin as coreTogglePlugin } from './core/toggle.js';
|
|
6
6
|
export const CATEGORY_LABELS = {
|
|
@@ -10,6 +10,38 @@ export const CATEGORY_LABELS = {
|
|
|
10
10
|
search: '搜索',
|
|
11
11
|
other: '其他',
|
|
12
12
|
};
|
|
13
|
+
/** 工具 deadline 对齐(M1 Task 7):search 45s(core 44s)、list/outdated 65s(core 60s + 5s 回包余量)。 */
|
|
14
|
+
const SEARCH_TOOL_TIMEOUT_MS = 45_000;
|
|
15
|
+
const SEARCH_CORE_DEADLINE_MS = 44_000;
|
|
16
|
+
const INSTALLED_TOOL_TIMEOUT_MS = 65_000;
|
|
17
|
+
const INSTALLED_CORE_DEADLINE_MS = 60_000;
|
|
18
|
+
/** 社区开放分类安全 slug(与 host-api 侧同语义) */
|
|
19
|
+
const COMMUNITY_SLUG_RE = /^[a-z0-9-]{1,32}$/;
|
|
20
|
+
/** 社区 summary 四键投影(Task 7 ②):agent 工具输出只带这四个字段。 */
|
|
21
|
+
export function communityToolSummary(c) {
|
|
22
|
+
if (!c)
|
|
23
|
+
return { acceptedCount: null, route: null, status: 'unavailable', version: null };
|
|
24
|
+
const unavailableLike = c.status === 'disabled' || c.status === 'unavailable';
|
|
25
|
+
return {
|
|
26
|
+
acceptedCount: unavailableLike ? null : c.acceptedCount,
|
|
27
|
+
route: unavailableLike ? null : c.route,
|
|
28
|
+
status: c.status,
|
|
29
|
+
version: c.version,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** latestErrorCode → 安全化原因(Task 7 ⑨):不泄露内部细节,按 code 归类。 */
|
|
33
|
+
export function latestErrorCodeReason(code) {
|
|
34
|
+
switch (code) {
|
|
35
|
+
case 'budget-exhausted':
|
|
36
|
+
return '因 GitHub 预算未完成';
|
|
37
|
+
case 'rate-limited':
|
|
38
|
+
return 'GitHub 限流';
|
|
39
|
+
case 'timeout':
|
|
40
|
+
return '检查超时';
|
|
41
|
+
default:
|
|
42
|
+
return '网络错误';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
13
45
|
function cloneJson(value) {
|
|
14
46
|
return JSON.parse(JSON.stringify(value));
|
|
15
47
|
}
|
|
@@ -36,9 +68,10 @@ export function registerTools(ctx, cfg, deps = {}) {
|
|
|
36
68
|
query: { type: 'string', description: 'Main keyword, e.g. 主题 or 搜索. Optional.' },
|
|
37
69
|
category: {
|
|
38
70
|
type: 'string',
|
|
39
|
-
description: `Optional
|
|
71
|
+
description: `Optional category: curated ${Object.keys(CATEGORY_LABELS).join('/')} or any community slug ([a-z0-9-]{1,32}, e.g. theme/memory/git).`,
|
|
40
72
|
},
|
|
41
73
|
limit: { type: 'number', description: 'Cards in this batch. Default all (registry is curated & small).' },
|
|
74
|
+
primary_only: { type: 'boolean', description: '只看主清单(排除社区条目). Optional.' },
|
|
42
75
|
},
|
|
43
76
|
output: {
|
|
44
77
|
schema: { type: 'object', additionalProperties: true },
|
|
@@ -56,19 +89,30 @@ export function registerTools(ctx, cfg, deps = {}) {
|
|
|
56
89
|
title: isError ? '市场搜索失败' : `DSH 市场 · ${meta?.items?.length ?? 0} 条`,
|
|
57
90
|
content: [],
|
|
58
91
|
}),
|
|
59
|
-
timeoutMs:
|
|
60
|
-
async execute(args) {
|
|
61
|
-
const
|
|
92
|
+
timeoutMs: SEARCH_TOOL_TIMEOUT_MS,
|
|
93
|
+
async execute(args, exec) {
|
|
94
|
+
const categoryRaw = typeof args.category === 'string' ? args.category.trim() : '';
|
|
95
|
+
let category = null;
|
|
96
|
+
if (categoryRaw !== '') {
|
|
97
|
+
if (categoryRaw in CATEGORY_LABELS || COMMUNITY_SLUG_RE.test(categoryRaw))
|
|
98
|
+
category = categoryRaw;
|
|
99
|
+
else
|
|
100
|
+
throw new Error(`非法分类: ${categoryRaw}(需精选分类或 [a-z0-9-]{1,32} slug)`);
|
|
101
|
+
}
|
|
62
102
|
const rawLimit = Number(args.limit);
|
|
63
103
|
const limit = Number.isFinite(rawLimit) && rawLimit > 0 ? clamp(rawLimit, 1, 80) : undefined;
|
|
64
|
-
// metadata-only:agent 卡片不需要 latest;与 Host GUI 共用 host namespace
|
|
104
|
+
// metadata-only:agent 卡片不需要 latest;与 Host GUI 共用 host namespace;
|
|
105
|
+
// deadline 44s 是本 waiter 的绝对上限(工具 timeout 45s 先到兜底)
|
|
65
106
|
const result = await m.listMarket(cfg, {
|
|
66
107
|
query: String(args.query || ''),
|
|
67
108
|
category,
|
|
68
109
|
offset: 0,
|
|
69
110
|
limit,
|
|
111
|
+
primaryOnly: args.primary_only === true,
|
|
70
112
|
withLatest: false,
|
|
71
113
|
namespace: 'host',
|
|
114
|
+
deadlineMs: SEARCH_CORE_DEADLINE_MS,
|
|
115
|
+
signal: exec?.signal,
|
|
72
116
|
});
|
|
73
117
|
// 安装标注唯一来源:listMarket 的单次 profile 快照。
|
|
74
118
|
// 状态不完整且存在可被误标的条目时 fail-closed——不把未知安装状态呈现成未安装;
|
|
@@ -81,6 +125,7 @@ export function registerTools(ctx, cfg, deps = {}) {
|
|
|
81
125
|
category,
|
|
82
126
|
total: result.total,
|
|
83
127
|
registry: summaryOf(result.registryState),
|
|
128
|
+
community: communityToolSummary(result.community),
|
|
84
129
|
items: result.items.map((e) => ({
|
|
85
130
|
id: e.id,
|
|
86
131
|
name: e.name,
|
|
@@ -113,11 +158,16 @@ export function registerTools(ctx, cfg, deps = {}) {
|
|
|
113
158
|
title: isError ? '列出失败' : `已装 · ${meta?.items?.length ?? 0} 个`,
|
|
114
159
|
content: [],
|
|
115
160
|
}),
|
|
116
|
-
timeoutMs:
|
|
117
|
-
async execute() {
|
|
118
|
-
const result = await m.listInstalledWithMeta(cfg, {
|
|
161
|
+
timeoutMs: INSTALLED_TOOL_TIMEOUT_MS,
|
|
162
|
+
async execute(_args, exec) {
|
|
163
|
+
const result = await m.listInstalledWithMeta(cfg, {
|
|
164
|
+
namespace: 'host',
|
|
165
|
+
deadlineMs: INSTALLED_CORE_DEADLINE_MS,
|
|
166
|
+
signal: exec?.signal,
|
|
167
|
+
});
|
|
119
168
|
return cloneJson({
|
|
120
169
|
registry: summaryOf(result.registryState),
|
|
170
|
+
community: communityToolSummary(result.community),
|
|
121
171
|
profileDir: result.profileDir,
|
|
122
172
|
others: result.others,
|
|
123
173
|
items: result.items.map((it) => ({
|
|
@@ -128,6 +178,8 @@ export function registerTools(ctx, cfg, deps = {}) {
|
|
|
128
178
|
registryId: it.registryId ?? null,
|
|
129
179
|
latestVersion: it.latestVersion ?? null,
|
|
130
180
|
outdated: it.outdated,
|
|
181
|
+
latestError: it.latestError ?? null,
|
|
182
|
+
latestErrorCode: it.latestErrorCode ?? null,
|
|
131
183
|
})),
|
|
132
184
|
});
|
|
133
185
|
},
|
|
@@ -146,11 +198,12 @@ export function registerTools(ctx, cfg, deps = {}) {
|
|
|
146
198
|
presentationMeta: (_args, value) => ({ kind: 'dshm-install', ...value }),
|
|
147
199
|
},
|
|
148
200
|
presentCall: (args) => ({ card: 'generic', title: `安装 · ${String(args.id || '')}`, kind: 'search', content: [] }),
|
|
149
|
-
presentResult: (_args, { isError, meta }) =>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
201
|
+
presentResult: (_args, { isError, meta }) => {
|
|
202
|
+
const out = meta;
|
|
203
|
+
if (out?.guard === true)
|
|
204
|
+
return { card: 'generic', title: `守卫拦截 · ${out.compensation?.status ?? ''}`, content: [] };
|
|
205
|
+
return { card: 'generic', title: isError ? '安装失败' : `已安装 · ${out?.pkg || ''}`, content: [] };
|
|
206
|
+
},
|
|
154
207
|
timeoutMs: installTimeoutMs() + 60_000,
|
|
155
208
|
async execute(args) {
|
|
156
209
|
const id = String(args.id || '').trim();
|
|
@@ -158,7 +211,16 @@ export function registerTools(ctx, cfg, deps = {}) {
|
|
|
158
211
|
throw new Error('缺少收录 id');
|
|
159
212
|
const version = typeof args.version === 'string' && args.version.trim() ? args.version.trim() : undefined;
|
|
160
213
|
const force = args.force === true;
|
|
161
|
-
|
|
214
|
+
try {
|
|
215
|
+
return cloneJson(await m.installFromRegistry(id, cfg, { version, forceIncompatible: force, namespace: 'host' }));
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
if (err instanceof InstallGuardError) {
|
|
219
|
+
// 结构化 error result(M2 Task 3 ㉑-㉜):不渲染「已安装」,无 force 通道;restartSafe 随结果携带
|
|
220
|
+
return { ok: false, guard: true, kind: err.kind, message: err.message, violations: err.violations, compensation: err.compensation, needsRestart: err.needsRestart, restartSafe: err.restartSafe, ...(err.repairBasis ? { repairBasis: err.repairBasis } : {}) };
|
|
221
|
+
}
|
|
222
|
+
throw err;
|
|
223
|
+
}
|
|
162
224
|
},
|
|
163
225
|
}));
|
|
164
226
|
ctx.tools.register(defineTool({
|
|
@@ -231,11 +293,24 @@ export function registerTools(ctx, cfg, deps = {}) {
|
|
|
231
293
|
presentResult: (_args, { isError, meta }) => {
|
|
232
294
|
const out = meta;
|
|
233
295
|
const n = out?.items?.filter((it) => it.outdated).length ?? 0;
|
|
234
|
-
|
|
296
|
+
const incomplete = out?.incompleteCount ?? 0;
|
|
297
|
+
// 禁止「未完成检查」冒充「全部最新」(Task 7 ⑨)
|
|
298
|
+
const title = isError
|
|
299
|
+
? '检查失败'
|
|
300
|
+
: incomplete > 0
|
|
301
|
+
? `${n} 个可升级(${incomplete} 项检查未完成)`
|
|
302
|
+
: n
|
|
303
|
+
? `${n} 个可升级`
|
|
304
|
+
: '全部最新';
|
|
305
|
+
return { card: 'generic', title, content: [] };
|
|
235
306
|
},
|
|
236
|
-
timeoutMs:
|
|
237
|
-
async execute() {
|
|
238
|
-
const result = await m.listInstalledWithMeta(cfg, {
|
|
307
|
+
timeoutMs: INSTALLED_TOOL_TIMEOUT_MS,
|
|
308
|
+
async execute(_args, exec) {
|
|
309
|
+
const result = await m.listInstalledWithMeta(cfg, {
|
|
310
|
+
namespace: 'host',
|
|
311
|
+
deadlineMs: INSTALLED_CORE_DEADLINE_MS,
|
|
312
|
+
signal: exec?.signal,
|
|
313
|
+
});
|
|
239
314
|
const items = result.items.map((it) => ({
|
|
240
315
|
pkg: it.pkg,
|
|
241
316
|
name: it.name,
|
|
@@ -244,11 +319,16 @@ export function registerTools(ctx, cfg, deps = {}) {
|
|
|
244
319
|
latestVersion: it.latestVersion ?? null,
|
|
245
320
|
latestTag: it.latestTag ?? null,
|
|
246
321
|
outdated: it.outdated,
|
|
322
|
+
latestError: it.latestError ?? null,
|
|
323
|
+
latestErrorCode: it.latestErrorCode ?? null,
|
|
247
324
|
}));
|
|
325
|
+
const incompleteCount = items.filter((it) => it.latestErrorCode !== null).length;
|
|
248
326
|
return cloneJson({
|
|
249
327
|
registry: summaryOf(result.registryState),
|
|
328
|
+
community: communityToolSummary(result.community),
|
|
250
329
|
items,
|
|
251
330
|
outdatedCount: items.filter((it) => it.outdated).length,
|
|
331
|
+
incompleteCount,
|
|
252
332
|
});
|
|
253
333
|
},
|
|
254
334
|
}));
|
|
@@ -257,6 +337,7 @@ export function registerTools(ctx, cfg, deps = {}) {
|
|
|
257
337
|
description: 'Upgrade an installed DSH plugin to the latest version (npm 拉最新精确版 / github 重新锁 HEAD)。pkg 来自 dshm_list 或 dshm_outdated。用户确认升级哪一个之后再调用。After success, tell the user it needs a restart, and offer dshm_restart.',
|
|
258
338
|
parameters: {
|
|
259
339
|
pkg: { type: 'string', required: true, description: '包名 from dshm_list / dshm_outdated' },
|
|
340
|
+
force: { type: 'boolean', description: 'peer 兼容预检不通过且用户已确认风险后置 true(等价 forceIncompatible,跳过 @deepseek-ai/dsh(-*) peer 拦截);守卫拦截(guard)无 force 通道,不受此参数影响。' },
|
|
260
341
|
},
|
|
261
342
|
output: {
|
|
262
343
|
schema: { type: 'object', additionalProperties: true },
|
|
@@ -264,17 +345,27 @@ export function registerTools(ctx, cfg, deps = {}) {
|
|
|
264
345
|
presentationMeta: (_args, value) => ({ kind: 'dshm-upgrade', ...value }),
|
|
265
346
|
},
|
|
266
347
|
presentCall: (args) => ({ card: 'generic', title: `升级 · ${String(args.pkg || '')}`, content: [] }),
|
|
267
|
-
presentResult: (_args, { isError, meta }) =>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
348
|
+
presentResult: (_args, { isError, meta }) => {
|
|
349
|
+
const out = meta;
|
|
350
|
+
if (out?.guard === true)
|
|
351
|
+
return { card: 'generic', title: `守卫拦截 · ${out.compensation?.status ?? ''}`, content: [] };
|
|
352
|
+
return { card: 'generic', title: isError ? '升级失败' : `已升级 · ${out?.pkg || ''}`, content: [] };
|
|
353
|
+
},
|
|
272
354
|
timeoutMs: installTimeoutMs() + 60_000,
|
|
273
355
|
async execute(args) {
|
|
274
356
|
const target = String(args.pkg || '').trim();
|
|
275
357
|
if (!target)
|
|
276
358
|
throw new Error('缺少 pkg');
|
|
277
|
-
|
|
359
|
+
const force = args.force === true;
|
|
360
|
+
try {
|
|
361
|
+
return cloneJson(await m.upgradePlugin(target, cfg, { forceIncompatible: force, namespace: 'host' }));
|
|
362
|
+
}
|
|
363
|
+
catch (err) {
|
|
364
|
+
if (err instanceof InstallGuardError) {
|
|
365
|
+
return { ok: false, guard: true, kind: err.kind, message: err.message, violations: err.violations, compensation: err.compensation, needsRestart: err.needsRestart, restartSafe: err.restartSafe, ...(err.repairBasis ? { repairBasis: err.repairBasis } : {}) };
|
|
366
|
+
}
|
|
367
|
+
throw err;
|
|
368
|
+
}
|
|
278
369
|
},
|
|
279
370
|
}));
|
|
280
371
|
ctx.tools.register(defineTool({
|
|
@@ -363,6 +454,15 @@ function renderList(out) {
|
|
|
363
454
|
].join('\n');
|
|
364
455
|
}
|
|
365
456
|
function renderInstall(out) {
|
|
457
|
+
const guardBlock = out.guard !== true ? null : [
|
|
458
|
+
`⛔ 安装被装后守卫拦截:${out.message ?? ''}`,
|
|
459
|
+
`终态:${out.compensation?.status ?? '—'}(${out.compensation?.note ?? ''})`,
|
|
460
|
+
out.repairBasis ? `修复依据:${out.repairBasis}` : null,
|
|
461
|
+
out.restartSafe === true ? '可以重启 DSH Web。' : '修复后再重启(不要现在一键重启)。',
|
|
462
|
+
'对用户如实说明守卫拦截与终态,不得说「已安装成功」或「已恢复原版本」。',
|
|
463
|
+
].filter(Boolean).join('\n');
|
|
464
|
+
if (out.guard === true)
|
|
465
|
+
return guardBlock;
|
|
366
466
|
const extra = buildsNote(out);
|
|
367
467
|
const compatNote = out.compat
|
|
368
468
|
? '注意:该版本与当前 DSH 运行时 peer 不兼容,已按用户确认强制安装。'
|
|
@@ -388,16 +488,22 @@ function renderUninstall(out) {
|
|
|
388
488
|
}
|
|
389
489
|
function renderOutdated(out) {
|
|
390
490
|
const outdated = (out.items || []).filter((it) => it.outdated);
|
|
491
|
+
const incomplete = (out.items || []).filter((it) => it.latestErrorCode);
|
|
391
492
|
if (!out.items?.length)
|
|
392
493
|
return 'web profile 没有已装插件。';
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
494
|
+
const head = [];
|
|
495
|
+
if (outdated.length) {
|
|
496
|
+
head.push(`${outdated.length}/${out.items.length} 个插件可升级:`, outdated.map((it) => `${it.name} (${it.pkg}):v${it.version} → ${it.latestTag || (it.latestVersion ? `v${it.latestVersion}` : '最新')}`).join('\n'));
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
head.push(`全部 ${out.items.length} 个插件均已是最新版本。`);
|
|
500
|
+
}
|
|
501
|
+
// 禁止「未完成检查」冒充「全部最新」(Task 7 ⑨):按 code 安全化原因列出
|
|
502
|
+
if (incomplete.length) {
|
|
503
|
+
head.push(`${incomplete.length} 项检查未完成:`, incomplete.map((it) => `· ${it.name} (${it.pkg})——${latestErrorCodeReason(it.latestErrorCode)}`).join('\n'));
|
|
504
|
+
}
|
|
505
|
+
head.push('询问用户要升级哪个,确认后调 dshm_upgrade(pkg)。');
|
|
506
|
+
return head.join('\n');
|
|
401
507
|
}
|
|
402
508
|
function renderUpgrade(out) {
|
|
403
509
|
const from = out.fromVersion ? `v${out.fromVersion} → ` : '';
|
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -138,6 +138,25 @@
|
|
|
138
138
|
"github": "iasiv5/dsh-onetree-log",
|
|
139
139
|
"homepage": "https://github.com/iasiv5/dsh-onetree-log"
|
|
140
140
|
},
|
|
141
|
+
{
|
|
142
|
+
"id": "dsh-quota-watch",
|
|
143
|
+
"name": "DSH Quota Watch",
|
|
144
|
+
"description": "侧栏额度监控卡片:显示 GLM Coding Plan 窗口与 Copilot 额度快照,凭据宿主解析,不留存历史;已适配 0.1.7-rc.2,详见仓库。",
|
|
145
|
+
"category": "tools",
|
|
146
|
+
"tags": [
|
|
147
|
+
"配额",
|
|
148
|
+
"监控",
|
|
149
|
+
"GLM",
|
|
150
|
+
"Copilot"
|
|
151
|
+
],
|
|
152
|
+
"verified": [
|
|
153
|
+
"0.1.7-rc.2"
|
|
154
|
+
],
|
|
155
|
+
"source": "npm",
|
|
156
|
+
"npm": "@iasiv5/dsh-quota-watch",
|
|
157
|
+
"github": "iasiv5/dsh-quota-watch",
|
|
158
|
+
"homepage": "https://github.com/iasiv5/dsh-quota-watch"
|
|
159
|
+
},
|
|
141
160
|
{
|
|
142
161
|
"id": "dsh-better-sidebar",
|
|
143
162
|
"name": "DSH Better Sidebar",
|