@deepseek-ai/dsh-plugin-recommendations 0.0.0-stage → 0.2.2-alpha.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,49 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/experimental/plugin-recommendations/README.md
5
+ /:
6
+ en: c34591bc4458dc55
7
+ zh: 7672f17a222f287e
8
+ /deepseek-ai-dsh-plugin-recommendations:
9
+ en: f7efb269a445d68e
10
+ zh: 171930b415e131f7
11
+ /deepseek-ai-dsh-plugin-recommendations/summary:
12
+ en: 9bf4699d4f05f511
13
+ zh: 33f63aeb459401b3
14
+ /deepseek-ai-dsh-plugin-recommendations/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: 59094206ec49ad20
17
+ /deepseek-ai-dsh-plugin-recommendations/use-this-package:
18
+ en: 79fde2c706fbaf14
19
+ zh: 42eb78e5ea7f0ed8
20
+ /deepseek-ai-dsh-plugin-recommendations/use-this-package/outcomes-and-recovery:
21
+ en: 3b07d58a6bc4394a
22
+ zh: 5d64630582c8da80
23
+ /deepseek-ai-dsh-plugin-recommendations/understand-the-implementation:
24
+ en: 804d38b1db15afe4
25
+ zh: e8627ae6cb763437
26
+ /deepseek-ai-dsh-plugin-recommendations/further-exploration:
27
+ en: 716e44387bd2d3ea
28
+ zh: 1a58c0a36d4f18d7
29
+ /deepseek-ai-dsh-plugin-recommendations/model-experience:
30
+ en: 215e7ba838619b7b
31
+ zh: a311da3843709f90
32
+ /deepseek-ai-dsh-plugin-recommendations/model-experience/official-plugin-recommendations:
33
+ en: 79e932b35e50619a
34
+ zh: 821b4d95d6ce1d22
35
+ /deepseek-ai-dsh-plugin-recommendations/model-experience/official-plugin-recommendations/what-the-model-sees:
36
+ en: 64cc3b2eff7bec02
37
+ zh: 9f5bab103219e352
38
+ /deepseek-ai-dsh-plugin-recommendations/model-experience/official-plugin-recommendations/token-effect:
39
+ en: b70f0604f4a70ef9
40
+ zh: e369c6a42092f9c4
41
+ /deepseek-ai-dsh-plugin-recommendations/model-experience/official-plugin-recommendations/kv-cache-effect:
42
+ en: e6575a3adcaa7d1c
43
+ zh: e5323a4571ecc8aa
44
+ /deepseek-ai-dsh-plugin-recommendations/known-limitations-and-deferred-work:
45
+ en: 8506b8b41b36450a
46
+ zh: 439deb24f6cd0f77
47
+ /deepseek-ai-dsh-plugin-recommendations/known-limitations-and-deferred-work/dev-note:
48
+ en: e67f633dc6fe7773
49
+ zh: e88aeb617ffb74ea
package/README.md CHANGED
@@ -1,3 +1,101 @@
1
- # Temporary Holding Version
1
+ ---
2
+ description: "Recommend built-in official plugins for explicit user needs and enable them from conversation cards."
3
+ kind: "package-reference"
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # @deepseek-ai/dsh-plugin-recommendations
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ Users can discover a relevant built-in plugin while discussing a concrete need or asking how to do something in DSH. The Agent answers first, then offers one card at the completed Turn’s end; the user can open its plugin details, choose Enable, or choose Not now. Enabling changes the whole profile and persists across restarts. The Agent learns the result with the next request; clicking starts no model turn. Users can later disable or remove the plugin from its details page.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ Open Plugins → More → Experimental plugins and enable [Personalized recommendations](../personalized-recommendations/README.md); its Plugin recommendations row is this package. Shipped profiles leave this experimental bundle disabled. Enabling pins it to the main Plugins list; disabling returns it to the secondary page only. The Personalized recommendations bundle also enables the shared [action card service](../action-cards/README.md) and its browser half; that service's `queuedMessageLimit` sets how many queued next-turn messages stop the current turn's card. This plugin has no configuration of its own.
29
+
30
+ The experiment keeps working when the bundle’s Personalized pages row is switched off, and neither package depends on the other. A Turn shows at most one card across both kinds: the first accepted `propose_action` call of the Turn becomes the card. For several independent needs, guidance follows an explicit user priority and otherwise prefers an existing plugin over a new page; other needs stay in the answer.
31
+
32
+ ### Outcomes and recovery
33
+
34
+ The card appears after the Turn of its accepted call ends. Enable requires permission to modify the profile. The card reads the plugin's catalog text when shown; a refused lookup shows its reason, a lost reply says the details could not be loaded, and both offer Not now and Try again but no Enable. The Host rechecks the plugin's catalog eligibility when the user acts. A saved selection can require a restart. A failed change can be retried from the same card, and so can an enable whose result a plugin reload cut off, which the card shows as unconfirmed; once that enable has landed, the next click on the card shows Enabled. A plugin already selected elsewhere settles as already enabled without another change. A later proposal replaces an undecided card, which then shows Replaced; while `queuedMessageLimit` or more messages are queued, the card tool refuses a proposal. A lost response leaves the outcome uncertain until the Session projection reconnects; repeated card actions are idempotent.
35
+
36
+ -----
37
+
38
+ <a id="understand-the-implementation"></a>
39
+ ## Understand the implementation
40
+
41
+ <details>
42
+ <summary>Implementation internals — click to expand</summary>
43
+
44
+ The Host entry is a function plugin. Its `apply` assembles the internal `pluginRecommendations` Remote service with `ctx.plugin`; the service reads catalog text for cards and enables plugins for its card kind. It is not a separately configurable or replaceable YAML entry and adds no service to `dsh-base`. Disabling the feature removes its skill, card kind and Remote and drains its owned enabling work.
45
+
46
+ The plugin adds no model tool and no system-prompt section. It registers the `official-plugins` skill; loading it appends the current eligible catalog from `pluginManager`. It registers the `enable_official_plugin` kind with the shared [action card service](../action-cards/README.md): the `official-plugins` skill, `details` holding the plugin `name`, and the Enable verb, which requires global Remote access. The service owns the `propose_action` tool, the card projection, Not now, the Remote action and the status notice. This plugin enables the named plugin through Plugin Manager and records `enabled`, `already-enabled`, `failed` or `unconfirmed`. The decision data carries `application: restart-required` when the saved selection waits for a restart, and the management `errorCode` of a failed change, or `operation-error` when Plugin Manager rejected the change without one. `unconfirmed` records an enable whose result a plugin reload cut off. Before every action, including Not now, such a card checks the profile, and a plugin found on records `enabled` instead of a retry or a dismissal. Not now is recorded for an unstarted, failed or unconfirmed card. Only shipped, optional, official plugins that are available, writable and error-free are eligible; the name is checked when the user acts, not when the model proposes. The Remote `describe(sessionId, callId)` returns the current title, description and selection of a card's plugin, or a refusal.
47
+
48
+ Concurrent actions on one card share one operation, and disposal stops local follow-up without waiting on a profile mutation queued behind its own HMR unload. Plugin Manager owns an accepted mutation; an unconfirmed card rechecks the profile when clicked again. Profile files own installation state; the card decision records only the historical outcome.
49
+
50
+ - [Source](src/index.ts) — registration and service entry.
51
+ - [Tests](tests/) — actions, recovery and lifecycle evidence.
52
+
53
+ </details>
54
+
55
+ -----
56
+
57
+ <a id="further-exploration"></a>
58
+ ## Further Exploration
59
+
60
+ - [Plugin manager](../../boot/plugin-manager/README.md) — profile changes.
61
+ - [Action cards](../action-cards/README.md) — the card tool, card state and user actions.
62
+ - [Recommendation cards](../client-ui-plugin-recommendations/README.md) — the card body.
63
+ - [Personalized pages](../creation-recommendations/README.md) — the other card kind.
64
+
65
+ <a id="model-experience"></a>
66
+ ## Model Experience
67
+
68
+ ### Official plugin recommendations
69
+
70
+ #### What the model sees
71
+
72
+ The existing `skill` tool loads `official-plugins` when the user asks how to do or use something in DSH, asks whether DSH can do something, or says a capability is missing or should change, such as tools, input, displayed thinking or titles, session search, terminals, worktrees, agent teams, desktop control, automation, access, approval prompts, output badges or signatures, and plugins or plugin sources. The catalog entry tells the Agent that such a question is not a request to develop DSH and to load the skill before reading files or source. Loading returns matching policy, the submission rule and the current catalog with names, localized titles, descriptions and enabled state. An enabled match gets usage guidance without a card; a partial match states its limit. Ordinary tasks, quoted wishes, discussion-only and declined requests get no card. The proposal is one [`propose_action`](../action-cards/README.md#model-experience) call in the same Turn as the skill load, with `action: "enable_official_plugin"`, the plugin's catalog `title`, a one-sentence `brief` in the user's language and `details` holding the catalog `name`; the call itself is the submission, and nothing changes until the user clicks Enable. Because the card shows only the plugin’s catalog title and description, the final answer says in one sentence, in the user’s language, how the plugin meets the need; `brief` is that same sentence. Only an accepted call's result says how to end the answer: `Card shown below your answer. Do not restate it. End with at most one short invitation in the user’s language to click Enable below; do not say the plugin is already on.` A refused call carries no invitation, so the Agent invites the user only to a card that exists. The next request after a click carries the shared status notice with this kind's guidance: a fact, not a request for more work, and enabling authorizes nothing beyond that plugin.
73
+
74
+ #### Token effect
75
+
76
+ While enabled, requests carry only the 495-character catalog entry, within the skill catalog's 500-character limit; the shared card tool is declared only after a card skill loads. A skill load appends about 26K characters: a 2.9K-character policy plus the catalog, about 22.9K characters for 14 eligible plugins with titles and descriptions in the eight shipped languages, growing with their number. A recommendation needs one skill load and one `propose_action` call before the final answer; the accepted call's result is 169 characters, about 42 tokens at four characters per token. A changed card appends one short status notice to the next request.
77
+
78
+ #### KV Cache effect
79
+
80
+ Skill contents and the proposal call append to the transcript; the common system prompt is unchanged, and the card tool joins the declarations only after a card skill loads, as the [action cards](../action-cards/README.md#model-experience) page describes. Enabling a recommended plugin can change later tool declarations and their cache reuse.
81
+
82
+ ## Known Limitations and Deferred Work
83
+
84
+ <a id="known-limitations-and-deferred-work"></a>
85
+
86
+ The manual real-model input set is in `evaluation/intent-cases.json`; unit tests do not measure semantic intent accuracy. Matching, priority between needs and respect for a request not to recommend are skill guidance evaluated with real model runs, not keyword guards.
87
+
88
+ - Only shipped, optional, official plugins are eligible; missing packages and third-party registries are excluded.
89
+ - Recommendations require a live root conversation; there is no unsolicited opportunity discovery.
90
+ - A historical decision does not continuously track changes later made on the Plugins page.
91
+ - The catalog reports enabled state per bundle: Personalized recommendations shows as enabled while only its Personalized pages row is off, so the skill can tell a user who asks for a page that it is already on.
92
+
93
+ <a id="dev-note"></a>
94
+ ### Dev Note
95
+
96
+ <details>
97
+ <summary>Working context for maintainers — click to expand</summary>
98
+
99
+ None.
100
+
101
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,101 @@
1
+ ---
2
+ description: "针对用户明确表达的需求推荐内置官方插件,并通过会话内卡片启用。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @deepseek-ai/dsh-plugin-recommendations
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ 用户讨论具体需求、或问 DSH 里某件事怎么做时,可以发现相关的内置插件。Agent 先正常回答,再在本轮完成后提供一张卡片;用户可以打开插件详情、点击启用或选择暂不需要。启用会修改整个 profile,并在重启后保留。Agent 在下一次请求时得知结果;点击本身不会开始新的模型轮次。用户之后可在插件详情页关闭或移除该插件。
13
+
14
+ ## 目录
15
+
16
+ - [使用此包](#use-this-package)
17
+ - [了解实现](#understand-the-implementation)
18
+ - [进一步阅读](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与后续工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用此包
27
+
28
+ 打开插件 → 更多 → 实验性插件,启用[个性化推荐](../personalized-recommendations/README.zh.md),其中的「插件推荐」行就是本包。内置 profile 默认不启用这个实验 bundle。启用后它会固定显示在插件主列表;关闭后只回到二级页。个性化推荐 bundle 同时启用共享的[操作卡片服务](../action-cards/README.zh.md)及其浏览器端;该服务的 `queuedMessageLimit` 决定排队等待后续轮次的消息达到多少条时本轮不再出卡。本插件没有自己的配置项。
29
+
30
+ 同一 bundle 的「个性化页面」行关闭后,本实验仍可使用,两个包互不依赖。两种卡片合计每轮最多一张:本轮第一个被接受的 `propose_action` 调用成为卡片。多个独立需求时,指引优先遵循用户明确的优先级,否则现成插件优先于新建页面;其余需求在回答正文中说明。
31
+
32
+ ### 结果与恢复
33
+
34
+ 被接受的调用所在的轮次结束后卡片才会出现。启用需要修改 profile 的权限。卡片显示时读取插件的目录文本;查询被拒绝时显示原因,回复丢失时说明无法读取插件信息,两种情况都提供「暂不需要」和「重试」,不提供启用。用户操作时 Host 会重新检查插件是否在可推荐目录里。已保存的选择可能需要重启。失败的变更可在同一张卡片上重试;插件重载打断启用、结果未能确认时,卡片显示未确认,同样可以重试;那次启用之后生效的,下一次点击卡片会显示「已启用」。已在别处开启的插件会直接记为已启用,不再改动。之后的新提案会替换尚未处理的卡片,旧卡片显示「已替换」;排队消息达到 `queuedMessageLimit` 时,卡片工具会拒绝提案。响应丢失时结果暂不确定,Session 投影重连后会恢复;重复点击卡片是幂等的。
35
+
36
+ -----
37
+
38
+ <a id="understand-the-implementation"></a>
39
+ ## 了解实现
40
+
41
+ <details>
42
+ <summary>实现细节——点击展开</summary>
43
+
44
+ Host 入口是函数插件。它的 `apply` 通过 `ctx.plugin` 组装内部的 `pluginRecommendations` Remote service;该 service 为卡片读取目录文本,并为本卡片种类启用插件。它不是可单独配置或替换的 YAML 条目,也不会向 `dsh-base` 添加 service。关闭功能会移除它的 skill、卡片种类和 Remote,并等待它拥有的启用操作结束。
45
+
46
+ 本插件不添加模型工具,也不添加 system prompt 段。它注册 `official-plugins` skill,加载时追加来自 `pluginManager` 的当前可推荐目录。它向共享的[操作卡片服务](../action-cards/README.zh.md)注册 `enable_official_plugin` 种类:skill 为 `official-plugins`,`details` 带插件的 `name`,动作「启用」需要全局 Remote 权限。`propose_action` 工具、卡片投影、「暂不需要」、Remote 操作和状态通知都由共享服务负责。本插件通过 Plugin Manager 启用指定插件,记录 `enabled`、`already-enabled`、`failed` 或 `unconfirmed`。保存的选择要等重启才生效时,决定数据带 `application: restart-required`;变更失败时带插件管理的 `errorCode`,Plugin Manager 拒绝变更但没有给出代码时带 `operation-error`。`unconfirmed` 表示插件重载打断了启用、没有拿到结果。这类卡片在每次操作前(包括「暂不需要」)都会检查 profile,发现插件已开启就记为 `enabled`,不再重试或记为忽略。未处理、失败和未确认的卡片可以记为「暂不需要」。只有已随安装提供、可选、官方、可读取、可写且无错误的插件可以推荐;名字在用户操作时检查,而不是在模型提议时。Remote `describe(sessionId, callId)` 返回卡片对应插件当前的标题、描述和启用状态,或拒绝原因。
47
+
48
+ 同一张卡上的并发操作共用一次执行;卸载时停止本地后续处理,不等待排在自身 HMR 卸载之后的 profile 变更。已接纳的变更由 Plugin Manager 负责完成;结果未确认的卡片再次点击时会重新检查 profile。安装状态由 profile 文件负责,卡片决定只记录历史结果。
49
+
50
+ - [源码](src/index.ts) — 注册与服务入口。
51
+ - [测试](tests/) — 操作、恢复和生命周期验证。
52
+
53
+ </details>
54
+
55
+ -----
56
+
57
+ <a id="further-exploration"></a>
58
+ ## 进一步阅读
59
+
60
+ - [插件管理器](../../boot/plugin-manager/README.zh.md) — 配置档案变更。
61
+ - [操作卡片](../action-cards/README.zh.md) — 卡片工具、卡片状态和用户操作。
62
+ - [推荐卡片](../client-ui-plugin-recommendations/README.zh.md) — 卡片正文。
63
+ - [个性化页面](../creation-recommendations/README.zh.md) — 另一种卡片。
64
+
65
+ <a id="model-experience"></a>
66
+ ## 模型体验
67
+
68
+ ### 官方插件推荐
69
+
70
+ #### 模型可见内容
71
+
72
+ 用户问 DSH 里某件事怎么做或怎么用、DSH 能不能做某件事,或者说缺少某种能力、希望它换一种工作方式时,例如工具、输入方式、思考内容或标题的显示、搜索会话、终端、工作树、多智能体协作、桌面操作、自动化、访问权限、授权确认、输出徽章或签名、插件及插件源,现有的 `skill` 工具会加载 `official-plugins`。目录条目告诉 Agent 这类问题不是要开发 DSH,并要求在读取文件或源码之前先加载它。加载结果包含匹配规则、提交方式,以及当前目录中的名称、本地化标题、描述和启用状态。已启用的插件只说明用法、不出卡;只覆盖部分需求时说明限制。普通任务、引用中的愿望、只讨论和明确不要推荐的请求不出卡。提案是在加载 skill 的同一轮里调用一次 [`propose_action`](../action-cards/README.zh.md#model-experience):`action: "enable_official_plugin"`、插件在目录中的 `title`、一句用户语言的 `brief`,以及带目录 `name` 的 `details`;调用本身就是提交,用户点击启用前什么都不会改变。卡片只显示插件在目录中的标题和描述,所以最终回答要用用户的语言、一句话说明这个插件如何满足需求;`brief` 就是这同一句话。只有被接受的调用结果会说明回答怎样结尾:`Card shown below your answer. Do not restate it. End with at most one short invitation in the user’s language to click Enable below; do not say the plugin is already on.`。被拒绝的调用不带邀请说明,所以 Agent 只会邀请用户点击已经出现的卡片。点击后的下一次请求会带共享的状态通知和本种类的说明:它是事实,不是继续工作的要求,启用一个插件也不授权该插件之外的任何事。
73
+
74
+ #### Token 影响
75
+
76
+ 启用期间,每次请求只多一条 495 字符的目录条目(在 skill 目录 500 字符的上限内);共享的卡片工具只在加载过卡片 skill 之后才声明。一次 skill 加载追加约 26K 字符:2.9K 字符的规则,加上目录;14 个可推荐插件(标题和描述含 8 种发布语言)时目录约 22.9K 字符,并随插件数量增长。一次推荐需要加载一次 skill,并在最终回答前调用一次 `propose_action`;被接受的调用结果 169 字符,按每 4 字符 1 个 token 计约 42 个 token。状态变化的卡片会在下一次请求追加一条简短通知。
77
+
78
+ #### KV Cache 影响
79
+
80
+ Skill 内容和提案调用追加到对话记录;公共 system prompt 不变,卡片工具只在加载过卡片 skill 之后才加入声明,见[操作卡片](../action-cards/README.zh.md#model-experience)。启用被推荐的插件可能改变之后的工具声明及其缓存复用。
81
+
82
+ ## 已知限制与后续工作
83
+
84
+ <a id="known-limitations-and-deferred-work"></a>
85
+
86
+ 人工运行的真实模型输入集见 `evaluation/intent-cases.json`;单元测试不衡量语义意图准确率。匹配、多需求之间的取舍以及尊重不推荐的要求都属于 skill 指引,通过真实模型测试评估,不是关键词判断。
87
+
88
+ - 只推荐已随安装提供、可选的官方插件;缺失的包和第三方注册源不在范围内。
89
+ - 推荐需要在线的根会话;不会主动寻找推荐机会。
90
+ - 历史决定不会持续跟踪之后在插件页做的修改。
91
+ - 目录按 bundle 报告启用状态:只关闭「个性化页面」行时,个性化推荐仍显示为已启用,skill 可能因此告诉想要页面的用户它已开启。
92
+
93
+ <a id="dev-note"></a>
94
+ ### 开发备注
95
+
96
+ <details>
97
+ <summary>维护者工作上下文——点击展开</summary>
98
+
99
+ 无。
100
+
101
+ </details>
package/icon.svg ADDED
@@ -0,0 +1,9 @@
1
+ <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M8 9.5C8 8.67157 8.67157 8 9.5 8H15.5C16.3284 8 17 8.67157 17 9.5V15.5C17 16.3284 16.3284 17 15.5 17H9.5C8.67157 17 8 16.3284 8 15.5V9.5ZM19 9.5C19 8.67157 19.6716 8 20.5 8H26.5C27.3284 8 28 8.67157 28 9.5V15.5C28 16.3284 27.3284 17 26.5 17H20.5C19.6716 17 19 16.3284 19 15.5V9.5ZM8 20.5C8 19.6716 8.67157 19 9.5 19H15.5C16.3284 19 17 19.6716 17 20.5V26.5C17 27.3284 16.3284 28 15.5 28H9.5C8.67157 28 8 27.3284 8 26.5V20.5ZM22.25 19H24.75V22.25H28V24.75H24.75V28H22.25V24.75H19V22.25H22.25V19Z" fill="url(#paint0_linear_plugin_market)" />
3
+ <defs>
4
+ <linearGradient id="paint0_linear_plugin_market" x1="8.5" y1="10" x2="30" y2="26" gradientUnits="userSpaceOnUse">
5
+ <stop stop-color="#67C7FE" />
6
+ <stop offset="1" stop-color="#3F77D8" />
7
+ </linearGradient>
8
+ </defs>
9
+ </svg>
package/lib/index.js ADDED
@@ -0,0 +1,287 @@
1
+ import { z } from "zod";
2
+ import { Service } from "@deepseek-ai/cordis";
3
+ import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
4
+ import { isPluginStopping } from "@deepseek-ai/dsh-action-cards";
5
+ import { readFile } from "node:fs/promises";
6
+ import { BUNDLED_SKILL_RANK } from "@deepseek-ai/dsh-skill";
7
+ //#region lib/types/skill.js
8
+ /** On-demand official-plugin catalog for explicit product-use needs. */
9
+ /** Shipped optional plugins only; no arbitrary install target is accepted.
10
+ * @param bundle - a profile bundle as the plugin manager reports it.
11
+ * @returns whether a card may offer and enable it.
12
+ */
13
+ function eligible(bundle) {
14
+ return bundle.official && bundle.optional && bundle.availability !== "missing" && bundle.readOnlyReason === void 0 && bundle.error === void 0;
15
+ }
16
+ /** The eligible plugins as the skill lists them: name, title, description and selection. */
17
+ function catalogOf(bundles) {
18
+ return JSON.stringify(bundles.filter(eligible).map((bundle) => ({
19
+ name: bundle.name,
20
+ title: bundle.meta?.title ?? bundle.name,
21
+ description: bundle.meta?.description ?? bundle.description ?? "",
22
+ enabled: bundle.enabled
23
+ })));
24
+ }
25
+ /** Register the experiment's skill without adding a model tool or a system-prompt section.
26
+ * @param ctx - Owning plugin context with skill and plugin registries.
27
+ */
28
+ function registerOfficialPluginsSkill(ctx) {
29
+ const candidate = {
30
+ name: "official-plugins",
31
+ description: "Check DSH’s optional plugins when the user asks how to do or use something in this app, whether it can, or says a capability is missing or should change: tools, input, thinking or title display, session search, terminals, worktrees, agent teams, desktop control, automation, access, approvals, badges or signatures, plugins or plugin sources. Not a request to develop DSH, even in its own repository: load before reading files or source. Not for ordinary tasks, quoted wishes or declined offers.",
32
+ invocation: {
33
+ modelInvocable: true,
34
+ userInvocable: false
35
+ },
36
+ provider: "dsh-official-plugins",
37
+ source: "bundled",
38
+ rank: BUNDLED_SKILL_RANK,
39
+ locator: null
40
+ };
41
+ ctx.skills.registerProvider(() => ({
42
+ name: candidate.provider,
43
+ list: () => Promise.resolve([candidate]),
44
+ async get(_candidate, { signal }) {
45
+ const policy = await readFile(new URL("../skills/official-plugins.md", import.meta.url), {
46
+ encoding: "utf8",
47
+ signal
48
+ });
49
+ const catalog = catalogOf(await ctx.pluginManager.listBundles());
50
+ signal?.throwIfAborted();
51
+ const { rank: _rank, locator: _locator, ...entry } = candidate;
52
+ return {
53
+ ...entry,
54
+ content: `${policy}\n## Official plugins\n\n${catalog}`
55
+ };
56
+ }
57
+ }));
58
+ }
59
+ //#endregion
60
+ //#region lib/types/index.js
61
+ /** Explicit-need official plugin recommendations: an `enable_official_plugin` card offers one plugin, and only a human click enables it. */
62
+ var __runInitializers = function(thisArg, initializers, value) {
63
+ var useValue = arguments.length > 2;
64
+ for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
65
+ return useValue ? value : void 0;
66
+ };
67
+ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
68
+ function accept(f) {
69
+ if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
70
+ return f;
71
+ }
72
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
73
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
74
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
75
+ var _, done = false;
76
+ for (var i = decorators.length - 1; i >= 0; i--) {
77
+ var context = {};
78
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
79
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
80
+ context.addInitializer = function(f) {
81
+ if (done) throw new TypeError("Cannot add initializers after decoration has completed");
82
+ extraInitializers.push(accept(f || null));
83
+ };
84
+ var result = (0, decorators[i])(kind === "accessor" ? {
85
+ get: descriptor.get,
86
+ set: descriptor.set
87
+ } : descriptor[key], context);
88
+ if (kind === "accessor") {
89
+ if (result === void 0) continue;
90
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
91
+ if (_ = accept(result.get)) descriptor.get = _;
92
+ if (_ = accept(result.set)) descriptor.set = _;
93
+ if (_ = accept(result.init)) initializers.unshift(_);
94
+ } else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
95
+ else descriptor[key] = _;
96
+ }
97
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
98
+ done = true;
99
+ };
100
+ const name = "plugin-recommendations";
101
+ /** Services the plugin uses; `actionCards` owns the card tool, card state and user actions. */
102
+ const inject = [
103
+ "pluginManager",
104
+ "agents",
105
+ "skills",
106
+ "actionCards"
107
+ ];
108
+ /** Card action this plugin registers. */
109
+ const ACTION = "enable_official_plugin";
110
+ const detailsSchema = z.object({ name: z.string().trim().min(1) });
111
+ /** Own entries only: browser-supplied ids such as `toString` never reach inherited object members. */
112
+ function ownEntry(record, key) {
113
+ return Object.hasOwn(record, key) ? record[key] : void 0;
114
+ }
115
+ function settled(decision) {
116
+ return {
117
+ status: "settled",
118
+ decision
119
+ };
120
+ }
121
+ function refused(reason) {
122
+ return {
123
+ status: "refused",
124
+ reason
125
+ };
126
+ }
127
+ /** Catalog lookups for card text and the enabling behind the `enable_official_plugin` card. */
128
+ let PluginRecommendations = (() => {
129
+ let _classSuper = TypertRemoteService;
130
+ let _instanceExtraInitializers = [];
131
+ let _describe_decorators;
132
+ return class PluginRecommendations extends _classSuper {
133
+ static {
134
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
135
+ _describe_decorators = [Remote];
136
+ __esDecorate(this, null, _describe_decorators, {
137
+ kind: "method",
138
+ name: "describe",
139
+ static: false,
140
+ private: false,
141
+ access: {
142
+ has: (obj) => "describe" in obj,
143
+ get: (obj) => obj.describe
144
+ },
145
+ metadata: _metadata
146
+ }, null, _instanceExtraInitializers);
147
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
148
+ enumerable: true,
149
+ configurable: true,
150
+ writable: true,
151
+ value: _metadata
152
+ });
153
+ }
154
+ static inject = inject;
155
+ /** Card reconciliations and enabling in flight; unloading waits for them. */
156
+ operations = (__runInitializers(this, _instanceExtraInitializers), /* @__PURE__ */ new Set());
157
+ stopped = Promise.withResolvers();
158
+ closing = false;
159
+ owner;
160
+ constructor(ctx) {
161
+ super(ctx, "pluginRecommendations");
162
+ this.owner = ctx.fiber;
163
+ }
164
+ [Service.init]() {
165
+ registerOfficialPluginsSkill(this.ctx);
166
+ this.ctx.actionCards.register({
167
+ action: ACTION,
168
+ skill: "official-plugins",
169
+ details: detailsSchema,
170
+ invitation: "End with at most one short invitation in the user’s language to click Enable below; do not say the plugin is already on.",
171
+ verbs: { enable: { global: true } },
172
+ openPhases: ["failed", "unconfirmed"],
173
+ statusGuidance: "Plugin cards: enabled means the plugin is on now, or after a DSH restart when application is restart-required; already-enabled means it was on before; failed, or unconfirmed when a reload cut enabling off, can be retried from the card; dismissed means the user declined it, so do not offer it again in this conversation; enabling authorizes nothing beyond that plugin.",
174
+ reconcile: (agent, proposal) => this.tracked(this.confirm(agent, proposal)),
175
+ act: (agent, proposal) => this.tracked(this.enable(agent, proposal))
176
+ });
177
+ this.ctx.effect(() => async () => {
178
+ this.closing = true;
179
+ this.stopped.resolve(void 0);
180
+ await Promise.allSettled([...this.operations]);
181
+ }, "plugin-recommendations: drain enabling");
182
+ }
183
+ /**
184
+ * Read the current catalog text for a logged card.
185
+ * @param agent - Exact live root resolved by the Gateway from the Session id.
186
+ * @param callId - The card's `propose_action` call id, never a package supplied by the browser.
187
+ * @returns the plugin's title, description and selection, or a refusal when the card names no available official plugin.
188
+ */
189
+ async describe(agent, callId) {
190
+ if (this.isStopping()) return {
191
+ status: "refused",
192
+ reason: "closing"
193
+ };
194
+ if (!this.ctx.agents.roots().includes(agent)) return {
195
+ status: "refused",
196
+ reason: "not-live-root"
197
+ };
198
+ const proposal = ownEntry(this.ctx.actionCards.view(agent.session).proposals, callId);
199
+ const details = proposal?.action === ACTION ? detailsSchema.safeParse(proposal.details) : void 0;
200
+ if (details?.success !== true) return {
201
+ status: "refused",
202
+ reason: "unknown"
203
+ };
204
+ const bundle = (await this.ctx.pluginManager.listBundles()).find((item) => item.name === details.data.name);
205
+ if (bundle === void 0 || !eligible(bundle)) return {
206
+ status: "refused",
207
+ reason: "unavailable"
208
+ };
209
+ return {
210
+ status: "available",
211
+ title: bundle.meta?.title ?? bundle.name,
212
+ description: bundle.meta?.description ?? bundle.description ?? "",
213
+ enabled: bundle.enabled
214
+ };
215
+ }
216
+ isStopping() {
217
+ return isPluginStopping(this.owner, this.closing);
218
+ }
219
+ /** Track an operation until it settles, so unloading waits for it. */
220
+ tracked(operation) {
221
+ this.operations.add(operation);
222
+ return operation.finally(() => {
223
+ this.operations.delete(operation);
224
+ });
225
+ }
226
+ /** Record `enabled` on an unconfirmed card whose interrupted enable has since turned its plugin on. */
227
+ async confirm(agent, proposal) {
228
+ const cards = this.ctx.actionCards;
229
+ if (ownEntry(cards.view(agent.session).decisions, proposal.callId)?.phase !== "unconfirmed") return;
230
+ if ((await this.ctx.pluginManager.listBundles()).find((item) => item.name === proposal.details.name)?.enabled === true) cards.record(agent, {
231
+ callId: proposal.callId,
232
+ action: ACTION,
233
+ phase: "enabled"
234
+ });
235
+ }
236
+ /** Enable the card's plugin once; an enabled card settles unchanged, and a failed or unconfirmed one can be retried. */
237
+ async enable(agent, proposal) {
238
+ const cards = this.ctx.actionCards;
239
+ const record = (phase, data = {}) => {
240
+ const fields = Object.fromEntries(Object.entries(data).filter(([, value]) => value !== void 0));
241
+ const decision = {
242
+ callId: proposal.callId,
243
+ action: ACTION,
244
+ phase,
245
+ ...Object.keys(fields).length === 0 ? {} : { data: fields }
246
+ };
247
+ cards.record(agent, decision);
248
+ return settled(decision);
249
+ };
250
+ const previous = ownEntry(cards.view(agent.session).decisions, proposal.callId);
251
+ if (previous !== void 0 && previous.phase !== "failed" && previous.phase !== "unconfirmed") return settled(previous);
252
+ const bundle = (await this.ctx.pluginManager.listBundles()).find((item) => item.name === proposal.details.name);
253
+ if (bundle === void 0 || !eligible(bundle)) return refused("unavailable");
254
+ if (this.isStopping()) return refused("closing");
255
+ if (!this.ctx.agents.roots().includes(agent)) return refused("not-live-root");
256
+ if (bundle.enabled) return record(previous?.phase === "unconfirmed" ? "enabled" : "already-enabled");
257
+ let result;
258
+ try {
259
+ result = await Promise.race([this.ctx.pluginManager.setBundleEnabled(bundle.name, true), this.stopped.promise]);
260
+ } catch (error) {
261
+ this.ctx.logger.warn("Enabling %s was rejected: %s", bundle.name, String(error));
262
+ return record("failed", { errorCode: "operation-error" });
263
+ }
264
+ if (result === void 0) {
265
+ record("unconfirmed");
266
+ return {
267
+ status: "unconfirmed",
268
+ reason: "closing"
269
+ };
270
+ }
271
+ return record(result.application === "failed" ? "failed" : "enabled", {
272
+ application: result.application === "restart-required" ? "restart-required" : void 0,
273
+ errorCode: result.error?.code
274
+ });
275
+ }
276
+ };
277
+ })();
278
+ /**
279
+ * Register the official-plugins skill, the `enable_official_plugin` card kind and the catalog-text Remote;
280
+ * disposing the plugin stops local follow-up of pending enabling and removes all three.
281
+ * @param ctx - Owning plugin context.
282
+ */
283
+ async function apply(ctx) {
284
+ await ctx.plugin(PluginRecommendations);
285
+ }
286
+ //#endregion
287
+ export { PluginRecommendations, apply, inject, name };
@@ -0,0 +1,3 @@
1
+ /* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */
2
+
3
+ export declare const TYPERT: unknown