@morehao/dsh-skills-console 0.5.0 → 0.5.2

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/lib/routes.js CHANGED
@@ -73,6 +73,11 @@ function matchRoute(method, pathname) {
73
73
  if (segments[0] === 'skills' && segments.length === 3 && segments[2] === 'files' && method === 'GET') {
74
74
  return { name: 'skill.files', params: { name: decodeURIComponent(segments[1]) } };
75
75
  }
76
+ // 技能介绍(模型生成):GET 只读缓存,POST 才真正调模型
77
+ if (segments[0] === 'skills' && segments.length === 3 && segments[2] === 'intro') {
78
+ if (method === 'GET') return { name: 'skill.intro.get', params: { name: decodeURIComponent(segments[1]) } };
79
+ if (method === 'POST') return { name: 'skill.intro.generate', params: { name: decodeURIComponent(segments[1]) } };
80
+ }
76
81
  if (segments[0] === 'skills' && segments.length === 3 && segments[2] === 'adopt' && method === 'POST') {
77
82
  return { name: 'skill.adopt', params: { name: decodeURIComponent(segments[1]) } };
78
83
  }
@@ -97,6 +102,13 @@ function matchRoute(method, pathname) {
97
102
  if (segments.length === 3 && segments[2] === 'refresh' && method === 'POST') {
98
103
  return { name: 'subscriptions.refresh', params: { id: decodeURIComponent(segments[1]) } };
99
104
  }
105
+ // 订阅候选的介绍:路径由服务端按快照反查,客户端只给订阅 id + 条目名
106
+ if (segments.length === 5 && segments[2] === 'entries' && segments[4] === 'intro') {
107
+ const id = decodeURIComponent(segments[1]);
108
+ const entryName = decodeURIComponent(segments[3]);
109
+ if (method === 'GET') return { name: 'subscription.intro.get', params: { id, name: entryName } };
110
+ if (method === 'POST') return { name: 'subscription.intro.generate', params: { id, name: entryName } };
111
+ }
100
112
  }
101
113
 
102
114
  if (segments[0] === 'updates') {
@@ -129,13 +141,24 @@ export function createRoutes({ service, logger }) {
129
141
  return;
130
142
  }
131
143
 
144
+ // 客户端断开(关抽屉 / 关页面)就取消模型调用:摘要是一次性辅助请求,没必要跑完再丢。
145
+ // 测试里的 fake res 没有 on/off,所以要按能力探测。
146
+ const abort = new AbortController();
147
+ const onClose = () => {
148
+ if (!res.writableEnded) abort.abort();
149
+ };
150
+ if (typeof res.on === 'function') res.on('close', onClose);
151
+
132
152
  try {
133
153
  service.reloadState();
134
154
  const body = method === 'GET' ? {} : await readBody(req);
135
155
  const query = Object.fromEntries(url.searchParams.entries());
136
- const result = await dispatch(route, { body, query, service });
156
+ const result = await dispatch(route, { body, query, service, signal: abort.signal });
157
+ // 生成期间客户端可能已经走了:往已关闭的响应写会抛 ERR_STREAM_WRITE_AFTER_END
158
+ if (res.writableEnded || res.destroyed) return;
137
159
  sendJson(res, 200, result);
138
160
  } catch (error) {
161
+ if (res.writableEnded || res.destroyed) return;
139
162
  if (error instanceof StateReadOnlyError) {
140
163
  sendJson(res, 409, {
141
164
  ok: false,
@@ -152,11 +175,13 @@ export function createRoutes({ service, logger }) {
152
175
  }
153
176
  logger('route.error', { message: error?.message, stack: String(error?.stack || '').slice(0, 800) });
154
177
  sendJson(res, 500, { ok: false, code: 'error.skills.internal', message: error?.message || 'internal error' });
178
+ } finally {
179
+ if (typeof res.off === 'function') res.off('close', onClose);
155
180
  }
156
181
  };
157
182
  }
158
183
 
159
- async function dispatch(route, { body, query, service }) {
184
+ async function dispatch(route, { body, query, service, signal = null }) {
160
185
  switch (route.name) {
161
186
  case 'state': {
162
187
  const state = await service.getState({ cwd: query.cwd || body.cwd, workspace: query.workspace });
@@ -180,6 +205,29 @@ async function dispatch(route, { body, query, service }) {
180
205
  return service.listSkillBody({ name: route.params.name, path: query.path });
181
206
  case 'skill.files':
182
207
  return service.listSkillFiles({ name: route.params.name });
208
+ case 'skill.intro.get':
209
+ return service.getSkillIntro({ name: route.params.name, lang: query.lang });
210
+ case 'subscription.intro.get':
211
+ return service.getRemoteIntro({
212
+ subscriptionId: route.params.id,
213
+ name: route.params.name,
214
+ lang: query.lang,
215
+ });
216
+ case 'subscription.intro.generate':
217
+ return service.generateRemoteIntro({
218
+ subscriptionId: route.params.id,
219
+ name: route.params.name,
220
+ lang: body.lang ?? query.lang,
221
+ force: body.force === true,
222
+ signal,
223
+ });
224
+ case 'skill.intro.generate':
225
+ return service.generateSkillIntro({
226
+ name: route.params.name,
227
+ lang: body.lang ?? query.lang,
228
+ force: body.force === true,
229
+ signal,
230
+ });
183
231
  case 'skill.adopt':
184
232
  return service.adopt({ name: route.params.name });
185
233
  case 'skill.mode':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morehao/dsh-skills-console",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "统一管理本地技能的安装、更新、删除与接入,让同一份技能在多个 Agent 间复用。A skill manager for DeepSeek Harness: install, update, remove, link and toggle agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "morehao <morehao@qq.com>",