@sparkelf/dsh-client-ui-skill-center 0.2.0-rc.9 → 0.2.1-alpha.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/client.js CHANGED
@@ -8,7 +8,7 @@ window.__ModuleLoader__.load({
8
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
10
  //#region \0dsh-css:/root/projects/deepseek-harness-plus/packages/plus/ui-skill-center/src/client/SkillCenterPage.module.css.mjs
11
- const css = ".DGiSta_page{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);flex-direction:column;align-items:center;gap:32px;padding:28px clamp(24px,4vw,48px) 48px;display:flex;overflow:auto}.DGiSta_page>*{width:100%;max-width:900px}.DGiSta_pageHead{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.DGiSta_pageTitle{margin:0;font-size:20px;font-weight:500;line-height:28px}.DGiSta_pageIntro{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px;line-height:20px}.DGiSta_toolbar{justify-content:flex-end;align-items:center;gap:8px;display:flex}.DGiSta_button{border:.5px solid var(--dsw-alias-border-l3);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:0 12px;font-size:13px}.DGiSta_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.DGiSta_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_primaryButton{background:var(--dsw-alias-brand-primary);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:8px;padding:0 14px;font-size:13px}.DGiSta_search{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);width:100%;height:36px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:10px;padding:0 12px;font-size:13px}.DGiSta_search::placeholder{color:var(--dsw-alias-label-tertiary)}.DGiSta_group{margin-top:32px}.DGiSta_groupTitle{align-items:baseline;gap:8px;margin:0 0 4px;font-size:15px;font-weight:500;line-height:22px;display:flex}.DGiSta_groupCount{color:var(--dsw-alias-label-tertiary);font-size:13px;font-weight:400}.DGiSta_groupHint{color:var(--dsw-alias-label-caption);font-size:12px;font-weight:400}.DGiSta_cards{margin:0;padding:0;list-style:none}.DGiSta_card{border-radius:12px;min-width:0;margin:0 -8px}.DGiSta_card:hover{background:var(--dsw-alias-interactive-bg-hover)}.DGiSta_cardHead{align-items:center;gap:14px;padding:8px;display:flex}.DGiSta_cardIcon{border:.5px solid var(--dsw-alias-border-l3);width:48px;height:48px;color:var(--dsw-alias-label-secondary);border-radius:10px;flex:none;justify-content:center;align-items:center;display:inline-flex}.DGiSta_cardMain{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.DGiSta_cardTitle{text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:20px;overflow:hidden}.DGiSta_cardDesc{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;line-height:18px;display:-webkit-box;overflow:hidden}.DGiSta_cardWhenToUse{color:var(--dsw-alias-label-caption);font-size:12px;line-height:18px}.DGiSta_cardMarks{flex-wrap:wrap;gap:6px;margin-top:2px;display:flex}.DGiSta_mark{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-tertiary);border-radius:4px;padding:1px 6px;font-size:11px;line-height:16px}.DGiSta_markOn{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.DGiSta_markOff{border-color:var(--dsw-alias-border-l4);color:var(--dsw-alias-label-caption)}.DGiSta_markDanger{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.DGiSta_cardEnd{flex:none;align-items:center;gap:8px;display:flex}.DGiSta_switch{background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:10px;width:36px;height:20px;transition:background .12s;position:relative}.DGiSta_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_switchOn{background:var(--dsw-alias-state-success-primary)}.DGiSta_switch:after{content:\"\";background:var(--dsw-alias-bg-layer-1);border-radius:50%;width:16px;height:16px;transition:transform .12s;position:absolute;top:2px;left:2px}.DGiSta_switchOn:after{transform:translate(16px)}.DGiSta_form{border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;flex-direction:column;gap:12px;padding:16px;display:flex}.DGiSta_field{flex-direction:column;gap:4px;display:flex}.DGiSta_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.DGiSta_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);height:32px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:8px;padding:0 10px;font-size:13px}.DGiSta_textarea{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);min-height:80px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-markdown-code-block-small);resize:vertical;background:0 0;border-radius:8px;padding:8px 10px;font-size:12px}.DGiSta_formActions{justify-content:flex-end;gap:8px;display:flex}.DGiSta_status{text-align:center;color:var(--dsw-alias-label-tertiary);padding:32px 0;font-size:13px}.DGiSta_statusError{color:var(--dsw-alias-state-error-primary)}";
11
+ const css = ".DGiSta_page{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);flex-direction:column;align-items:center;gap:32px;padding:28px clamp(24px,4vw,48px) 48px;display:flex;overflow:auto}.DGiSta_page>*{width:100%;max-width:960px}.DGiSta_pageHead{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.DGiSta_pageTitle{margin:0;font-size:20px;font-weight:500;line-height:28px}.DGiSta_pageIntro{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px;line-height:20px}.DGiSta_toolbar{justify-content:flex-end;align-items:center;gap:16px;display:flex}.DGiSta_iconButton{width:28px;height:28px;color:var(--dsw-alias-label-caption);cursor:pointer;background:0 0;border:0;border-radius:28px;justify-content:center;align-items:center;display:inline-flex}.DGiSta_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.DGiSta_iconButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_iconWrap{display:inline-flex}.DGiSta_button{border:.5px solid var(--dsw-alias-border-l3);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:0 12px;font-size:13px}.DGiSta_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.DGiSta_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_search{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);width:100%;height:36px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:10px;padding:0 12px;font-size:13px}.DGiSta_search::placeholder{color:var(--dsw-alias-label-tertiary)}.DGiSta_group{margin-top:32px}.DGiSta_groupTitle{align-items:baseline;gap:8px;margin:0 0 4px;font-size:15px;font-weight:500;line-height:22px;display:flex}.DGiSta_groupCount{color:var(--dsw-alias-label-tertiary);font-size:13px;font-weight:400}.DGiSta_groupHint{color:var(--dsw-alias-label-caption);font-size:12px;font-weight:400}.DGiSta_cards{margin:0;padding:0;list-style:none}.DGiSta_card{border-radius:12px;min-width:0;margin:0 -8px}.DGiSta_card:hover{background:var(--dsw-alias-interactive-bg-hover)}.DGiSta_cardHead{align-items:center;gap:14px;padding:8px;display:flex}.DGiSta_cardIcon{border:.5px solid var(--dsw-alias-border-l3);width:48px;height:48px;color:var(--dsw-alias-label-secondary);border-radius:10px;flex:none;justify-content:center;align-items:center;display:inline-flex}.DGiSta_cardForeign{opacity:.72}.DGiSta_cardMain{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.DGiSta_workspaceBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);vertical-align:middle;border-radius:4px;margin-left:8px;padding:1px 6px;font-size:11px;font-weight:400;line-height:16px}.DGiSta_cardTitle{text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:20px;overflow:hidden}.DGiSta_cardDesc{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;line-height:18px;display:-webkit-box;overflow:hidden}.DGiSta_cardWhenToUse{color:var(--dsw-alias-label-caption);font-size:12px;line-height:18px}.DGiSta_cardMarks{flex-wrap:wrap;gap:6px;margin-top:2px;display:flex}.DGiSta_mark{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-tertiary);border-radius:4px;padding:1px 6px;font-size:11px;line-height:16px}.DGiSta_markOn{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.DGiSta_markOff{border-color:var(--dsw-alias-border-l4);color:var(--dsw-alias-label-caption)}.DGiSta_markDanger{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.DGiSta_cardEnd{flex:none;align-items:center;gap:8px;display:flex}.DGiSta_switch{background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:10px;width:36px;height:20px;transition:background .12s;position:relative}.DGiSta_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_switchOn{background:var(--dsw-alias-state-success-primary)}.DGiSta_switch:after{content:\"\";background:var(--dsw-alias-bg-layer-1);border-radius:50%;width:16px;height:16px;transition:transform .12s;position:absolute;top:2px;left:2px}.DGiSta_switchOn:after{transform:translate(16px)}.DGiSta_dialog{width:min(560px,100%)}.DGiSta_form{flex-direction:column;gap:12px;display:flex}.DGiSta_field{flex-direction:column;gap:4px;display:flex}.DGiSta_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.DGiSta_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);height:32px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:8px;padding:0 10px;font-size:13px}.DGiSta_textarea{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);min-height:80px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-markdown-code-block-small);resize:vertical;background:0 0;border-radius:8px;padding:8px 10px;font-size:12px}.DGiSta_status{text-align:center;color:var(--dsw-alias-label-tertiary);padding:32px 0;font-size:13px}.DGiSta_statusError{color:var(--dsw-alias-state-error-primary)}";
12
12
  const tagId = "@sparkelf/dsh-client-ui-skill-center/SkillCenterPage.module.css";
13
13
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
14
14
  const tag = document.createElement("style");
@@ -22,6 +22,7 @@ window.__ModuleLoader__.load({
22
22
  "card": "DGiSta_card",
23
23
  "cardDesc": "DGiSta_cardDesc",
24
24
  "cardEnd": "DGiSta_cardEnd",
25
+ "cardForeign": "DGiSta_cardForeign",
25
26
  "cardHead": "DGiSta_cardHead",
26
27
  "cardIcon": "DGiSta_cardIcon",
27
28
  "cardMain": "DGiSta_cardMain",
@@ -29,14 +30,16 @@ window.__ModuleLoader__.load({
29
30
  "cardTitle": "DGiSta_cardTitle",
30
31
  "cardWhenToUse": "DGiSta_cardWhenToUse",
31
32
  "cards": "DGiSta_cards",
33
+ "dialog": "DGiSta_dialog",
32
34
  "field": "DGiSta_field",
33
35
  "fieldLabel": "DGiSta_fieldLabel",
34
36
  "form": "DGiSta_form",
35
- "formActions": "DGiSta_formActions",
36
37
  "group": "DGiSta_group",
37
38
  "groupCount": "DGiSta_groupCount",
38
39
  "groupHint": "DGiSta_groupHint",
39
40
  "groupTitle": "DGiSta_groupTitle",
41
+ "iconButton": "DGiSta_iconButton",
42
+ "iconWrap": "DGiSta_iconWrap",
40
43
  "input": "DGiSta_input",
41
44
  "mark": "DGiSta_mark",
42
45
  "markDanger": "DGiSta_markDanger",
@@ -46,14 +49,14 @@ window.__ModuleLoader__.load({
46
49
  "pageHead": "DGiSta_pageHead",
47
50
  "pageIntro": "DGiSta_pageIntro",
48
51
  "pageTitle": "DGiSta_pageTitle",
49
- "primaryButton": "DGiSta_primaryButton",
50
52
  "search": "DGiSta_search",
51
53
  "status": "DGiSta_status",
52
54
  "statusError": "DGiSta_statusError",
53
55
  "switch": "DGiSta_switch",
54
56
  "switchOn": "DGiSta_switchOn",
55
57
  "textarea": "DGiSta_textarea",
56
- "toolbar": "DGiSta_toolbar"
58
+ "toolbar": "DGiSta_toolbar",
59
+ "workspaceBadge": "DGiSta_workspaceBadge"
57
60
  };
58
61
  //#endregion
59
62
  //#region src/client/api.ts
@@ -159,20 +162,23 @@ window.__ModuleLoader__.load({
159
162
  */
160
163
  function SkillCard({ skill, t, onToggle, onDelete }) {
161
164
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
162
- className: SkillCenterPage_module_css_default.card,
165
+ className: skill.isActiveWorkspace === false && skill.workspaceName !== void 0 ? SkillCenterPage_module_css_default.card + " " + SkillCenterPage_module_css_default.cardForeign : SkillCenterPage_module_css_default.card,
163
166
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
164
167
  className: SkillCenterPage_module_css_default.cardHead,
165
168
  children: [
166
169
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
167
170
  className: SkillCenterPage_module_css_default.cardIcon,
168
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutline16, { size: 22 })
171
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutlineRegular, { size: 22 })
169
172
  }),
170
173
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
171
174
  className: SkillCenterPage_module_css_default.cardMain,
172
175
  children: [
173
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
176
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
174
177
  className: SkillCenterPage_module_css_default.cardTitle,
175
- children: skill.name
178
+ children: [skill.name, skill.workspaceName === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
179
+ className: SkillCenterPage_module_css_default.workspaceBadge,
180
+ children: skill.workspaceName
181
+ })]
176
182
  }),
177
183
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
178
184
  className: SkillCenterPage_module_css_default.cardDesc,
@@ -226,10 +232,10 @@ window.__ModuleLoader__.load({
226
232
  }
227
233
  /**
228
234
  * The skill center panel.
229
- * @param props - the slot's runtime share, providing the dictionary resolver.
235
+ * @param props - the slot's runtime share: the dictionary resolver and the workspace rows.
230
236
  * @returns the rendered panel.
231
237
  */
232
- function SkillCenterPage({ t }) {
238
+ function SkillCenterPage({ t, renderSlot, ...runtime }) {
233
239
  const api = (0, react.useMemo)(() => new SkillApi(), []);
234
240
  const [state, setState] = (0, react.useState)({ kind: "loading" });
235
241
  const [query, setQuery] = (0, react.useState)("");
@@ -241,11 +247,16 @@ window.__ModuleLoader__.load({
241
247
  content: "",
242
248
  root: "user"
243
249
  });
250
+ const useWorkspaces = runtime.useWorkspaces;
251
+ const workspaces = useWorkspaces === void 0 ? [] : useWorkspaces((snapshot) => snapshot.items);
252
+ const scope = (0, react.useMemo)(() => {
253
+ return (workspaces.find((item) => item.sessionIds.length > 0) ?? workspaces[0])?.path;
254
+ }, [workspaces]);
244
255
  const load = (0, react.useCallback)(async () => {
245
256
  try {
246
257
  setState({
247
258
  kind: "ready",
248
- payload: await api.list()
259
+ payload: await api.list(scope)
249
260
  });
250
261
  } catch (error) {
251
262
  setState({
@@ -253,7 +264,7 @@ window.__ModuleLoader__.load({
253
264
  message: error instanceof Error ? error.message : String(error)
254
265
  });
255
266
  }
256
- }, [api]);
267
+ }, [api, scope]);
257
268
  (0, react.useEffect)(() => {
258
269
  load();
259
270
  }, [load]);
@@ -336,7 +347,7 @@ window.__ModuleLoader__.load({
336
347
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
337
348
  className: SkillCenterPage_module_css_default.page,
338
349
  children: [
339
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
350
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
340
351
  className: SkillCenterPage_module_css_default.pageHead,
341
352
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
342
353
  className: SkillCenterPage_module_css_default.pageTitle,
@@ -348,14 +359,21 @@ window.__ModuleLoader__.load({
348
359
  className: SkillCenterPage_module_css_default.toolbar,
349
360
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
350
361
  type: "button",
351
- className: SkillCenterPage_module_css_default.button,
362
+ className: SkillCenterPage_module_css_default.iconButton,
363
+ "aria-label": t("action.refresh"),
364
+ title: t("action.refresh"),
352
365
  onClick: () => {
353
366
  load();
354
367
  },
355
- children: t("action.refresh")
356
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
357
- type: "button",
358
- className: SkillCenterPage_module_css_default.primaryButton,
368
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
369
+ className: SkillCenterPage_module_css_default.iconWrap,
370
+ "aria-hidden": "true",
371
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {})
372
+ })
373
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
374
+ variant: "primary",
375
+ size: "sm",
376
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, { size: 13 }),
359
377
  onClick: () => {
360
378
  setCreating((v) => !v);
361
379
  },
@@ -363,81 +381,86 @@ window.__ModuleLoader__.load({
363
381
  })]
364
382
  })]
365
383
  }),
366
- creating ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
367
- className: SkillCenterPage_module_css_default.form,
368
- children: [
369
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
370
- label: t("create.name"),
371
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
372
- className: SkillCenterPage_module_css_default.input,
373
- value: draft.name,
374
- onChange: set("name")
375
- })
376
- }),
377
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
378
- label: t("create.description"),
379
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
380
- className: SkillCenterPage_module_css_default.input,
381
- value: draft.description,
382
- onChange: set("description")
383
- })
384
- }),
385
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
386
- label: t("create.whenToUse"),
387
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
388
- className: SkillCenterPage_module_css_default.input,
389
- value: draft.whenToUse,
390
- onChange: set("whenToUse")
391
- })
392
- }),
393
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
394
- label: t("create.content"),
395
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
396
- className: SkillCenterPage_module_css_default.textarea,
397
- value: draft.content,
398
- onChange: set("content")
399
- })
400
- }),
401
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
402
- label: t("create.root"),
403
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
404
- className: SkillCenterPage_module_css_default.input,
405
- value: draft.root,
406
- onChange: (e) => {
407
- setDraft((d) => ({
408
- ...d,
409
- root: e.target.value === "project" ? "project" : "user"
410
- }));
411
- },
412
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
413
- value: "user",
414
- children: t("create.rootUser")
415
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
416
- value: "project",
417
- children: t("create.rootProject")
418
- })]
384
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
385
+ open: creating,
386
+ onClose: () => {
387
+ setCreating(false);
388
+ },
389
+ title: t("create.title"),
390
+ closeLabel: t("action.cancel"),
391
+ className: SkillCenterPage_module_css_default.dialog,
392
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
393
+ variant: "outline",
394
+ autoFocus: true,
395
+ onClick: () => {
396
+ setCreating(false);
397
+ },
398
+ children: t("action.cancel")
399
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
400
+ variant: "primary",
401
+ onClick: () => {
402
+ submit();
403
+ },
404
+ children: t("action.confirm")
405
+ })] }),
406
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
407
+ className: SkillCenterPage_module_css_default.form,
408
+ children: [
409
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
410
+ label: t("create.name"),
411
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
412
+ className: SkillCenterPage_module_css_default.input,
413
+ value: draft.name,
414
+ onChange: set("name")
415
+ })
416
+ }),
417
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
418
+ label: t("create.description"),
419
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
420
+ className: SkillCenterPage_module_css_default.input,
421
+ value: draft.description,
422
+ onChange: set("description")
423
+ })
424
+ }),
425
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
426
+ label: t("create.whenToUse"),
427
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
428
+ className: SkillCenterPage_module_css_default.input,
429
+ value: draft.whenToUse,
430
+ onChange: set("whenToUse")
431
+ })
432
+ }),
433
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
434
+ label: t("create.content"),
435
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
436
+ className: SkillCenterPage_module_css_default.textarea,
437
+ value: draft.content,
438
+ onChange: set("content")
439
+ })
440
+ }),
441
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
442
+ label: t("create.root"),
443
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
444
+ className: SkillCenterPage_module_css_default.input,
445
+ value: draft.root,
446
+ onChange: (e) => {
447
+ setDraft((d) => ({
448
+ ...d,
449
+ root: e.target.value === "project" ? "project" : "user"
450
+ }));
451
+ },
452
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
453
+ value: "user",
454
+ children: t("create.rootUser")
455
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
456
+ value: "project",
457
+ children: t("create.rootProject")
458
+ })]
459
+ })
419
460
  })
420
- }),
421
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
422
- className: SkillCenterPage_module_css_default.formActions,
423
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
424
- type: "button",
425
- className: SkillCenterPage_module_css_default.button,
426
- onClick: () => {
427
- setCreating(false);
428
- },
429
- children: t("action.cancel")
430
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
431
- type: "button",
432
- className: SkillCenterPage_module_css_default.primaryButton,
433
- onClick: () => {
434
- submit();
435
- },
436
- children: t("action.confirm")
437
- })]
438
- })
439
- ]
440
- }) : null,
461
+ ]
462
+ })
463
+ }),
441
464
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
442
465
  className: SkillCenterPage_module_css_default.search,
443
466
  placeholder: t("search.placeholder"),
@@ -494,7 +517,8 @@ window.__ModuleLoader__.load({
494
517
  }
495
518
  }, group.key + ":" + skill.name))
496
519
  })]
497
- }, group.key))
520
+ }, group.key)),
521
+ renderSlot("skill-center.section", {})
498
522
  ]
499
523
  });
500
524
  }
@@ -506,7 +530,7 @@ window.__ModuleLoader__.load({
506
530
  * @returns the icon element.
507
531
  */
508
532
  function SkillCenterPanelIcon({ size }) {
509
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutline16, { size });
533
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutlineRegular, { size });
510
534
  }
511
535
  //#endregion
512
536
  //#region src/client/locales.ts
@@ -591,7 +615,11 @@ window.__ModuleLoader__.load({
591
615
  ctx.slots.inject("main", () => ctx.slots.register({
592
616
  name: "main",
593
617
  key: PANEL_ID,
594
- locale: NS
618
+ locale: NS,
619
+ children: { "skill-center.section": {
620
+ kind: "list",
621
+ scope: "root"
622
+ } }
595
623
  }, SkillCenterPage));
596
624
  ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
597
625
  name: "sidebar.panellist",
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { existsSync } from "node:fs";
2
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
1
+ import { existsSync, lstatSync } from "node:fs";
2
+ import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
- import { dirname, join, resolve } from "node:path";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
5
  //#region lib/types/index.js
6
6
  /**
7
7
  * Skill center — host half. Serves the skill-center data source over the
@@ -40,37 +40,37 @@ const SOURCE_GROUPS = [
40
40
  {
41
41
  key: "bundled",
42
42
  title: "系统内置",
43
- hint: "随 Harness 一同安装,不能删除"
43
+ hint: "随 Harness 一同安装,对所有项目生效"
44
44
  },
45
45
  {
46
- key: "runtime",
47
- title: "运行时注册",
48
- hint: "由已安装的插件在运行时注册"
46
+ key: "project-dsh",
47
+ title: "项目 .dsh/skills",
48
+ hint: "位于项目目录,仅对该工作区生效"
49
49
  },
50
50
  {
51
- key: "user-agents",
52
- title: "用户 ~/.agents/skills",
53
- hint: "在用户 agents 目录下,对所有项目生效"
51
+ key: "project-agents",
52
+ title: "项目 .agents/skills",
53
+ hint: "位于项目目录,随项目提交,对协作者共享"
54
54
  },
55
55
  {
56
- key: "user-dsh",
57
- title: "用户 ~/.dsh/skills",
58
- hint: "在用户 dsh 目录下,对所有项目生效"
56
+ key: "custom",
57
+ title: "自定义目录",
58
+ hint: "由本插件的 customSkillDirs 配置"
59
59
  },
60
60
  {
61
- key: "project-agents",
62
- title: "项目 .agents/skills",
63
- hint: "随项目提交,对协作者共享"
61
+ key: "user-dsh",
62
+ title: "用户 ~/.dsh/skills",
63
+ hint: "在用户 dsh 目录下,对本机所有项目生效"
64
64
  },
65
65
  {
66
- key: "project-dsh",
67
- title: "项目 .dsh/skills",
68
- hint: "项目本地技能,通常不提交"
66
+ key: "user-agents",
67
+ title: "用户 ~/.agents/skills",
68
+ hint: "在用户 agents 目录下,对本机所有项目生效"
69
69
  },
70
70
  {
71
- key: "custom",
72
- title: "自定义目录",
73
- hint: "由本插件的 customSkillDirs 配置"
71
+ key: "runtime",
72
+ title: "运行时注册",
73
+ hint: "由已安装的插件在运行时注册"
74
74
  }
75
75
  ];
76
76
  /**
@@ -124,6 +124,121 @@ function levelOfSource(source, provider) {
124
124
  if (s.includes("runtime")) return "runtime";
125
125
  return "custom";
126
126
  }
127
+ /** The Markdown a skill directory may declare its bundle with. */
128
+ const SKILL_FILE = "SKILL.md";
129
+ /**
130
+ * Read one root directory for skills.
131
+ *
132
+ * The web-app bundle disables the host `skill-filesystem` row and lets each preset
133
+ * own local discovery, so the host registry carries only the bundled catalog: a
134
+ * panel reading the registry alone shows the shipped skills and none of the
135
+ * project or user ones the agent is actually running with. Reading the root
136
+ * conventions here closes that gap without a second catalog authority — the
137
+ * registry still owns discovery for anything it does serve, and this scan is
138
+ * merged under it by name.
139
+ *
140
+ * Both layouts the harness accepts are read: a directory holding SKILL.md, and a
141
+ * flat Markdown file.
142
+ *
143
+ * @param root - absolute root directory to scan.
144
+ * @param level - the group key every skill under this root belongs to.
145
+ * @param workspace - the project a project-scoped root belongs to, so a row can name
146
+ * its origin and mark itself when it is not the workspace being viewed.
147
+ * @returns the skills found, sorted by name; missing roots yield none.
148
+ */
149
+ async function scanSkillRoot(root, level, workspace) {
150
+ let entries;
151
+ try {
152
+ entries = await readdir(root, { withFileTypes: true });
153
+ } catch {
154
+ return [];
155
+ }
156
+ const found = [];
157
+ for (const entry of entries) {
158
+ const path = entry.isDirectory() || entry.isSymbolicLink() ? join(root, entry.name, SKILL_FILE) : entry.name.endsWith(".md") ? join(root, entry.name) : void 0;
159
+ if (path === void 0 || !existsSync(path)) continue;
160
+ const name = entry.name.replace(/\.md$/u, "");
161
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name)) continue;
162
+ let source;
163
+ try {
164
+ source = await readFile(path, "utf8");
165
+ } catch {
166
+ continue;
167
+ }
168
+ const front = /^---\r?\n([\s\S]*?)\r?\n---/.exec(source)?.[1] ?? "";
169
+ const field = (key) => {
170
+ return new RegExp("^\\s*" + key + "\\s*:\\s*(.+?)\\s*$", "mu").exec(front)?.[1]?.replace(/^['"]|['"]$/gu, "");
171
+ };
172
+ const disabled = readDisabledFlag(source);
173
+ const invocation = field("invocation");
174
+ found.push({
175
+ name,
176
+ description: field("description") ?? "",
177
+ level,
178
+ path,
179
+ linked: lstatSyncSafe(path),
180
+ modelInvocable: disabled !== true && invocation !== "user",
181
+ userInvocable: invocation !== "model",
182
+ ...field("when-to-use") === void 0 ? {} : { whenToUse: field("when-to-use") },
183
+ ...workspace === void 0 ? {} : {
184
+ workspaceRoot: workspace.root,
185
+ workspaceName: workspace.name,
186
+ isActiveWorkspace: workspace.active
187
+ }
188
+ });
189
+ }
190
+ return found.sort((a, b) => a.name.localeCompare(b.name));
191
+ }
192
+ /** Whether a skill file is reached through a symlink, which makes deletion unsafe. */
193
+ function lstatSyncSafe(path) {
194
+ try {
195
+ return lstatSync(path).isSymbolicLink() || lstatSync(dirname(path)).isSymbolicLink();
196
+ } catch {
197
+ return false;
198
+ }
199
+ }
200
+ /**
201
+ * Every local skill root this deployment uses, with the group each belongs to.
202
+ *
203
+ * @param deps - the resolved configuration.
204
+ * @param projectRoots - project roots whose skill directories are in scope.
205
+ * @param activeRoot - the project root the view is about, marked active so the panel
206
+ * can distinguish a row from another workspace.
207
+ * @returns the roots to scan, in group order.
208
+ */
209
+ function localSkillRoots(deps, projectRoots, activeRoot) {
210
+ const roots = [];
211
+ for (const projectRoot of projectRoots) {
212
+ const workspace = {
213
+ root: projectRoot,
214
+ name: basename(projectRoot) || projectRoot,
215
+ active: projectRoot === activeRoot
216
+ };
217
+ roots.push({
218
+ path: join(projectRoot, ".dsh", "skills"),
219
+ level: "project-dsh",
220
+ workspace
221
+ });
222
+ roots.push({
223
+ path: join(projectRoot, ".agents", "skills"),
224
+ level: "project-agents",
225
+ workspace
226
+ });
227
+ }
228
+ for (const dir of deps.customSkillDirs) roots.push({
229
+ path: dir,
230
+ level: "custom"
231
+ });
232
+ roots.push({
233
+ path: join(deps.dshHome, "skills"),
234
+ level: "user-dsh"
235
+ });
236
+ roots.push({
237
+ path: join(deps.agentsHome, "skills"),
238
+ level: "user-agents"
239
+ });
240
+ return roots;
241
+ }
127
242
  /**
128
243
  * Read `disable-model-invocation` from a skill file.
129
244
  * @param source - the skill file's content.
@@ -196,11 +311,12 @@ async function readJson(req, maxBytes) {
196
311
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("request body must be a JSON object");
197
312
  return parsed;
198
313
  }
199
- /** The list handler: group the registry snapshot by source. */
314
+ /** The list handler: group the registry snapshot and the local roots together. */
200
315
  async function handleList(deps, cwd) {
201
316
  const projectRoots = [...new Set(deps.activeSessionCwds().map(findProjectRoot).concat(findProjectRoot(cwd)))];
202
317
  const snapshot = await deps.snapshotProject(cwd);
203
318
  const byGroup = new Map(SOURCE_GROUPS.map((g) => [g.key, []]));
319
+ const seen = /* @__PURE__ */ new Set();
204
320
  for (const skill of snapshot.skills) {
205
321
  const level = levelOfSource(skill.source, skill.provider);
206
322
  const entry = {
@@ -212,8 +328,26 @@ async function handleList(deps, cwd) {
212
328
  userInvocable: skill.invocation.userInvocable,
213
329
  ...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse }
214
330
  };
331
+ seen.add(level + ":" + skill.name);
215
332
  (byGroup.get(level) ?? byGroup.get("custom"))?.push(entry);
216
333
  }
334
+ for (const root of localSkillRoots(deps, projectRoots, findProjectRoot(cwd))) for (const skill of await scanSkillRoot(root.path, root.level, root.workspace)) {
335
+ if (seen.has(root.level + ":" + skill.name)) continue;
336
+ seen.add(root.level + ":" + skill.name);
337
+ byGroup.get(root.level)?.push({
338
+ name: skill.name,
339
+ description: skill.description,
340
+ level: skill.level,
341
+ path: skill.path,
342
+ linked: skill.linked,
343
+ modelInvocable: skill.modelInvocable,
344
+ userInvocable: skill.userInvocable,
345
+ ...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse },
346
+ ...skill.workspaceRoot === void 0 ? {} : { workspaceRoot: skill.workspaceRoot },
347
+ ...skill.workspaceName === void 0 ? {} : { workspaceName: skill.workspaceName },
348
+ ...skill.isActiveWorkspace === void 0 ? {} : { isActiveWorkspace: skill.isActiveWorkspace }
349
+ });
350
+ }
217
351
  const groups = [];
218
352
  for (const g of SOURCE_GROUPS) {
219
353
  const skills = (byGroup.get(g.key) ?? []).sort((a, b) => a.name.localeCompare(b.name));
@@ -393,4 +527,4 @@ function apply(ctx, config = {}) {
393
527
  for (const route of makeRoutes(deps)) ctx.effect(() => ctx.webServer.register(route), "ui-skill-center: route " + route.path);
394
528
  }
395
529
  //#endregion
396
- export { ROUTES, apply, buildSkillContent, findProjectRoot, inject, levelOfSource, makeRoutes, name, projectSkillRoot, readDisabledFlag, userSkillRoot, withDisabledFlag };
530
+ export { ROUTES, apply, buildSkillContent, findProjectRoot, inject, levelOfSource, localSkillRoots, makeRoutes, name, projectSkillRoot, readDisabledFlag, scanSkillRoot, userSkillRoot, withDisabledFlag };
@@ -8,7 +8,7 @@
8
8
  * @module @sparkelf/dsh-client-ui-skill-center/client/SkillCenterPage
9
9
  */
10
10
  import { type ReactNode } from 'react';
11
- import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
11
+ import type { PropsRenderSlots, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
12
12
  import type { SkillCenterLocaleKey } from './locales.ts';
13
13
  /** What the panel needs from its slot: the dictionary resolver. */
14
14
  export interface SkillCenterPageProps {
@@ -16,8 +16,8 @@ export interface SkillCenterPageProps {
16
16
  }
17
17
  /**
18
18
  * The skill center panel.
19
- * @param props - the slot's runtime share, providing the dictionary resolver.
19
+ * @param props - the slot's runtime share: the dictionary resolver and the workspace rows.
20
20
  * @returns the rendered panel.
21
21
  */
22
- export declare function SkillCenterPage({ t }: PropsRuntime<'main'> & SkillCenterPageProps): ReactNode;
22
+ export declare function SkillCenterPage({ t, renderSlot, ...runtime }: PropsRuntime<'main'> & PropsRenderSlots<'skill-center.section'> & SkillCenterPageProps): ReactNode;
23
23
  //# sourceMappingURL=SkillCenterPage.d.ts.map
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import type { Context } from '@deepseek-ai/cordis';
10
10
  import type { MainPanelId } from '@deepseek-ai/dsh-client-ui-layout/client';
11
+ export type { SkillCenterSectionOwnerProps } from './slot-contract.ts';
11
12
  import { type SkillCenterLocaleKey } from './locales.ts';
12
13
  declare module '@deepseek-ai/dsh-client-ui-slots' {
13
14
  interface LocaleNamespaceMap {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Owner props a contributed section receives. The slot passes none today; the type exists so a
3
+ * consumer can name the slot's owner share without importing the page.
4
+ */
5
+ export interface SkillCenterSectionOwnerProps {
6
+ /** Reserved for an owner share the panel may add; currently empty. */
7
+ readonly reserved?: never;
8
+ }
9
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
10
+ interface SlotMap {
11
+ /** A section contributed to the skill center page, below the skill list. */
12
+ 'skill-center.section': {
13
+ kind: 'list';
14
+ scope: 'root';
15
+ };
16
+ }
17
+ }
18
+ //# sourceMappingURL=slot-contract.d.ts.map
@@ -88,6 +88,66 @@ export declare function findProjectRoot(cwd: string): string;
88
88
  * @returns the group key.
89
89
  */
90
90
  export declare function levelOfSource(source: string, provider: string): string;
91
+ /** One skill found by reading a root directory. */
92
+ export interface ScannedSkill {
93
+ name: string;
94
+ description: string;
95
+ whenToUse?: string;
96
+ level: string;
97
+ path: string;
98
+ linked: boolean;
99
+ modelInvocable: boolean;
100
+ userInvocable: boolean;
101
+ /** The project a project-scoped skill came from; absent for user and bundled roots. */
102
+ workspaceRoot?: string;
103
+ workspaceName?: string;
104
+ isActiveWorkspace?: boolean;
105
+ }
106
+ /**
107
+ * Read one root directory for skills.
108
+ *
109
+ * The web-app bundle disables the host `skill-filesystem` row and lets each preset
110
+ * own local discovery, so the host registry carries only the bundled catalog: a
111
+ * panel reading the registry alone shows the shipped skills and none of the
112
+ * project or user ones the agent is actually running with. Reading the root
113
+ * conventions here closes that gap without a second catalog authority — the
114
+ * registry still owns discovery for anything it does serve, and this scan is
115
+ * merged under it by name.
116
+ *
117
+ * Both layouts the harness accepts are read: a directory holding SKILL.md, and a
118
+ * flat Markdown file.
119
+ *
120
+ * @param root - absolute root directory to scan.
121
+ * @param level - the group key every skill under this root belongs to.
122
+ * @param workspace - the project a project-scoped root belongs to, so a row can name
123
+ * its origin and mark itself when it is not the workspace being viewed.
124
+ * @returns the skills found, sorted by name; missing roots yield none.
125
+ */
126
+ export declare function scanSkillRoot(root: string, level: string, workspace?: {
127
+ root: string;
128
+ name: string;
129
+ active: boolean;
130
+ }): Promise<ScannedSkill[]>;
131
+ /**
132
+ * Every local skill root this deployment uses, with the group each belongs to.
133
+ *
134
+ * @param deps - the resolved configuration.
135
+ * @param projectRoots - project roots whose skill directories are in scope.
136
+ * @param activeRoot - the project root the view is about, marked active so the panel
137
+ * can distinguish a row from another workspace.
138
+ * @returns the roots to scan, in group order.
139
+ */
140
+ export declare function localSkillRoots(deps: Pick<SkillRoutesDeps, 'dshHome' | 'agentsHome'> & {
141
+ customSkillDirs: readonly string[];
142
+ }, projectRoots: readonly string[], activeRoot?: string): {
143
+ path: string;
144
+ level: string;
145
+ workspace?: {
146
+ root: string;
147
+ name: string;
148
+ active: boolean;
149
+ };
150
+ }[];
91
151
  /**
92
152
  * Read `disable-model-invocation` from a skill file.
93
153
  * @param source - the skill file's content.
package/package.json CHANGED
@@ -61,5 +61,5 @@
61
61
  },
62
62
  "type": "module",
63
63
  "types": "lib/types/index.d.ts",
64
- "version": "0.2.0-rc.9"
64
+ "version": "0.2.1-alpha.2"
65
65
  }