@morehao/dsh-skills-console 0.5.3 → 0.5.4

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 CHANGED
@@ -113,7 +113,7 @@ The panel shows up in DSH Web's settings page / sidebar.
113
113
 
114
114
  1. **Add skill**: click "Add skill" in the top right, enter `owner/repo` or a GitHub URL, then click "Detect"; when the repo has several skills, tick the ones to install (multi-select, all selected by default, each with its description), and a single skill is selected automatically. If nothing is detected you can type skill names by hand; leaving them empty installs everything in the repo.
115
115
  2. **Share with other agents**: only needed for agents that read their own directory (for example Claude Code). Click "Share settings" on a row and tick the target agents; agents that already read the shared directory are not listed. To choose which agents new installs are shared with by default, use "Auto-share on install" in the header (nothing is ticked by default).
116
- 3. **Check for updates**: click the refresh icon in the list header; rows with updates light up "Update", or update everything at once with "Update all". Locally modified skills ask for confirmation first.
116
+ 3. **Check for updates**: manual checks and the background auto-check are **complementary** paths sharing the same verdict — both land in the installed view's update status. Click the refresh icon in the list header to check now, light up "Update", or "Update all"; the background also runs a check every 6 hours (reusing DSH's timer service, which is silently skipped when unavailable — manual checks are unaffected), so the panel is usually already current when you open it. Locally modified skills ask for confirmation before updating.
117
117
  4. **Invocation policy**: click the "Auto / Manual only / Fully disabled" status button on a row and switch in the centered invocation-policy dialog. Auto = both the model and `/` can invoke it; Manual only = only the `disable-model-invocation` frontmatter key in `SKILL.md` changes and `/` still works; Fully disabled = `SKILL.md` is renamed to `SKILL.md.disabled` with a marker left behind, and restoring returns to the mode from before. When the author already declared manual-only in `SKILL.md`, the chip is read-only and is never overridden. While parked, install and update are rejected so the file cannot be silently written back.
118
118
 
119
119
  ## Safety and reliability
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/@morehao/dsh-skills-console.svg)](https://www.npmjs.com/package/@morehao/dsh-skills-console) [![check](https://github.com/morehao/dsh-skills-console/actions/workflows/check.yml/badge.svg)](https://github.com/morehao/dsh-skills-console/actions/workflows/check.yml) [![license](https://img.shields.io/npm/l/@morehao/dsh-skills-console.svg)](LICENSE)
8
8
 
9
- DSH(DeepSeek Harness)的技能控制台:**装、更新、删除、启停、共享至其他 Agent,外加一份订阅清单**。
9
+ DSH(DeepSeek Harness)的技能控制台:**安装、更新、删除、启停、共享至其他 Agent,外加一份订阅清单**。
10
10
 
11
11
  它把本机技能收进一张清单:有哪些、从哪来、谁能用、是不是最新。要用的打开,不用的关掉;别的 Agent 需要,一键建链过去——技能本体始终只有一份。
12
12
 
@@ -113,7 +113,7 @@ plugin_manager install_bundle(target = <本仓库绝对路径>)
113
113
 
114
114
  1. **添加技能**:点右上角「添加技能」,填 `owner/repo` 或 GitHub URL,点「检测技能」;仓库里有多个技能时勾选要装的(支持多选,默认全选,列表带每个技能的说明),只有一个会默认勾上。检测不到时可以手填技能名,留空则装仓库内全部。
115
115
  2. **共享至其他 Agent**:只有「只读自己目录」的 Agent 才需要这一步(例如 Claude Code)。点行内「共享设置」勾选目标 Agent;本来就读共享目录的 Agent 不在列表里。之后新装技能默认共享至哪些 Agent,用页头「新安装时自动共享」设定(默认一个都不勾)。
116
- 3. **检查更新**:点列表头上的刷新图标;有更新的行会亮起「更新」,也可以一次「全部更新」。本地改过的技能会先要求确认。
116
+ 3. **检查更新**:手动检查与后台自动检测是**互补**的两条路径,共用同一套判定、结论都落在已安装页的更新状态里。手动点列表头上的刷新图标即可检查、点亮「更新」或「全部更新」;后台另每 6 小时自动跑一次(复用 DSH 的 timer 服务,未装 timer 时这项静默跳过,手动检查不受影响),所以打开面板时往往已是最新。本地改过的技能更新前会先要求确认。
117
117
  4. **调用策略**:点行内「自动 / 仅手动 / 完全停用」状态按钮,在居中的调用策略弹窗里切换。自动=模型与 `/` 都能调用;仅手动=只改 `SKILL.md` 的 `disable-model-invocation`,`/` 仍可用;完全停用=把 `SKILL.md` 改名为 `SKILL.md.disabled` 并留 marker,恢复时回到停用前的模式。作者已在 `SKILL.md` 里声明仅手动时,chip 只读展示,不会被覆盖。停用期间更新 / 安装会被拒绝,避免文件被悄悄写回。
118
118
 
119
119
  ## 安全与可靠性
@@ -2691,6 +2691,49 @@ export function createService({
2691
2691
  return { ok: true, checks: results };
2692
2692
  },
2693
2693
 
2694
+ /**
2695
+ * 定时上游检测(由 lib/index.js 的 timer 服务按固定间隔调用)。
2696
+ * 只做判定,把结论写进 state.checks;绝不自动安装。
2697
+ * 全程 fail-open:任何异常只记日志,不向定时器抛。
2698
+ */
2699
+ async runAutoCheck({ now = Date.now() } = {}) {
2700
+ const AUTO_CHECK_MIN_INTERVAL_MS = 5 * 60 * 60 * 1000; // 6h 周期,容 1h 抖动
2701
+ try {
2702
+ if (store.readOnly) return { ok: true, skipped: 'readonly' };
2703
+ const settings = state().settings || {};
2704
+ const auto = settings.autoCheck || {};
2705
+ if (auto.enabled === false) return { ok: true, skipped: 'disabled' };
2706
+ const installs = Array.isArray(state().installs) ? state().installs : [];
2707
+ const subscriptions = Array.isArray(state().subscriptions) ? state().subscriptions : [];
2708
+ if (installs.length === 0 || subscriptions.length === 0) return { ok: true, skipped: 'empty' };
2709
+ if (enqueue.pending() > 0) return { ok: true, skipped: 'busy' };
2710
+ const last = auto.lastRunAt ? Date.parse(auto.lastRunAt) : NaN;
2711
+ if (Number.isFinite(last) && now - last < AUTO_CHECK_MIN_INTERVAL_MS) {
2712
+ return { ok: true, skipped: 'throttled' };
2713
+ }
2714
+ const result = await this.checkUpdates({});
2715
+ const checks = Array.isArray(result?.checks) ? result.checks : [];
2716
+ const updatable = checks.filter((item) => item.status === 'update-available').length;
2717
+ const at = nowIso();
2718
+ if (!store.readOnly) {
2719
+ store.update((draft) => {
2720
+ if (!draft.settings) draft.settings = {};
2721
+ const prev = draft.settings.autoCheck || {};
2722
+ draft.settings.autoCheck = {
2723
+ enabled: prev.enabled !== false,
2724
+ lastRunAt: at,
2725
+ lastResult: { count: checks.length, updatable, at },
2726
+ };
2727
+ });
2728
+ }
2729
+ logger('auto.check', { count: checks.length, updatable });
2730
+ return { ok: true, count: checks.length, updatable, lastRunAt: at };
2731
+ } catch (error) {
2732
+ logger('auto.check.failed', { message: error?.message, code: error?.code || null });
2733
+ return { ok: false, error: error?.message || String(error) };
2734
+ }
2735
+ },
2736
+
2694
2737
  async planUpdate({ name }) {
2695
2738
  const install = store.findInstall(name);
2696
2739
  if (!install) throw new ServiceError('error.skills.not-managed', `${name} 没有安装记录`);
package/lib/core/state.js CHANGED
@@ -19,7 +19,12 @@ export const STATE_VERSION = 4;
19
19
  export function emptyState() {
20
20
  return {
21
21
  version: STATE_VERSION,
22
- settings: { exportTargets: [], placement: 'settings' },
22
+ settings: {
23
+ exportTargets: [],
24
+ placement: 'settings',
25
+ // 定时上游检测的开关与上次跑批概览(仅 README 展示,不影响写路径)
26
+ autoCheck: { enabled: true, lastRunAt: null, lastResult: null },
27
+ },
23
28
  agents: { custom: [], dismissed: [] },
24
29
  subscriptions: [],
25
30
  installs: [],
@@ -87,6 +92,7 @@ export function upgradeStateV1(value, { now = new Date().toISOString() } = {}) {
87
92
  settings: {
88
93
  exportTargets: legacyExportTargets(value.settings),
89
94
  placement: value.settings?.placement === 'sidebar' ? 'sidebar' : 'settings',
95
+ autoCheck: { enabled: true, lastRunAt: null, lastResult: null },
90
96
  },
91
97
  installs,
92
98
  exports: exportsRows,
@@ -210,6 +216,12 @@ function validateV3(state) {
210
216
  ];
211
217
  if (settings.placement !== 'sidebar') settings.placement = 'settings';
212
218
 
219
+ // autoCheck 是纯增量字段:老 state 没有它时补默认值,类型写坏也只做宽松归一(不 fail)。
220
+ if (!isPlainObject(settings.autoCheck)) settings.autoCheck = { enabled: true, lastRunAt: null, lastResult: null };
221
+ if (typeof settings.autoCheck.enabled !== 'boolean') settings.autoCheck.enabled = true;
222
+ if (typeof settings.autoCheck.lastRunAt !== 'string') settings.autoCheck.lastRunAt = null;
223
+ if (!isPlainObject(settings.autoCheck.lastResult)) settings.autoCheck.lastResult = null;
224
+
213
225
  for (const row of state.installs) {
214
226
  if (!isPlainObject(row) || typeof row.name !== 'string' || !row.name) {
215
227
  return { ok: false, reason: 'state.installs[] has a row without name' };
package/lib/index.js CHANGED
@@ -116,5 +116,26 @@ export function apply(ctx, config = {}) {
116
116
  const handler = createRoutes({ service, logger });
117
117
  ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: ROUTE_PREFIX, handler }), 'dsh-skills-console:routes');
118
118
  ctx.effect(() => () => policies.dispose(), 'dsh-skills-console:policy');
119
+
120
+ // 定时上游检测:复用宿主 timer 服务(`@deepseek-ai/cordis-plugin-timer`)。
121
+ // 它是可选服务——宿主没装时静默降级为「只有手动检查」,其余功能不受影响。
122
+ // `timer.interval` 内部走 ctx.effect,插件卸载时会自动 clearInterval,不会泄漏。
123
+ const timer = ctx.get('timer');
124
+ if (timer && typeof timer.interval === 'function') {
125
+ const AUTO_CHECK_MS = 6 * 60 * 60 * 1000;
126
+ ctx.effect(
127
+ () => timer.interval(() => void service.runAutoCheck(), AUTO_CHECK_MS),
128
+ 'dsh-skills-console:auto-check',
129
+ );
130
+ // 启动后给一次延迟首检(避开启动高峰与迁移窗口),内部节流会兜住重复。
131
+ ctx.effect(
132
+ () => timer.timeout(() => void service.runAutoCheck(), 30 * 1000),
133
+ 'dsh-skills-console:auto-check-first',
134
+ );
135
+ logger('plugin.autoCheck', { intervalMs: AUTO_CHECK_MS });
136
+ } else {
137
+ logger('plugin.autoCheck.off', { reason: 'timer-service-missing' });
138
+ }
139
+
119
140
  logger('plugin.apply', { stateDir: paths.stateDir, canonical: paths.canonical, readOnly: store.readOnly });
120
141
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morehao/dsh-skills-console",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
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>",