feat(experimental): 把个性化页面和插件推荐合并为实验性插件「个性化推荐」 (#7034)

* feat(experimental): ship the two card experiments as one Personalized recommendations bundle

@deepseek-ai/dsh-personalized-recommendations inserts the action card
service, its browser half, Personalized pages and Plugin recommendations
with their card bodies under the existing row ids. As the only layer that
declares those ids, the Plugins page addresses and switches each row,
including the two shared card rows that were read-only while two bundles
inserted them.

The two experiment packages stop being bundles; the new bundle takes their
place in the official lists and the dsh installation, with the personalized
pages icon, localized title and description, and a feature preview of both
cards. The two feature rows describe what they do on the bundle page.

* fix(experimental): drop the model reason line from plugin recommendation cards

The reason follows the language of the user's question while the catalog
description below it follows the interface language, so the two lines could
differ in language and largely repeat each other; the answer already gives
the reason. The card keeps the catalog title and description, both in the
interface language, as its details target.

* test(web): refresh the card fixtures for the Personalized recommendations bundle

Both card replays enable the new bundle, so each Session lists both card
skills, and each skill's catalog lists Personalized recommendations in place
of the two former bundles. The plugin card goldens lose the reason line.

---------

Co-authored-by: ZiyaZhang <199893125+ZiyaZhang@users.noreply.github.com>
This commit is contained in:
Ziya
2026-10-23 07:55:49 +00:00
committed by GitHub
co-authored by ZiyaZhang
parent 63021ce0b4
commit d6d51be4cb
93 changed files with 793 additions and 168 deletions
+1 -2
View File
@@ -108,8 +108,7 @@
"@deepseek-ai/dsh-experimental-tool-worktree": "workspace:*",
"@deepseek-ai/dsh-cua-bundle": "workspace:*",
"@deepseek-ai/dsh-experimental-plugin-market-bundle": "workspace:*",
"@deepseek-ai/dsh-creation-recommendations": "workspace:*",
"@deepseek-ai/dsh-plugin-recommendations": "workspace:*"
"@deepseek-ai/dsh-personalized-recommendations": "workspace:*"
},
"devDependencies": {
"@deepseek-ai/dsh-skill-badge": "workspace:*",
@@ -20,7 +20,7 @@ describe('creation recommendation after an explicit wish', () => {
beforeAll(async () => {
scaffold = await launchWebScaffold({ replayFixture: fixture, compareReplaySession: true,
profile: { packages: [{ dir: fileURLToPath(new URL('../../../packages/experimental/creation-recommendations', import.meta.url)), enabled: true }] },
profile: { packages: [{ dir: fileURLToPath(new URL('../../../packages/experimental/personalized-recommendations', import.meta.url)), enabled: true }] },
})
browser = await chromium.launch()
page = await newEnglishPage(browser)
+1 -2
View File
@@ -863,8 +863,7 @@ describe('web e2e: plugin manager', () => {
['@deepseek-ai/dsh-experimental-voice-input-bundle', 1],
['@deepseek-ai/dsh-experimental-plugin-market-bundle', 1],
['@deepseek-ai/dsh-experimental-auto-review', 1],
['@deepseek-ai/dsh-creation-recommendations', 1],
['@deepseek-ai/dsh-plugin-recommendations', 1],
['@deepseek-ai/dsh-personalized-recommendations', 1],
['@deepseek-ai/dsh-experimental-inspector-profile', 1],
['@deepseek-ai/dsh-cua-bundle', 1],
['@deepseek-ai/dsh-experimental-tool-worktree', 1],
+1 -1
View File
@@ -22,7 +22,7 @@ describe('official plugin recommendation in a conversation', () => {
beforeAll(async () => {
scaffold = await launchWebScaffold({ replayFixture: fixture, compareReplaySession: true,
profile: { packages: [{ dir: fileURLToPath(new URL('../../../packages/experimental/plugin-recommendations', import.meta.url)), enabled: true }] },
profile: { packages: [{ dir: fileURLToPath(new URL('../../../packages/experimental/personalized-recommendations', import.meta.url)), enabled: true }] },
})
browser = await chromium.launch()
page = await newEnglishPage(browser)
+1
View File
@@ -4887,6 +4887,7 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them.
| `@deepseek-ai/dsh-official-bundles` | — | [`packages/util/official-bundles/src/index.ts`](../packages/util/official-bundles/src/index.ts) |
| `@deepseek-ai/dsh-output-retention` | — | [`packages/util/output-retention/src/index.ts`](../packages/util/output-retention/src/index.ts) |
| `@deepseek-ai/dsh-package-manifest` | — | [`packages/util/package-manifest/src/index.ts`](../packages/util/package-manifest/src/index.ts) |
| `@deepseek-ai/dsh-personalized-recommendations` | — | [`packages/experimental/personalized-recommendations/src/index.ts`](../packages/experimental/personalized-recommendations/src/index.ts) |
| `@deepseek-ai/dsh-plugin-detail` | — | [`packages/util/plugin-detail/src/index.ts`](../packages/util/plugin-detail/src/index.ts) |
| `@deepseek-ai/dsh-plugin-market-index` | — | [`packages/util/plugin-market-index/src/index.ts`](../packages/util/plugin-market-index/src/index.ts) |
| `@deepseek-ai/dsh-remote-mock` | — | [`packages/test-support/remote-mock/src/index.ts`](../packages/test-support/remote-mock/src/index.ts) |
+1
View File
@@ -4889,6 +4889,7 @@ export interface Config {
| `@deepseek-ai/dsh-official-bundles` | — | [`packages/util/official-bundles/src/index.ts`](../packages/util/official-bundles/src/index.ts) |
| `@deepseek-ai/dsh-output-retention` | — | [`packages/util/output-retention/src/index.ts`](../packages/util/output-retention/src/index.ts) |
| `@deepseek-ai/dsh-package-manifest` | — | [`packages/util/package-manifest/src/index.ts`](../packages/util/package-manifest/src/index.ts) |
| `@deepseek-ai/dsh-personalized-recommendations` | — | [`packages/experimental/personalized-recommendations/src/index.ts`](../packages/experimental/personalized-recommendations/src/index.ts) |
| `@deepseek-ai/dsh-plugin-detail` | — | [`packages/util/plugin-detail/src/index.ts`](../packages/util/plugin-detail/src/index.ts) |
| `@deepseek-ai/dsh-plugin-market-index` | — | [`packages/util/plugin-market-index/src/index.ts`](../packages/util/plugin-market-index/src/index.ts) |
| `@deepseek-ai/dsh-remote-mock` | — | [`packages/test-support/remote-mock/src/index.ts`](../packages/test-support/remote-mock/src/index.ts) |
+2
View File
@@ -302,6 +302,7 @@ flowchart TD
pkg_hook_protocol["hook-protocol"]
pkg_hooks_claude_code["hooks-claude-code"]
pkg_hooks_codex["hooks-codex"]
pkg_personalized_recommendations["personalized-recommendations"]
pkg_plugin_recommendations["plugin-recommendations"]
pkg_session_title_all_prompts_llm["session-title-all-prompts-llm"]
pkg_skill_badge["skill-badge"]
@@ -1597,6 +1598,7 @@ flowchart TD
| [`experimental-terminal-bundle`](../packages/experimental/terminal-bundle) | `experimental` | — |
| [`experimental-voice-input-bundle`](../packages/experimental/voice-input-bundle) | `experimental` | — |
| [`experimental-webworker-packer`](../packages/experimental/webworker-packer) | `experimental` | — |
| [`personalized-recommendations`](../packages/experimental/personalized-recommendations) | `experimental` | — |
| [`client-ui-cordis`](../packages/extensions/ui-cordis) | `extensions` | — |
| [`cordis-client-runner`](../packages/extensions/cordis-client-runner) | `extensions` | — |
| [`host-directory-picker`](../packages/host/directory-picker) | `host` | — |
+2
View File
@@ -304,6 +304,7 @@ flowchart TD
pkg_hook_protocol["hook-protocol"]
pkg_hooks_claude_code["hooks-claude-code"]
pkg_hooks_codex["hooks-codex"]
pkg_personalized_recommendations["personalized-recommendations"]
pkg_plugin_recommendations["plugin-recommendations"]
pkg_session_title_all_prompts_llm["session-title-all-prompts-llm"]
pkg_skill_badge["skill-badge"]
@@ -1599,6 +1600,7 @@ flowchart TD
| [`experimental-terminal-bundle`](../packages/experimental/terminal-bundle) | `experimental` | — |
| [`experimental-voice-input-bundle`](../packages/experimental/voice-input-bundle) | `experimental` | — |
| [`experimental-webworker-packer`](../packages/experimental/webworker-packer) | `experimental` | — |
| [`personalized-recommendations`](../packages/experimental/personalized-recommendations) | `experimental` | — |
| [`client-ui-cordis`](../packages/extensions/ui-cordis) | `extensions` | — |
| [`cordis-client-runner`](../packages/extensions/cordis-client-runner) | `extensions` | — |
| [`host-directory-picker`](../packages/host/directory-picker) | `host` | — |
+4 -4
View File
@@ -9,8 +9,8 @@
en: 6cc2cd9ced70bb09
zh: 1c0e6cb4ba2ed637
/tool-schema-catalog/tool-package-map:
en: c089eeb9ebe832b1
zh: 97f6ac552abb55e3
en: 4d42222acd39d083
zh: 8153a5065bf2ebd6
/tool-schema-catalog/deepseek-ai-dsh-plugin-manager:
en: e0143916bb634762
zh: e0143916bb634762
@@ -60,8 +60,8 @@
en: 60c9df6ffda98a1d
zh: 60c9df6ffda98a1d
/tool-schema-catalog/deepseek-ai-dsh-action-cards/propose-action:
en: c289411d447b015a
zh: 3a770388e90df4df
en: 633c32b6df889830
zh: b88ea00b2033c11b
/tool-schema-catalog/deepseek-ai-dsh-tool-ask-user:
en: e713a4a11a15d13c
zh: e713a4a11a15d13c
+2 -2
View File
@@ -19,7 +19,7 @@ This table connects model-visible tool names to the plugin package and service s
| `@deepseek-ai/dsh-plugin-market` | `plugin_market` | `ctx.tools`, `ctx.pluginMarket` | `tool/call`, `tool/result` | - | - |
| `@deepseek-ai/dsh-mcp-resources` | `list_mcp_resource_templates`, `list_mcp_resources`, `read_mcp_resource` | `ctx.tools`, `ctx.mcpResources` | `tool/call`, `tool/result` | - | - |
| `@deepseek-ai/dsh-experimental-browser-use-stagehand-native` | `stagehand_act`, `stagehand_extract`, `stagehand_navigate`, `stagehand_observe`, `stagehand_screenshot`, `stagehand_tabs` | `ctx.browserUse`, `ctx.agents`, `ctx.tools`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | - |
| `@deepseek-ai/dsh-action-cards` | `propose_action` | `ctx.tools`, `ctx.agents`, `ctx.sessionProjections`, `ctx.remoteAccess` | `tool/call`, `tool/result`, `plugin:action-cards/decision`, `user/message` | - | Declared only to an Agent that loaded the skill of a registered card kind; the kind packages register their actions, and the Personalized pages and Plugin recommendations bundles supply the shipped kinds. |
| `@deepseek-ai/dsh-action-cards` | `propose_action` | `ctx.tools`, `ctx.agents`, `ctx.sessionProjections`, `ctx.remoteAccess` | `tool/call`, `tool/result`, `plugin:action-cards/decision`, `user/message` | - | Declared only to an Agent that loaded the skill of a registered card kind; the kind packages register their actions, and the Personalized recommendations bundle supplies the shipped kinds. |
| `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`, `ctx.userQuestions` | `tool/call`, `tool/result after an answer or timeout`, `late user/message` | - | ask_user_question keeps the original blocking behavior by default; set `mode: timed` to opt into a foreground timeout and pending result while the question remains answerable. In timed mode, `timeout: -1` keeps that call blocking indefinitely. |
| `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`, `ctx.tools`, `ctx.ptcRuntime`, `ctx.systemPrompt` | `tool/call`, `one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`, `tool/result` | - | The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program. |
| `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agents`, `ctx.userQuestions (execution time, opportunistic)` | `tool/call`, `plan/mode inactive on an approved review`, `tool/result` | - | exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-questions seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary. |
@@ -539,7 +539,7 @@ Show the user one optional action card under this answer. Call only as the skill
Source: [`packages/experimental/action-cards/src/index.ts`](../packages/experimental/action-cards/src/index.ts)
Declared only to an Agent that loaded the skill of a registered card kind; the kind packages register their actions, and the Personalized pages and Plugin recommendations bundles supply the shipped kinds.
Declared only to an Agent that loaded the skill of a registered card kind; the kind packages register their actions, and the Personalized recommendations bundle supplies the shipped kinds.
<a id="deepseek-aidsh-tool-ask-user"></a>
+2 -2
View File
@@ -23,7 +23,7 @@
| `@deepseek-ai/dsh-plugin-market` | `plugin_market` | `ctx.tools`, `ctx.pluginMarket` | `tool/call`, `tool/result` | - | - |
| `@deepseek-ai/dsh-mcp-resources` | `list_mcp_resource_templates`, `list_mcp_resources`, `read_mcp_resource` | `ctx.tools`, `ctx.mcpResources` | `tool/call`, `tool/result` | - | - |
| `@deepseek-ai/dsh-experimental-browser-use-stagehand-native` | `stagehand_act`、`stagehand_extract`、`stagehand_navigate`、`stagehand_observe`、`stagehand_screenshot`、`stagehand_tabs` | `ctx.browserUse`、`ctx.agents`、`ctx.tools`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | - |
| `@deepseek-ai/dsh-action-cards` | `propose_action` | `ctx.tools`、`ctx.agents`、`ctx.sessionProjections`、`ctx.remoteAccess` | `tool/call`、`tool/result`、`plugin:action-cards/decision`、`user/message` | - | 只向加载过已注册卡片种类 Skill 的 Agent 声明;各种类的包注册自己的动作,个性化页面和插件推荐两个 Bundle 提供随产品发布的种类。 |
| `@deepseek-ai/dsh-action-cards` | `propose_action` | `ctx.tools`、`ctx.agents`、`ctx.sessionProjections`、`ctx.remoteAccess` | `tool/call`、`tool/result`、`plugin:action-cards/decision`、`user/message` | - | 只向加载过已注册卡片种类 Skill 的 Agent 声明;各种类的包注册自己的动作,个性化推荐 Bundle 提供随产品发布的种类。 |
| `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`、`ctx.userQuestions` | `tool/call`、`tool/result after an answer or timeout`、`late user/message` | - | ask_user_question 默认保持原有阻塞行为;设置 `mode: timed` 后才启用前台超时与 pending 结果,同时问题仍可回答;timed 模式内 `timeout: -1` 让本次调用无限期阻塞。 |
| `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`、`ctx.tools`、`ctx.ptcRuntime`、`ctx.systemPrompt` | `tool/call`、`one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`、`tool/result` | - | PTC 执行器只向模型公开 run_code,并在运行时语言生成的 SDK 中声明其他可见能力。程序绑定按提交顺序进入共享的受守卫工具流水线;并行安全的函数体最多重叠 maxParallelSubCalls 个,嵌套结果保持与外层程序关联。 |
| `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`、`ctx.systemPrompt`、`ctx.agents`、`ctx.userQuestions (execution time, opportunistic)` | `tool/call`、`plan/mode inactive on an approved review`、`tool/result` | - | 规划未激活时,exit_plan_mode 仍保留在面向模型的 schema 中,这样状态转换不会在规划策略变更之外额外造成工具目录变动。其执行路径会拒绝规划模式之外的调用;在规划模式下,它通过用户交互 seam 提交计划(批准/根据反馈继续规划),批准后会在步骤边界记录规划模式已停用。 |
@@ -543,7 +543,7 @@
来源:[`packages/experimental/action-cards/src/index.ts`](../packages/experimental/action-cards/src/index.ts)
只向加载过已注册卡片种类 Skill 的 Agent 声明;各种类的包注册自己的动作,个性化页面和插件推荐两个 Bundle 提供随产品发布的种类。
只向加载过已注册卡片种类 Skill 的 Agent 声明;各种类的包注册自己的动作,个性化推荐 Bundle 提供随产品发布的种类。
<a id="deepseek-aidsh-tool-ask-user"></a>
@@ -1239,8 +1239,7 @@ describe('PluginManagerPage', () => {
it.each([
'@deepseek-ai/dsh-experimental-session-titles-bundle',
'@deepseek-ai/dsh-creation-recommendations',
'@deepseek-ai/dsh-plugin-recommendations',
'@deepseek-ai/dsh-personalized-recommendations',
])('returns %s details to the secondary page and retains it across React remounts', (name) => {
const b = renderTab({ packages: [pkg({ name, official: true, enabled: false })] })
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
@@ -1258,8 +1257,7 @@ describe('PluginManagerPage', () => {
it.each([
'@deepseek-ai/dsh-experimental-tool-worktree',
'@deepseek-ai/dsh-creation-recommendations',
'@deepseek-ai/dsh-plugin-recommendations',
'@deepseek-ai/dsh-personalized-recommendations',
])('pins enabled %s onto the main list and removes it when disabled', (first) => {
const team = '@deepseek-ai/dsh-experimental-agent-team-profile'
const second = '@deepseek-ai/dsh-experimental-cot-translation-bundle'
+2 -2
View File
@@ -18,8 +18,8 @@
en: b55aeb927737b90d
zh: 0b215fcb5c2c7908
/packages-experimental/packages:
en: 4d32c0a6c009a2ca
zh: 47bf9acd64100263
en: 895f169f8f3ebbc7
zh: 62786c4876d97055
/packages-experimental/related-documentation:
en: 1cb9d3448d44e48d
zh: cd326d70a746b896
+1
View File
@@ -42,6 +42,7 @@ Promotion requires a named product role, documented public behavior and limitati
| [`client-ui-creation-recommendations`](client-ui-creation-recommendations/README.md) | Personalized page card body with location selection and undo | — |
| [`plugin-recommendations`](plugin-recommendations/README.md) | Explicit-need official plugin recommendations in conversations | `ctx.pluginRecommendations` |
| [`client-ui-plugin-recommendations`](client-ui-plugin-recommendations/README.md) | Official plugin card body with catalog text and enabling | — |
| [`personalized-recommendations`](personalized-recommendations/README.md) | Default-disabled bundle of personalized pages, plugin recommendations and their action cards | — |
| [`tool-session-query`](tool-session-query/README.md) | Workspace-authorized model session search, trace, and event reads | — |
| [`session-search`](session-search/README.md) | Optional experimental global session search tools | — |
| [`ralph-bundle`](ralph-bundle/README.md) | Optional repeated delegation with an isolated workflow engine | — |
+1
View File
@@ -42,6 +42,7 @@ kind: "package-group"
| [`client-ui-creation-recommendations`](client-ui-creation-recommendations/README.zh.md) | 个性化页面卡片正文、位置选择和撤销 | — |
| [`plugin-recommendations`](plugin-recommendations/README.zh.md) | 在对话中针对明确需求推荐官方插件 | `ctx.pluginRecommendations` |
| [`client-ui-plugin-recommendations`](client-ui-plugin-recommendations/README.zh.md) | 官方插件卡片正文:目录文本与启用 | — |
| [`personalized-recommendations`](personalized-recommendations/README.zh.md) | 默认禁用的个性化页面、插件推荐及其操作卡片组合 | — |
| [`tool-session-query`](tool-session-query/README.zh.md) | 按工作区授权的模型会话搜索、追踪与事件读取 | — |
| [`session-search`](session-search/README.zh.md) | 可选实验性全局会话搜索工具 | — |
| [`ralph-bundle`](ralph-bundle/README.zh.md) | 带隔离 workflow 引擎的可选重复委派 | — |
@@ -15,8 +15,8 @@
en: d152484eb41ac6b4
zh: b00dddf4c88f0a45
/deepseek-ai-dsh-action-cards/use-this-package:
en: 4e3a256d5aa6d29b
zh: b89ffa23ebb49e7e
en: e239e255a6692f95
zh: ae8e359e37e484b3
/deepseek-ai-dsh-action-cards/use-this-package/register-a-card-kind:
en: 8432c02ef18eda11
zh: 6d43a0a32edd7e30
@@ -60,8 +60,8 @@
en: 58785543e2428dba
zh: 4b082f77b3b2f482
/deepseek-ai-dsh-action-cards/known-limitations-and-deferred-work:
en: 163aa0c2a6b7e3f8
zh: 26cdcae33579114a
en: 8e3eba4e9fda82cb
zh: afaf74aad9a01533
/deepseek-ai-dsh-action-cards/known-limitations-and-deferred-work/dev-note:
en: e67f633dc6fe7773
zh: bd8a8936229e035e
+1 -2
View File
@@ -25,7 +25,7 @@ An experiment can offer one optional action below an answer, such as creating a
<a id="use-this-package"></a>
## Use this package
The [Personalized pages](../creation-recommendations/README.md) and [Plugin recommendations](../plugin-recommendations/README.md) bundles each insert this service and its [browser half](../client-ui-action-cards/README.md); enabling either bundle enables both. The service has one configuration field:
The [Personalized recommendations](../personalized-recommendations/README.md) bundle inserts this service and its [browser half](../client-ui-action-cards/README.md). The service has one configuration field:
| Field | Default | Meaning |
|---|---|---|
@@ -125,7 +125,6 @@ The notice is appended after the request's other new context and never rewrites
- Only one card is offered per turn across all kinds. A later card of any kind replaces every unstarted earlier card, so a second kind's card replaces an unstarted card of the first.
- Refusal reasons of user actions are plain strings by design: the service defines `closing`, `not-live-root`, `unknown`, `unavailable`, `access-denied` and `read-only`, each kind adds its own, and a browser body names the reasons it knows and treats others as unavailable.
- Each kind validates `details` on both sides, with its Host schema when the model proposes and before each action, and with its browser body's own schema, so the two schemas change together.
- The Personalized pages and Plugin recommendations bundles both insert the rows of this service and its browser half, and with both enabled the Loader keeps one entry per row id. Two optional bundles cannot yet share a dependency row addressably: while both are enabled, the Plugins page cannot address the two shared rows (`action-cards`, `client-ui-action-cards`), so they cannot be switched or configured individually. Use the bundle switches; `queuedMessageLimit` can then be changed only in the profile's `cordis.patch.yml`.
<a id="dev-note"></a>
### Dev Note
@@ -25,7 +25,7 @@ kind: "package-reference"
<a id="use-this-package"></a>
## 使用本包
[个性化页面](../creation-recommendations/README.zh.md)和[插件推荐](../plugin-recommendations/README.zh.md)两个 Bundle 都会插入本服务及其[浏览器端](../client-ui-action-cards/README.zh.md);启用其中任一个即同时启用两者。本服务只有一个配置字段:
[个性化推荐](../personalized-recommendations/README.zh.md) Bundle 会插入本服务及其[浏览器端](../client-ui-action-cards/README.zh.md)。本服务只有一个配置字段:
| 字段 | 默认值 | 含义 |
|---|---|---|
@@ -125,7 +125,6 @@ Latest status of action cards from user actions and background work. These are s
- 所有种类合计每轮只展示一张卡片。任何种类的新卡片都会替换此前所有尚未开始的卡片,因此第二个种类的卡片会替换第一个种类尚未开始的卡片。
- 用户操作的拒绝原因有意设计为普通字符串:服务定义 `closing`、`not-live-root`、`unknown`、`unavailable`、`access-denied` 和 `read-only`,各种类可以添加自己的原因,浏览器端正文为已知原因显示文案,其余按 unavailable 处理。
- 每个种类在两端各自校验 `details`:Host 端的 schema 在模型提议时和每次操作前校验,浏览器端正文用自己的 schema 校验,因此两个 schema 要一起修改。
- 个性化页面和插件推荐两个 Bundle 都会插入本服务及其浏览器端的行,两个都启用时 Loader 对每个行 id 只保留一个条目。平台目前不支持两个可选 Bundle 以可定位的方式共享同一依赖行:两个都启用时,插件页无法定位这两条共享行(`action-cards`、`client-ui-action-cards`),不能单独开关或配置它们。请使用 Bundle 开关;此时 `queuedMessageLimit` 只能在 profile 的 `cordis.patch.yml` 中修改。
<a id="dev-note"></a>
### 开发备注
@@ -15,8 +15,8 @@
en: d152484eb41ac6b4
zh: b00dddf4c88f0a45
/deepseek-ai-dsh-client-ui-action-cards/use-this-package:
en: 163d385e91a5df38
zh: 95bdde9e844cca51
en: 717a53d2170d253b
zh: 4e52852690601371
/deepseek-ai-dsh-client-ui-action-cards/use-this-package/register-a-card-body:
en: 9ec9f9f8f3165d3e
zh: 2d8154ec2fe0c9ab
@@ -25,7 +25,7 @@ Users see an optional action card below a completed answer: its title, a brief t
<a id="use-this-package"></a>
## Use this package
Enable the [Personalized pages](../creation-recommendations/README.md) or [Plugin recommendations](../plugin-recommendations/README.md) bundle; each inserts this package beside its [Host service](../action-cards/README.md). This browser contribution has no configuration fields.
Enable the [Personalized recommendations](../personalized-recommendations/README.md) bundle, which inserts this package beside its [Host service](../action-cards/README.md). This browser contribution has no configuration fields.
### Register a card body
@@ -25,7 +25,7 @@ kind: "package-reference"
<a id="use-this-package"></a>
## 使用本包
启用[个性化页面](../creation-recommendations/README.zh.md)或[插件推荐](../plugin-recommendations/README.zh.md) Bundle,它们都会在本包旁插入对应的 [Host 服务](../action-cards/README.zh.md)。本浏览器组件没有配置字段。
启用[个性化推荐](../personalized-recommendations/README.zh.md) Bundle,它会在本包旁插入对应的 [Host 服务](../action-cards/README.zh.md)。本浏览器组件没有配置字段。
### 注册卡片正文
@@ -15,8 +15,8 @@
en: d152484eb41ac6b4
zh: b00dddf4c88f0a45
/deepseek-ai-dsh-client-ui-creation-recommendations/use-this-package:
en: 5f3f7e9eea09ae0c
zh: 378acca8f793cf34
en: 328d7d1d81bb2830
zh: 425df912753bae83
/deepseek-ai-dsh-client-ui-creation-recommendations/use-this-package/outcomes-and-recovery:
en: 5a3df9cd58832f91
zh: 2068dca0a62394fe
@@ -25,7 +25,7 @@ Users see one creation proposal below the completed answer. Short plans appear i
<a id="use-this-package"></a>
## Use this package
Enable the [Personalized pages bundle](../creation-recommendations/README.md). This browser contribution has no configuration fields; it fills the shared [action card frame](../client-ui-action-cards/README.md) for `create_personalized_page` cards.
Enable the [Personalized recommendations bundle](../personalized-recommendations/README.md). This browser contribution has no configuration fields; it fills the shared [action card frame](../client-ui-action-cards/README.md) for `create_personalized_page` cards.
### Outcomes and recovery
@@ -25,7 +25,7 @@ kind: "package-reference"
<a id="use-this-package"></a>
## 使用本包
启用[个性化页面 Bundle](../creation-recommendations/README.zh.md)。本浏览器组件没有配置字段;它为 `create_personalized_page` 卡片填充共享的[操作卡片框架](../client-ui-action-cards/README.zh.md)。
启用[个性化推荐 Bundle](../personalized-recommendations/README.zh.md)。本浏览器组件没有配置字段;它为 `create_personalized_page` 卡片填充共享的[操作卡片框架](../client-ui-action-cards/README.zh.md)。
### 操作结果与恢复
@@ -9,17 +9,17 @@
en: 54f686f0de0dc75d
zh: 33341df7cfb4fa5c
/deepseek-ai-dsh-client-ui-plugin-recommendations/summary:
en: eaf579aa61aba6e7
zh: 8179f857e803f236
en: a4484eb034e7311a
zh: 34e47149ecf23d67
/deepseek-ai-dsh-client-ui-plugin-recommendations/table-of-contents:
en: d152484eb41ac6b4
zh: b00dddf4c88f0a45
/deepseek-ai-dsh-client-ui-plugin-recommendations/use-this-package:
en: 7a588dae45ca8bef
zh: 9ebf6d4b9af830c3
en: 10c47fd420b1f232
zh: 7094c3b465978372
/deepseek-ai-dsh-client-ui-plugin-recommendations/understand-the-implementation:
en: aa599e92c7a36c5b
zh: d95249f0fa9cbbcb
en: 1b20e5e631381ab1
zh: a3f0bbc3dc14001d
/deepseek-ai-dsh-client-ui-plugin-recommendations/further-exploration:
en: 4fd18141ccbba60b
zh: 137a74a945ac3f23
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
## Summary
Users see one plugin recommendation below the completed answer, with the plugin's current title and description in the interface language and the Agent's reason. The title, reason and description form one target that highlights on hover; clicking it opens that plugin's details in Plugins without enabling it or changing the current Session. Enable and Not now remain separate choices. Decisions and the details link remain available after reconnecting.
Users see one plugin recommendation below the completed answer, with the plugin's current title and description in the interface language. The title and description form one target that highlights on hover; clicking it opens that plugin's details in Plugins without enabling it or changing the current Session. Enable and Not now remain separate choices. Decisions and the details link remain available after reconnecting.
## Table of Contents
@@ -25,7 +25,7 @@ Users see one plugin recommendation below the completed answer, with the plugin'
<a id="use-this-package"></a>
## Use this package
Enable the [Plugin recommendations bundle](../plugin-recommendations/README.md). This browser contribution has no configuration fields; it fills the shared [action card frame](../client-ui-action-cards/README.md) for `enable_official_plugin` cards.
Enable the [Personalized recommendations bundle](../personalized-recommendations/README.md). This browser contribution has no configuration fields; it fills the shared [action card frame](../client-ui-action-cards/README.md) for `enable_official_plugin` cards.
-----
@@ -35,7 +35,7 @@ Enable the [Plugin recommendations bundle](../plugin-recommendations/README.md).
<details>
<summary>Implementation internals — click to expand</summary>
The plugin mounts its catalog-text Remote, then registers its body with the shared card frame, which places the card after its Turn and supplies the card, its recorded decision and card-bound actions. The body asks the Host for the plugin's current catalog text and selection and renders the header with the Official badge, the details target, status, and Enable and Not now through the frame's action. The details target is named View details with the plugin title, and the reason and catalog description remain its accessible description. A refused lookup shows its reason and a lost reply says the details could not be loaded; both offer Not now and Try again, which repeats the lookup, but no Enable. A recorded outcome stays as recorded even after its plugin leaves the catalog. While an action runs, only its own button reads Working, and both stay disabled. The plugin keeps returned catalog text by plugin name for its lifetime, so a revisited card renders it at once while its own lookup refreshes the selection. Until a plugin's first lookup returns, the card shows the proposed title, a placeholder description line and the disabled actions, so its layout holds. The Remote, the body registration and its dictionaries share one plugin lifetime.
The plugin mounts its catalog-text Remote, then registers its body with the shared card frame, which places the card after its Turn and supplies the card, its recorded decision and card-bound actions. The body asks the Host for the plugin's current catalog text and selection and renders the header with the Official badge, the details target, status, and Enable and Not now through the frame's action. The details target is named View details with the plugin title, and the catalog description remains its accessible description. A refused lookup shows its reason and a lost reply says the details could not be loaded; both offer Not now and Try again, which repeats the lookup, but no Enable. A recorded outcome stays as recorded even after its plugin leaves the catalog. While an action runs, only its own button reads Working, and both stay disabled. The plugin keeps returned catalog text by plugin name for its lifetime, so a revisited card renders it at once while its own lookup refreshes the selection. Until a plugin's first lookup returns, the card shows the proposed title, a placeholder description line and the disabled actions, so its layout holds. The Remote, the body registration and its dictionaries share one plugin lifetime.
Cards use the application font, a distinct title and compact body scale, and a separate action footer. The built entry supplies generated Remote descriptors to the source-tested mount lifecycle.
@@ -9,7 +9,7 @@ kind: "package-reference"
## 概述
用户在完整回答下方看到一张插件推荐卡片,显示插件当前的界面语言标题、描述和 Agent 给出的理由。标题、理由和描述是同一个点击区域,悬停时整体高亮;点击可打开插件页中的对应详情,不会启用插件或改变当前 Session。启用和暂不需要仍是独立操作。重新连接后仍能看到之前的选择并打开详情。
用户在完整回答下方看到一张插件推荐卡片,显示插件当前的界面语言标题和描述。标题和描述是同一个点击区域,悬停时整体高亮;点击可打开插件页中的对应详情,不会启用插件或改变当前 Session。启用和暂不需要仍是独立操作。重新连接后仍能看到之前的选择并打开详情。
## 目录
@@ -25,7 +25,7 @@ kind: "package-reference"
<a id="use-this-package"></a>
## 使用本包
启用[插件推荐 Bundle](../plugin-recommendations/README.zh.md)。这个浏览器端贡献没有配置项;它为 `enable_official_plugin` 卡片填充共享的[操作卡片框架](../client-ui-action-cards/README.zh.md)。
启用[个性化推荐 Bundle](../personalized-recommendations/README.zh.md)。这个浏览器端贡献没有配置项;它为 `enable_official_plugin` 卡片填充共享的[操作卡片框架](../client-ui-action-cards/README.zh.md)。
-----
@@ -35,7 +35,7 @@ kind: "package-reference"
<details>
<summary>实现细节 — 点击展开</summary>
插件先挂载读取目录文本的 Remote,再向共享的卡片框架注册正文;框架把卡片放在所在回合之后,并提供卡片、已记录的决定和绑定到卡片的操作。正文向 Host 读取插件当前的目录文本和启用状态,渲染带「官方」标记的标题栏、详情点击区域、状态,以及通过框架操作发出的启用和暂不需要。详情点击区域的无障碍名称是「查看详情」加插件标题,推荐理由和目录描述仍作为它的无障碍描述。查询被拒绝时显示原因,回复丢失时说明无法读取插件信息;两种情况都提供「暂不需要」和重新查询的「重试」,不提供启用。已记录的结果保持不变,插件之后离开目录也一样。操作进行中只有被点击的按钮显示「正在处理…」,两个按钮都禁用。插件在自身生命周期内按插件名记住读到的目录文本,再次进入会话时卡片立即显示,同时卡片自己的查询会刷新启用状态。某个插件的第一次查询返回前,卡片显示提议的标题、一行描述占位和禁用的按钮,布局不跳动。Remote、正文注册和词典共用同一个插件生命周期。
插件先挂载读取目录文本的 Remote,再向共享的卡片框架注册正文;框架把卡片放在所在回合之后,并提供卡片、已记录的决定和绑定到卡片的操作。正文向 Host 读取插件当前的目录文本和启用状态,渲染带「官方」标记的标题栏、详情点击区域、状态,以及通过框架操作发出的启用和暂不需要。详情点击区域的无障碍名称是「查看详情」加插件标题,目录描述仍作为它的无障碍描述。查询被拒绝时显示原因,回复丢失时说明无法读取插件信息;两种情况都提供「暂不需要」和重新查询的「重试」,不提供启用。已记录的结果保持不变,插件之后离开目录也一样。操作进行中只有被点击的按钮显示「正在处理…」,两个按钮都禁用。插件在自身生命周期内按插件名记住读到的目录文本,再次进入会话时卡片立即显示,同时卡片自己的查询会刷新启用状态。某个插件的第一次查询返回前,卡片显示提议的标题、一行描述占位和禁用的按钮,布局不跳动。Remote、正文注册和词典共用同一个插件生命周期。
卡片沿用应用字体,通过标题、正文和独立操作区区分信息层级。构建入口将生成的 Remote 描述传给可直接从源码测试的挂载流程。
@@ -4,10 +4,8 @@
.details { display: block; box-sizing: border-box; width: calc(100% + 16px); margin: -8px; padding: 8px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: inherit; font: inherit; text-align: start; cursor: pointer; transition: background-color 120ms ease; }
@media (hover: hover) { .details:hover { background: var(--dsw-alias-interactive-bg-hover); } }
.details:focus-visible { outline: none; background: var(--dsw-alias-interactive-bg-hover); box-shadow: inset 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
.reason, .description { display: block; }
.title { display: block; font-size: 16px; font-weight: 500; line-height: 1.5; overflow-wrap: anywhere; }
.reason { margin: 8px 0; font-size: 13px; line-height: 1.7; white-space: pre-wrap; overflow-wrap: anywhere; }
.description { margin: 0; font-size: 13px; line-height: 1.7; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; }
.description { display: block; margin: 8px 0 0; font-size: 13px; line-height: 1.7; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; }
.placeholder { display: inline-block; width: 50%; height: 0.8em; vertical-align: middle; border-radius: var(--dsw-radius-xs); background: var(--dsw-alias-bg-skeleton); }
.footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; padding-top: 16px; border-top: 0.5px solid var(--dsw-alias-border-l1); flex-wrap: wrap; }
.scope { font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-secondary); }
@@ -60,7 +60,7 @@ function statusKey(phase: string, data: CardData): RecommendationKey | undefined
}
/**
* Display one plugin card: its current catalog text, the model's reason, the user's choice and its outcome.
* Display one plugin card: its current catalog title and description, the user's choice and its outcome.
* Until the plugin's first catalog lookup returns, a placeholder line and the disabled actions hold the final layout.
* A refused or lost lookup leaves Not now and a retry of the lookup, but not Enable.
* @param props - card, state, actions and plugin-lifetime services.
@@ -81,7 +81,6 @@ export function PluginCardBody({
const [attempt, setAttempt] = useState(0)
const [pending, setPending] = useState<CardVerb>()
const [error, setError] = useState<RecommendationKey>()
const reasonId = useId()
const descriptionId = useId()
const phase = decision?.phase
useEffect(() => {
@@ -130,9 +129,8 @@ export function PluginCardBody({
<header className={css.header}><span>{t('heading')}</span><span className={css.badge}>{t('official')}</span></header>
<div className={css.body} aria-busy={loading || undefined}>
<button type="button" className={css.details} onClick={() => { openDetails(name) }}
aria-label={t('detailsFor', { title })} aria-describedby={available === undefined ? reasonId : `${reasonId} ${descriptionId}`}>
aria-label={t('detailsFor', { title })} aria-describedby={available === undefined ? undefined : descriptionId}>
<strong className={css.title}>{title}</strong>
<span id={reasonId} className={css.reason}>{proposal.brief}</span>
{(loading || available !== undefined) && <span id={descriptionId} className={css.description}>
{available === undefined ? <span className={css.placeholder} /> : resolveText(available.description)}
</span>}
@@ -53,15 +53,16 @@ it('keeps every dictionary aligned', () => {
for (const dictionary of [en, ja, es, fr, de, ru, pt]) expect(Object.keys(dictionary).sort()).toEqual(Object.keys(zh).sort())
})
it('shows the catalog text and reason as one details target with the official badge, and enables only on click', async () => {
it('shows the catalog title and description as one details target with the official badge, and enables only on click', async () => {
const value = props()
await shown(value)
expect(screen.getByText('推荐插件')).toBeTruthy()
expect(screen.getByText('官方')).toBeTruthy()
const details = screen.getByRole('button', { name: '查看详情:会话搜索' })
expect(details.textContent).toBe('会话搜索帮你找到上周讨论的方案检索过去的会话')
// The details label names the plugin; the reason and catalog description stay readable as its description.
expect(description(details)).toEqual(['帮你找到上周讨论的方案', '检索过去的会话'])
expect(details.textContent).toBe('会话搜索检索过去的会话')
// The details label names the plugin; the catalog description stays readable as its description. The model's brief is not shown.
expect(description(details)).toEqual(['检索过去的会话'])
expect(screen.queryByText(proposal.brief)).toBeNull()
expect(screen.getAllByRole('button').map(button => button.textContent)).toEqual([details.textContent, '暂不需要', '启用'])
expect(screen.getByText('启用后可在所有会话中使用')).toBeTruthy()
expect(value.act).not.toHaveBeenCalled()
@@ -141,7 +142,7 @@ it.each([
await shown(value)
expect(screen.getByRole('alert').textContent).toBe(message)
const details = screen.getByRole('button', { name: '查看详情:搜索旧会话' })
expect(description(details)).toEqual(['帮你找到上周讨论的方案'])
expect(description(details)).toBeUndefined()
expect(screen.queryByRole('button', { name: '启用' })).toBeNull()
fireEvent.click(screen.getByRole('button', { name: '暂不需要' }))
await waitFor(() => { expect(value.act).toHaveBeenCalledExactlyOnceWith('dismiss') })
@@ -169,7 +170,7 @@ it('holds the description and action lines while the first catalog lookup is pen
expect(body).not.toBeNull()
const details = screen.getByRole('button', { name: '查看详情:搜索旧会话' })
expect(within(details).getByText('搜索旧会话')).toBeTruthy()
expect(description(details)).toEqual(['帮你找到上周讨论的方案'])
expect(description(details)).toBeUndefined()
expect(container.querySelectorAll('button span > span')).toHaveLength(1)
expect(screen.getByText('启用后可在所有会话中使用')).toBeTruthy()
for (const name of ['暂不需要', '启用']) expect(screen.getByRole('button', { name }).hasAttribute('disabled')).toBe(true)
@@ -3,8 +3,8 @@
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/experimental/creation-recommendations/README.md
/:
en: 08688503db441fbf
zh: 855dbe0b9ce53ab9
en: dca7dd27c5f2c369
zh: 00d34e3837bed75a
/deepseek-ai-dsh-creation-recommendations:
en: f2cefc8c9ad1d38f
zh: 9d3a5dc7ac8e2329
@@ -15,10 +15,10 @@
en: d152484eb41ac6b4
zh: b00dddf4c88f0a45
/deepseek-ai-dsh-creation-recommendations/use-this-package:
en: cf57a8f8347c7aba
zh: f8a2e23006c8c09d
en: e5e313410faf1ec0
zh: 0683eb113eb52c96
/deepseek-ai-dsh-creation-recommendations/use-this-package/outcomes-and-recovery:
en: 0ab58b8291b06ba8
en: 1d96f4897b1a5a28
zh: 25ca08bc167d1e4a
/deepseek-ai-dsh-creation-recommendations/understand-the-implementation:
en: 825aa7414c0a8d90
@@ -1,6 +1,6 @@
---
description: "Create persistent removable interface plugins after an explicit user choice."
kind: "package-bundle"
kind: "package-reference"
---
# @deepseek-ai/dsh-creation-recommendations
@@ -25,7 +25,7 @@ For an explicit interface request, a clear implied viewing or editing need, or a
<a id="use-this-package"></a>
## Use this package
Open Plugins → More → Experimental plugins and enable Personalized pages. Shipped profiles leave it disabled. Enabling pins it to the main Plugins list; disabling returns it to the secondary page only. The 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 make the card tool refuse the current turn's card. An explicit user location takes precedence; otherwise the Agent recommends a location by scope and interaction:
Open Plugins → More → Experimental plugins and enable [Personalized recommendations](../personalized-recommendations/README.md); its Personalized pages row is this package. Shipped profiles leave it 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 make the card tool refuse the current turn's card. An explicit user location takes precedence; otherwise the Agent recommends a location by scope and interaction:
| Location | Use |
|:--|:--|
@@ -47,7 +47,7 @@ For multiple independent needs, guidance selects one page proposal for the answe
The card appears after the Turn of its accepted call ends. An unstarted proposal stays available across later human messages, including follow-ups about its plan. The next proposal in the same conversation replaces it: the old card shows Replaced without creation controls, and the Host refuses its stale clicks, so one unstarted card exists per conversation. Refreshing, switching views and background notices do not change a card. Already-started work keeps its progress and retry controls. Replacement is derived from the later proposal, not a new persisted decision record. While the user has queued `queuedMessageLimit` or more messages for later turns, the card tool refuses the proposal, so no card is offered.
Creation requires a live root conversation, permission to modify the profile, and a Session sandbox mode other than read-only: the Creator inherits that mode, so a read-only Session refuses Create and leaves the card unstarted. Cancel is available during generation; retry waits for the child’s cleanup. Once installation starts, the card shows Installing and lets the accepted operation finish. Closing or leaving the conversation does not uninstall a completed plugin. Receipts survive reconnects; interrupted or uncertain runs are reconciled with the installed bundle before any card action, including Not now, so an uncertain run whose page was installed shows Installed with Undo instead of being dismissed. Failed removal preserves the existing receipt and its application outcome for retry. Unloading this recommendation bundle stops its Creator work and local follow-up. Plugin Manager owns accepted profile changes and may finish after reload; reopening checks the installed profile before retrying. Installed plugins remain. Recovery confirms installation only; it does not infer runtime activation from the profile’s enabled flag.
Creation requires a live root conversation, permission to modify the profile, and a Session sandbox mode other than read-only: the Creator inherits that mode, so a read-only Session refuses Create and leaves the card unstarted. Cancel is available during generation; retry waits for the child’s cleanup. Once installation starts, the card shows Installing and lets the accepted operation finish. Closing or leaving the conversation does not uninstall a completed plugin. Receipts survive reconnects; interrupted or uncertain runs are reconciled with the installed bundle before any card action, including Not now, so an uncertain run whose page was installed shows Installed with Undo instead of being dismissed. Failed removal preserves the existing receipt and its application outcome for retry. Unloading this plugin stops its Creator work and local follow-up. Plugin Manager owns accepted profile changes and may finish after reload; reopening checks the installed profile before retrying. Installed plugins remain. Recovery confirms installation only; it does not infer runtime activation from the profile’s enabled flag.
Undo uninstalls the plugin and keeps its files. On the removed card, Restore validates the retained profile copy again and reinstalls it without running the Creator; it needs the same live root conversation and profile permission as Create. If that copy is missing or fails validation, Restore is refused and a new proposal is needed.
@@ -1,6 +1,6 @@
---
description: "经用户明确选择后创建可持久保留、可移除的界面插件。"
kind: "package-bundle"
kind: "package-reference"
---
# @deepseek-ai/dsh-creation-recommendations
@@ -25,7 +25,7 @@ kind: "package-bundle"
<a id="use-this-package"></a>
## 使用本包
打开插件 → 更多 → 实验性插件,启用个性化页面。内置配置档案默认不启用此插件。启用后在插件一级页显示,关闭后仅保留在二级页。该 Bundle 同时启用共享的[操作卡片服务](../action-cards/README.zh.md)及其浏览器端;该服务的 `queuedMessageLimit` 规定为下一轮排队的消息达到多少条时,卡片工具拒绝本轮的卡片。用户明确指定的位置优先;否则 Agent 根据作用范围和交互方式推荐:
打开插件 → 更多 → 实验性插件,启用[个性化推荐](../personalized-recommendations/README.zh.md),其中的个性化页面行就是本包。内置配置档案默认不启用此插件。启用后在插件一级页显示,关闭后仅保留在二级页。个性化推荐 Bundle 同时启用共享的[操作卡片服务](../action-cards/README.zh.md)及其浏览器端;该服务的 `queuedMessageLimit` 规定为下一轮排队的消息达到多少条时,卡片工具拒绝本轮的卡片。用户明确指定的位置优先;否则 Agent 根据作用范围和交互方式推荐:
| 位置 | 用途 |
|:--|:--|
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Personalisierte Seiten",
"description": "Planen Sie im Gespräch eine personalisierte Seite, wenn vorhandene Funktionen einen konkreten Bedarf nicht abdecken. Klicken Sie auf Erstellen, um ein eigenständiges Plugin zu erzeugen, das Sie weiter verwenden oder entfernen können."
"description": "Schlägt unter der Antwort eine Seite oder ein kleines Tool zur dauerhaften Nutzung vor; nach einem Klick auf Erstellen wird es im Hintergrund als Plugin-Seite erstellt, die Sie abbrechen, rückgängig machen und wiederherstellen können. Die Erstellung dauert einige Minuten und verbraucht viele Token."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Personalized pages",
"description": "Plan a personalized page in the conversation when existing features do not meet a specific need. Click Create to generate an independent plugin you can keep using or remove."
"description": "Proposes a page or small tool you can keep, below the answer; after you click Create it is built in the background as a plugin page that you can cancel, undo and restore. A build takes minutes and many tokens."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Páginas personalizadas",
"description": "Planifica una página personalizada en la conversación cuando las funciones existentes no cubren una necesidad concreta. Haz clic en Crear para generar un complemento independiente que puedes seguir usando o eliminar."
"description": "Propone, debajo de la respuesta, una página o una pequeña herramienta para conservar; al hacer clic en Crear, se genera en segundo plano como una página de complemento que puedes cancelar, deshacer y restaurar. La generación tarda unos minutos y consume muchos tokens."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Pages personnalisées",
"description": "Planifiez une page personnalisée dans la conversation lorsque les fonctionnalités existantes ne répondent pas à un besoin précis. Cliquez sur Créer pour générer un plugin indépendant que vous pouvez continuer à utiliser ou supprimer."
"description": "Propose sous la réponse une page ou un petit outil à conserver ; après un clic sur Créer, il est généré en arrière-plan sous forme de page de plugin que vous pouvez interrompre, annuler et restaurer. La génération prend quelques minutes et consomme beaucoup de jetons."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "パーソナルページ",
"description": "既存の機能では明確な要望を満たせないとき、会話の中でパーソナルページを計画します。作成をクリックすると独立したプラグインが生成され、使い続けることも元に戻すこともできます。"
"description": "長く使えるページや小さなツールが欲しいときに、回答の下で案を提案します。「作成」をクリックするとプラグインページとしてバックグラウンドで生成され、キャンセル、元に戻す、復元ができます。生成には数分かかり、多くのトークンを消費します。"
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Páginas personalizadas",
"description": "Planeje uma página personalizada na conversa quando os recursos existentes não atendem a uma necessidade específica. Clique em Criar para gerar um plug-in independente que você pode continuar usando ou remover."
"description": "Propõe, abaixo da resposta, uma página ou uma pequena ferramenta para manter; depois que você clica em Criar, ela é gerada em segundo plano como uma página de plug-in que você pode cancelar, desfazer e restaurar. A geração leva alguns minutos e consome muitos tokens."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Персональные страницы",
"description": "Спланируйте в разговоре персональную страницу, когда существующие функции не покрывают конкретную потребность. Нажмите «Создать», чтобы сгенерировать независимый плагин, который можно продолжать использовать или удалить."
"description": "Предлагает под ответом страницу или небольшой инструмент для постоянного использования; после нажатия «Создать» он создается в фоне как страница-плагин, которую можно прервать, отменить и восстановить. Создание занимает несколько минут и расходует много токенов."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "个性化页面",
"description": "现有功能不能满足你的明确需求时,在对话中规划个性化页面;点击创建后生成独立插件,可持续使用或撤销。"
"description": "想要能长期使用的页面或小工具时,在回答下方提议方案;点击创建后在后台生成插件页面,可取消、撤销和恢复,生成需要几分钟并消耗较多 token。"
}
}
@@ -25,7 +25,6 @@
"types": "./lib/types/types.d.ts",
"default": "./lib/types/types.js"
},
"./cordis.patch.yml": "./cordis.patch.yml",
"./references/*.md": "./references/*.md",
"./skills/*.md": "./skills/*.md"
},
@@ -64,16 +63,10 @@
"icon.svg",
"locale/*.json",
"lib/index.js",
"cordis.patch.yml",
"references/*.md",
"skills/*.md",
"lib/types/**/*.js",
"lib/types/**/*.d.ts"
],
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
},
"icon": "./icon.svg"
}
@@ -0,0 +1,37 @@
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/experimental/personalized-recommendations/README.md
/:
en: d7cf0e812c2b84ef
zh: 959798411569393e
/deepseek-ai-dsh-personalized-recommendations:
en: 4f78733960cd89fa
zh: da27faf83f5e53bd
/deepseek-ai-dsh-personalized-recommendations/summary:
en: 5508a30d24f41f16
zh: dae436e8131b1b65
/deepseek-ai-dsh-personalized-recommendations/table-of-contents:
en: d152484eb41ac6b4
zh: ce40cab8e7534137
/deepseek-ai-dsh-personalized-recommendations/use-this-package:
en: 391782dd00ef7700
zh: 346ae3ac63482b92
/deepseek-ai-dsh-personalized-recommendations/understand-the-implementation:
en: e386724a57fffa9b
zh: 865f1db569dcafc8
/deepseek-ai-dsh-personalized-recommendations/further-exploration:
en: 0e308f273a226d6c
zh: 2b13028f9abb32e2
/deepseek-ai-dsh-personalized-recommendations/model-experience:
en: 8c3ddb0d6cbd0d6b
zh: 603f48a0df45ee7d
/deepseek-ai-dsh-personalized-recommendations/model-experience/kv-cache-effect:
en: 9ee776745fc68e18
zh: 126dcac4b16b31dd
/deepseek-ai-dsh-personalized-recommendations/known-limitations-and-deferred-work:
en: 965570522f569e46
zh: 63a608a10cdd6b80
/deepseek-ai-dsh-personalized-recommendations/known-limitations-and-deferred-work/dev-note:
en: e67f633dc6fe7773
zh: 8ec3e73528ddb1a6
@@ -0,0 +1,88 @@
---
description: "Enable personalized page proposals and built-in plugin recommendations below answers with one experimental bundle."
kind: "package-bundle"
---
# @deepseek-ai/dsh-personalized-recommendations
English | [中文](README.zh.md)
## Summary
Get an optional card below an answer for two kinds of need: a page or small tool you want to keep, and a built-in plugin that provides a capability you ask about. Nothing is built or enabled until you click Create or Enable on the card. Enable Personalized recommendations on the Web or Desktop Plugins page; shipped profiles leave it disabled. Its page lists both features and their card rows, each with its own switch.
## Table of Contents
- [Use this package](#use-this-package)
- [Understand the implementation](#understand-the-implementation)
- [Further Exploration](#further-exploration)
- [Model Experience](#model-experience)
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
- [Dev Note](#dev-note)
-----
<a id="use-this-package"></a>
## Use this package
Open Plugins → More → Experimental plugins and enable Personalized recommendations. Enabling pins it to the main Plugins list; disabling returns it to the secondary page only. While the bundle is on, its page lists six rows in this order, each with its own switch:
| Row | Package | Contributes |
|---|---|---|
| Action cards | [`action-cards`](../action-cards/README.md) | The card tool, card state, user actions and status notices |
| Action card display | [`client-ui-action-cards`](../client-ui-action-cards/README.md) | The card frame below answers |
| Personalized pages | [`creation-recommendations`](../creation-recommendations/README.md) | Page proposals and their background creation |
| Personalized page cards | [`client-ui-creation-recommendations`](../client-ui-creation-recommendations/README.md) | The page card body |
| Plugin recommendations | [`plugin-recommendations`](../plugin-recommendations/README.md) | Built-in plugin recommendations and enabling |
| Plugin recommendation cards | [`client-ui-plugin-recommendations`](../client-ui-plugin-recommendations/README.md) | The plugin card body |
Switch off Personalized pages or Plugin recommendations to keep only the other feature: the switched-off feature withdraws its skill and card kind, and its earlier cards accept only Not now. Both features need the Action cards row; switching it off stops both until it is on again. Without a card body row, a card shows its proposal title and brief with Not now only. The Action cards row's `queuedMessageLimit` field sets how many queued next-turn messages make the card tool refuse the current turn's card. The Plugins page has no settings page for that row, so change the field with an `id: action-cards` override in the profile's `cordis.patch.yml`.
-----
<a id="understand-the-implementation"></a>
## Understand the implementation
<details>
<summary>Maintainer details — click to expand</summary>
The static `cordis.patch.yml` inserts the six rows with their package-owned ids and no configuration. This bundle is the only layer that declares these ids, so the plugin manager addresses each row by its id and the Plugins page can switch it. Both feature plugins inject `actionCards` and register their card kinds with it; the three browser rows register the card frame and the two card bodies. `src/index.ts` exports nothing.
</details>
-----
<a id="further-exploration"></a>
## Further Exploration
[Action cards](../action-cards/README.md) · [Personalized pages](../creation-recommendations/README.md) · [Plugin recommendations](../plugin-recommendations/README.md) · [Plugin manager page](../../client/ui-plugin-manager/README.md)
-----
<a id="model-experience"></a>
## Model Experience
Indirectly, through the two features' skills, the shared `propose_action` tool and the card status notice, which the row packages document.
#### KV Cache effect
Enabling the bundle adds the two skills' catalog entries to requests; the card tool joins the declarations only after a card skill loads, as described on the [action cards](../action-cards/README.md#model-experience) page.
## Known Limitations and Deferred Work
<a id="known-limitations-and-deferred-work"></a>
- The bundle page lists every row the patch inserts, including the four card rows that only support the two features; it cannot mark a row as supporting or hide it.
- Each feature's own limitations are on its page: [Personalized pages](../creation-recommendations/README.md#known-limitations-and-deferred-work) and [Plugin recommendations](../plugin-recommendations/README.md#known-limitations-and-deferred-work).
-----
<a id="dev-note"></a>
### Dev Note
<details>
<summary>Working context for maintainers — click to expand</summary>
None.
</details>
@@ -0,0 +1,88 @@
---
description: "用一个实验性 Bundle 在回答下方启用个性化页面提议和内置插件推荐。"
kind: "package-bundle"
---
# @deepseek-ai/dsh-personalized-recommendations
[English](README.md) | 中文
## 概述
针对两类需求在回答下方提供一张可选卡片:想要长期使用的页面或小工具,以及能提供你所问能力的内置插件。在卡片上点击创建或启用之前,不会生成或开启任何内容。在 Web 或 Desktop 的插件页面启用个性化推荐;发布的 profile 默认禁用它。它的页面列出两项功能及其卡片行,每行都有自己的开关。
## 目录
- [使用本包](#use-this-package)
- [了解实现](#understand-the-implementation)
- [延伸阅读](#further-exploration)
- [模型体验](#model-experience)
- [已知限制与后续工作](#known-limitations-and-deferred-work)
- [开发备注](#dev-note)
-----
<a id="use-this-package"></a>
## 使用本包
打开插件 → 更多 → 实验性插件,启用个性化推荐。启用后它固定显示在插件主列表;关闭后只回到二级页。Bundle 开启期间,它的页面按以下顺序列出六行,每行都有自己的开关:
| 行 | 包 | 提供 |
|---|---|---|
| 操作卡片 | [`action-cards`](../action-cards/README.zh.md) | 卡片工具、卡片状态、用户操作和状态通知 |
| 操作卡片显示 | [`client-ui-action-cards`](../client-ui-action-cards/README.zh.md) | 回答下方的卡片框架 |
| 个性化页面 | [`creation-recommendations`](../creation-recommendations/README.zh.md) | 页面提议及其后台创建 |
| 个性化页面卡片 | [`client-ui-creation-recommendations`](../client-ui-creation-recommendations/README.zh.md) | 页面卡片正文 |
| 插件推荐 | [`plugin-recommendations`](../plugin-recommendations/README.zh.md) | 内置插件推荐和启用 |
| 插件推荐卡片 | [`client-ui-plugin-recommendations`](../client-ui-plugin-recommendations/README.zh.md) | 插件卡片正文 |
关闭个性化页面或插件推荐即可只保留另一项功能:被关闭的功能撤下它的 Skill 和卡片种类,它之前的卡片只接受“暂不需要”。两项功能都需要操作卡片行;关闭它后两项功能都停止,直到重新开启。缺少卡片正文行时,卡片只显示提案标题、说明和“暂不需要”。操作卡片行的 `queuedMessageLimit` 字段规定排队等待下一轮的消息达到多少条时,卡片工具拒绝本轮的卡片。插件页面没有为该行提供设置页,请在 profile 的 `cordis.patch.yml` 中用 `id: action-cards` 覆盖项修改该字段。
-----
<a id="understand-the-implementation"></a>
## 了解实现
<details>
<summary>维护者细节 — 点击展开</summary>
静态 `cordis.patch.yml` 以各包自有的 id 插入这六行,不带配置。只有本 Bundle 声明这些 id,因此插件管理器按 id 定位每一行,插件页面可以开关它。两个功能插件都注入 `actionCards` 并向它注册卡片种类;三个浏览器端行注册卡片框架和两种卡片正文。`src/index.ts` 不导出任何内容。
</details>
-----
<a id="further-exploration"></a>
## 延伸阅读
[操作卡片](../action-cards/README.zh.md) · [个性化页面](../creation-recommendations/README.zh.md) · [插件推荐](../plugin-recommendations/README.zh.md) · [插件管理页面](../../client/ui-plugin-manager/README.zh.md)
-----
<a id="model-experience"></a>
## 模型体验
间接影响来自两项功能的 Skill、共享的 `propose_action` 工具和卡片状态通知,由各行所在的包说明。
#### KV Cache 效果
启用本 Bundle 会在请求中加入两个 Skill 的目录条目;卡片工具只在加载卡片 Skill 后才加入声明,见[操作卡片](../action-cards/README.zh.md#model-experience)页面。
## 已知限制与后续工作
<a id="known-limitations-and-deferred-work"></a>
- Bundle 页面列出补丁插入的每一行,包括只为两项功能提供支持的四个卡片行;页面不能把某行标为辅助行,也不能隐藏它。
- 两项功能各自的限制见其页面:[个性化页面](../creation-recommendations/README.zh.md#known-limitations-and-deferred-work)和[插件推荐](../plugin-recommendations/README.zh.md#known-limitations-and-deferred-work)。
-----
<a id="dev-note"></a>
### 开发备注
<details>
<summary>维护者工作上下文 — 点击展开</summary>
无。
</details>
@@ -7,3 +7,7 @@
name: '@deepseek-ai/dsh-creation-recommendations'
- id: client-ui-creation-recommendations
name: '@deepseek-ai/dsh-client-ui-creation-recommendations'
- id: plugin-recommendations
name: '@deepseek-ai/dsh-plugin-recommendations'
- id: client-ui-plugin-recommendations
name: '@deepseek-ai/dsh-client-ui-plugin-recommendations'
@@ -0,0 +1 @@
![Personalized recommendations feature preview](preview.svg)
@@ -0,0 +1 @@
![个性化推荐功能预览](preview.zh.svg)
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 277 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 129 KiB

@@ -0,0 +1,9 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<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)" />
<defs>
<linearGradient id="paint0_linear_plugin_market" x1="8.5" y1="10" x2="30" y2="26" gradientUnits="userSpaceOnUse">
<stop stop-color="#67C7FE" />
<stop offset="1" stop-color="#3F77D8" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 895 B

@@ -0,0 +1,7 @@
{
"meta": {
"title": "Personalisierte Empfehlungen",
"description": "Zeigt für Ihre individuellen Anforderungen an DSH Empfehlungskarten unter der Antwort an: Wenn Sie eine Seite oder ein kleines Tool zur dauerhaften Nutzung möchten, wird eine neue personalisierte Seite vorgeschlagen, die nach einem Klick auf Erstellen im Hintergrund erstellt wird; wenn Sie fragen, wie etwas in dieser App geht, oder sagen, dass eine Funktion fehlt, wird das integrierte Plugin empfohlen, das sie bereitstellt, und nach einem Klick auf Aktivieren eingeschaltet.",
"detail": "docs/DETAIL.md"
}
}
@@ -0,0 +1,7 @@
{
"meta": {
"title": "Personalized recommendations",
"description": "Shows recommendation cards below the answer for what you want from DSH: when you want a page or small tool you can keep, it proposes a new personalized page, built in the background after you click Create; when you ask how to do something in this app or say a capability is missing, it recommends the built-in plugin that provides it, turned on when you click Enable.",
"detail": "docs/DETAIL.md"
}
}
@@ -0,0 +1,7 @@
{
"meta": {
"title": "Recomendaciones personalizadas",
"description": "Muestra tarjetas de recomendación debajo de la respuesta para tus necesidades personalizadas en DSH: cuando quieres una página o una pequeña herramienta para conservar, propone una nueva página personalizada, que se genera en segundo plano al hacer clic en Crear; cuando preguntas cómo hacer algo en esta aplicación o dices que falta una función, recomienda el complemento integrado que la ofrece, que se activa al hacer clic en Habilitar.",
"detail": "docs/DETAIL.md"
}
}
@@ -0,0 +1,7 @@
{
"meta": {
"title": "Recommandations personnalisées",
"description": "Affiche des cartes de recommandation sous la réponse pour vos besoins personnalisés dans DSH : lorsque vous voulez une page ou un petit outil à conserver, propose une nouvelle page personnalisée, générée en arrière-plan après un clic sur Créer ; lorsque vous demandez comment faire quelque chose dans cette application ou signalez qu’une fonctionnalité manque, recommande le plugin intégré qui la fournit, activé lorsque vous cliquez sur Activer.",
"detail": "docs/DETAIL.md"
}
}
@@ -0,0 +1,7 @@
{
"meta": {
"title": "パーソナライズされたおすすめ",
"description": "DSH に対するあなた個人のニーズに合わせて、回答の下におすすめカードを表示します。長く使えるページや小さなツールが欲しいときは新しいパーソナルページを提案し、「作成」をクリックするとバックグラウンドで生成します。このアプリで何かをする方法を尋ねたときや機能が足りないと言ったときは、それを提供する内蔵プラグインを推薦し、「有効にする」をクリックすると有効になります。",
"detail": "docs/DETAIL.md"
}
}
@@ -0,0 +1,7 @@
{
"meta": {
"title": "Recomendações personalizadas",
"description": "Mostra cartões de recomendação abaixo da resposta para suas necessidades personalizadas no DSH: quando você quer uma página ou uma pequena ferramenta para manter, propõe uma nova página personalizada, gerada em segundo plano depois que você clica em Criar; quando você pergunta como fazer algo neste aplicativo ou diz que falta um recurso, recomenda o plug-in integrado que o oferece, ativado quando você clica em Ativar.",
"detail": "docs/DETAIL.md"
}
}
@@ -0,0 +1,7 @@
{
"meta": {
"title": "Персональные рекомендации",
"description": "Показывает карточки рекомендаций под ответом с учетом ваших индивидуальных потребностей в DSH: когда вам нужна страница или небольшой инструмент для постоянного использования, предлагает новую персональную страницу, которая создается в фоне после нажатия «Создать»; когда вы спрашиваете, как что-то сделать в этом приложении, или говорите, что не хватает какой-то возможности, рекомендует встроенный плагин, который ее предоставляет, и включает его после нажатия «Включить».",
"detail": "docs/DETAIL.md"
}
}
@@ -0,0 +1,7 @@
{
"meta": {
"title": "个性化推荐",
"description": "针对你对 DSH 的个性化需求,在回答下方展示推荐卡片:想要能长期使用的页面或小工具时,提议新增个性化页面,点击创建后在后台生成;问到本应用怎么做某件事或缺少某项能力时,推荐对应的内置插件,点击启用后开启。",
"detail": "docs/DETAIL.zh.md"
}
}
@@ -0,0 +1,70 @@
{
"name": "@deepseek-ai/dsh-personalized-recommendations",
"description": "Personalized pages and built-in plugin recommendations offered as cards below answers",
"version": "0.2.1-rc.3",
"icon": "./icon.svg",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
"directory": "packages/experimental/personalized-recommendations"
},
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json",
"./cordis.patch.yml": "./cordis.patch.yml",
"./locale/*.json": "./locale/*.json"
},
"files": [
"icon.svg",
"locale/*.json",
"docs",
"lib/index.js",
"cordis.patch.yml",
"lib/types/**/*.d.ts"
],
"license": "MIT",
"dependencies": {
"@deepseek-ai/dsh-action-cards": "workspace:*",
"@deepseek-ai/dsh-client-ui-action-cards": "workspace:*",
"@deepseek-ai/dsh-creation-recommendations": "workspace:*",
"@deepseek-ai/dsh-client-ui-creation-recommendations": "workspace:*",
"@deepseek-ai/dsh-plugin-recommendations": "workspace:*",
"@deepseek-ai/dsh-client-ui-plugin-recommendations": "workspace:*"
},
"peerDependencies": {
"@deepseek-ai/cordis": "workspace:~"
},
"devDependencies": {
"@deepseek-ai/cordis": "workspace:~",
"@deepseek-ai/cordis-plugin-include": "workspace:~",
"js-yaml": "^4.3.2",
"@deepseek-ai/cordis-plugin-timer": "workspace:~",
"@deepseek-ai/dsh-agent": "workspace:*",
"@deepseek-ai/dsh-api-remote-access": "workspace:*",
"@deepseek-ai/dsh-app-boot": "workspace:*",
"@deepseek-ai/dsh-hmr": "workspace:*",
"@deepseek-ai/dsh-llm": "workspace:*",
"@deepseek-ai/dsh-plugin-manager": "workspace:*",
"@deepseek-ai/dsh-session": "workspace:*",
"@deepseek-ai/dsh-session-projection": "workspace:*",
"@deepseek-ai/dsh-skill": "workspace:*",
"@deepseek-ai/dsh-system-prompt": "workspace:*",
"@deepseek-ai/dsh-tool-executor-native": "workspace:*",
"@deepseek-ai/dsh-tools": "workspace:*"
},
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
}
}
@@ -0,0 +1,2 @@
/** Personalized recommendations composition; runtime rows live in cordis.patch.yml. */
export {}
@@ -0,0 +1,148 @@
/** The bundle is the only layer that declares the six card rows, so the Plugins page addresses and switches each one. */
import { readFileSync } from 'node:fs'
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { expect, it, onTestFinished } from 'vitest'
import * as yaml from 'js-yaml'
import type { Context } from '@deepseek-ai/cordis'
import { entryListSchema } from '@deepseek-ai/cordis-plugin-include'
import Timer from '@deepseek-ai/cordis-plugin-timer'
import { boot, initProfile, loadProfileDirectory, readProfilePatches, type ProfileContext } from '@deepseek-ai/dsh-app-boot'
import Hmr from '@deepseek-ai/dsh-hmr'
import PluginManager from '@deepseek-ai/dsh-plugin-manager'
import Sessions from '@deepseek-ai/dsh-session'
import Projections from '@deepseek-ai/dsh-session-projection'
import Agents from '@deepseek-ai/dsh-agent'
import Llm from '@deepseek-ai/dsh-llm'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import Tools from '@deepseek-ai/dsh-tools'
import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native'
import Skills from '@deepseek-ai/dsh-skill'
import Access from '@deepseek-ai/dsh-api-remote-access'
import * as ActionCards from '@deepseek-ai/dsh-action-cards'
import * as Pages from '@deepseek-ai/dsh-creation-recommendations'
import * as Plugins from '@deepseek-ai/dsh-plugin-recommendations'
interface Row { readonly id: string; readonly name: string }
const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as {
name: string
publishConfig: { access: string }
dependencies: Record<string, string>
dsh: { bundle: { patch: string } }
}
const patch = yaml.load(readFileSync(new URL(`../${manifest.dsh.bundle.patch}`, import.meta.url), 'utf8'), {
schema: entryListSchema,
}) as { insert: Row[] }[]
const rows = patch[0]?.insert ?? []
/** A browser row's Host entry is an empty `apply`; its contribution loads in the Client. */
function browserRow(): object {
return { apply(): void {} }
}
/** Source modules standing in for the row packages, which the Loader would otherwise import from their built lib. */
const MODULES: Readonly<Record<string, object>> = {
'@deepseek-ai/dsh-action-cards': ActionCards,
'@deepseek-ai/dsh-client-ui-action-cards': browserRow(),
'@deepseek-ai/dsh-creation-recommendations': Pages,
'@deepseek-ai/dsh-client-ui-creation-recommendations': browserRow(),
'@deepseek-ai/dsh-plugin-recommendations': Plugins,
'@deepseek-ai/dsh-client-ui-plugin-recommendations': browserRow(),
}
it('publishes one layer inserting the card service, the card display and both features with their card bodies', () => {
expect(manifest.publishConfig.access).toBe('public')
expect(manifest.dsh.bundle.patch).toBe('./cordis.patch.yml')
expect(patch).toEqual([{ insert: [
{ id: 'action-cards', name: '@deepseek-ai/dsh-action-cards' },
{ id: 'client-ui-action-cards', name: '@deepseek-ai/dsh-client-ui-action-cards' },
{ id: 'creation-recommendations', name: '@deepseek-ai/dsh-creation-recommendations' },
{ id: 'client-ui-creation-recommendations', name: '@deepseek-ai/dsh-client-ui-creation-recommendations' },
{ id: 'plugin-recommendations', name: '@deepseek-ai/dsh-plugin-recommendations' },
{ id: 'client-ui-plugin-recommendations', name: '@deepseek-ai/dsh-client-ui-plugin-recommendations' },
] }])
expect(Object.keys(manifest.dependencies).sort()).toEqual(rows.map(row => row.name).sort())
})
it('addresses every row through its own id and switches the shared card service and each feature', async () => {
const home = await mkdtemp(join(tmpdir(), 'dsh-personalized-recommendations-'))
let owner: Context | undefined
onTestFinished(async () => { await owner?.fiber.dispose(); await rm(home, { recursive: true, force: true }) })
const dir = join(home, 'profiles', 'test')
const anchor = join(home, 'package.json')
await writeFile(anchor, JSON.stringify({ name: 'installation', dependencies: { [manifest.name]: '1.0.0' } }))
initProfile(dir, ['fixture-base', manifest.name])
async function bundle(root: string, name: string, inserted: object[]) {
const path = join(root, 'node_modules', name)
await mkdir(path, { recursive: true })
await writeFile(join(path, 'package.json'), JSON.stringify({
name, version: '1.0.0', dsh: { bundle: { patch: './cordis.patch.yml' } },
}))
await writeFile(join(path, 'cordis.patch.yml'), JSON.stringify([{ insert: inserted }]))
}
await bundle(dir, 'fixture-base', [
['session', 'sessions'], ['projection', 'projections'], ['llm', 'llm'], ['prompt', 'prompt'], ['tools', 'tools'],
['executor', 'executor'], ['skills', 'skills'], ['agents', 'agents'], ['access', 'access'], ['manager', 'manager'],
].map(([id, name]) => ({ id, name: `cordis:${name!}` })))
// The shipped rows keep their ids; each name serves that row's package source through a Loader builtin.
await bundle(home, manifest.name, rows.map(row => ({ id: row.id, name: `cordis:${row.id}` })))
await writeFile(join(dir, 'cordis.yml'), '[]\n')
const profile: ProfileContext = {
name: 'test', dir, home, cwd: home, patchPath: join(dir, 'cordis.patch.yml'), installAnchor: anchor,
startedBundles: loadProfileDirectory('test', dir, anchor).layers.map(layer => layer.packageName),
overlays: [], telemetryDisabledEnv: undefined,
}
const ctx = await boot('test', join(dir, 'cordis.yml'), readProfilePatches('test', profile), (ctx) => {
owner = ctx
ctx.provide('profileContext', profile)
ctx.provide('appReady', { onReady: (listener) => { listener(); return () => {} } })
// Personalized pages starts Creator children through this service only after a Create click.
ctx.provide('subagents', {})
Object.assign(ctx.loader.builtins, {
sessions: Sessions, projections: Projections, llm: Llm, prompt: SystemPrompt, tools: Tools, executor: NativeToolExecutor,
skills: Skills, agents: Agents, access: Access, manager: PluginManager,
...Object.fromEntries(rows.map(row => [row.id, MODULES[row.name]])),
})
})
await ctx.plugin(Timer)
await ctx.plugin(Hmr, { root: [], ignored: [], debounce: 0 })
await ctx.hmr.runExclusive(async () => {})
const skills = async () => (await ctx.skills.list()).map(skill => skill.name).sort()
expect(await skills()).toEqual(['official-plugins', 'personalized-pages'])
const listed = (await ctx.pluginManager.listBundles()).find(bundle => bundle.name === manifest.name)
expect(listed).toMatchObject({ enabled: true, optional: true })
expect(listed?.rows.map(row => row.rowId)).toEqual(rows.map(row => row.id))
const plugins = await ctx.pluginManager.listPlugins()
for (const row of listed?.rows ?? []) {
expect([...ctx.loader.entries()].filter(entry => entry.options.id === row.rowId)).toHaveLength(1)
const plugin = plugins.find(item => item.entryId === row.entryId)
expect(plugin).toMatchObject({ patchId: row.rowId, enabled: true })
expect(plugin?.readOnlyReason).toBeUndefined()
}
const toggle = async (id: string, enabled: boolean) => {
const entryId = listed?.rows.find(row => row.rowId === id)?.entryId
if (entryId === undefined) throw new Error(`the bundle lists no live ${id} row`)
expect(await ctx.pluginManager.setPluginEnabled(entryId, enabled)).toMatchObject({ application: 'applied' })
expect((await ctx.pluginManager.listPlugins()).find(item => item.entryId === entryId)?.enabled).toBe(enabled)
}
await toggle('creation-recommendations', false)
expect(await skills()).toEqual(['official-plugins'])
// Both features inject the card service, so switching it off withdraws them until it is on again.
await toggle('action-cards', false)
expect(ctx.get('actionCards')).toBeUndefined()
expect(await skills()).toEqual([])
expect(yaml.load(await readFile(profile.patchPath, 'utf8'))).toEqual([
{ id: 'creation-recommendations', disabled: true }, { id: 'action-cards', disabled: true },
])
await toggle('action-cards', true)
expect(await skills()).toEqual(['official-plugins'])
await toggle('creation-recommendations', true)
expect(await skills()).toEqual(['official-plugins', 'personalized-pages'])
await toggle('plugin-recommendations', false)
expect(await skills()).toEqual(['personalized-pages'])
await toggle('plugin-recommendations', true)
expect(await skills()).toEqual(['official-plugins', 'personalized-pages'])
expect(yaml.load(await readFile(profile.patchPath, 'utf8'))).toEqual([])
}, 30_000)
@@ -0,0 +1,15 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib/types"
},
"include": [
"src"
],
"references": [
{
"path": "../../../vendor/cordis"
}
]
}
@@ -0,0 +1,3 @@
import { defineConfig } from 'tsdown'
export default defineConfig({ entry: ['lib/types/index.js'], outDir: 'lib', format: ['esm'], platform: 'node', target: 'es2024', fixedExtension: false, dts: false, clean: false })
@@ -3,8 +3,8 @@
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/experimental/plugin-recommendations/README.md
/:
en: 74f11064759209bf
zh: 694dc0dc26dacd3b
en: c34591bc4458dc55
zh: 7672f17a222f287e
/deepseek-ai-dsh-plugin-recommendations:
en: f7efb269a445d68e
zh: 171930b415e131f7
@@ -15,8 +15,8 @@
en: d152484eb41ac6b4
zh: 59094206ec49ad20
/deepseek-ai-dsh-plugin-recommendations/use-this-package:
en: 8e26a2013becb72f
zh: 37f5cf1e56588ec2
en: 79fde2c706fbaf14
zh: 42eb78e5ea7f0ed8
/deepseek-ai-dsh-plugin-recommendations/use-this-package/outcomes-and-recovery:
en: 3b07d58a6bc4394a
zh: 5d64630582c8da80
@@ -33,17 +33,17 @@
en: 79e932b35e50619a
zh: 821b4d95d6ce1d22
/deepseek-ai-dsh-plugin-recommendations/model-experience/official-plugin-recommendations/what-the-model-sees:
en: 5526f467498a8458
zh: 9495ede65dbc6771
en: 64cc3b2eff7bec02
zh: 9f5bab103219e352
/deepseek-ai-dsh-plugin-recommendations/model-experience/official-plugin-recommendations/token-effect:
en: 9849eee72560906e
zh: d64a2f5b1e33f249
en: 2d88ac78f2ad5ea7
zh: c4d3ac643cf723b1
/deepseek-ai-dsh-plugin-recommendations/model-experience/official-plugin-recommendations/kv-cache-effect:
en: e6575a3adcaa7d1c
zh: e5323a4571ecc8aa
/deepseek-ai-dsh-plugin-recommendations/known-limitations-and-deferred-work:
en: 054c9961595232a9
zh: 2de8775f30a34ea1
en: 8506b8b41b36450a
zh: 439deb24f6cd0f77
/deepseek-ai-dsh-plugin-recommendations/known-limitations-and-deferred-work/dev-note:
en: e67f633dc6fe7773
zh: e88aeb617ffb74ea
@@ -1,6 +1,6 @@
---
description: "Recommend built-in official plugins for explicit user needs and enable them from conversation cards."
kind: "package-bundle"
kind: "package-reference"
---
# @deepseek-ai/dsh-plugin-recommendations
@@ -25,9 +25,9 @@ Users can discover a relevant built-in plugin while discussing a concrete need o
<a id="use-this-package"></a>
## Use this package
Open Plugins → More → Experimental plugins and enable Plugin recommendations. Shipped profiles leave this experimental bundle disabled. Enabling pins it to the main Plugins list; disabling returns it to the secondary page only. The 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 bundle has no configuration of its own.
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.
The experiment works with or without Personalized pages, and neither package depends on the other. Both bundles insert the same card-service rows, and the Loader keeps one entry for each. With both bundles enabled, those two rows cannot be switched or configured individually: use the bundle switches, and change `queuedMessageLimit` in the profile's `cordis.patch.yml` ([Known Limitations](#known-limitations-and-deferred-work)). 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.
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.
### Outcomes and recovery
@@ -69,11 +69,11 @@ Concurrent actions on one card share one operation, and disposal stops local fol
#### What the model sees
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. 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.
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.
#### Token effect
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 8.5K characters: a 2.7K-character policy plus the catalog, about 5.5K characters for 15 eligible plugins and 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.
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 20.9K characters: a 2.9K-character policy plus the catalog, about 17.7K 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.
#### KV Cache effect
@@ -88,7 +88,7 @@ The manual real-model input set is in `evaluation/intent-cases.json`; unit tests
- Only shipped, optional, official plugins are eligible; missing packages and third-party registries are excluded.
- Recommendations require a live root conversation; there is no unsolicited opportunity discovery.
- A historical decision does not continuously track changes later made on the Plugins page.
- Two optional bundles cannot yet share a dependency row addressably. With Personalized pages also enabled, the Plugins page cannot switch or configure the shared `action-cards` and `client-ui-action-cards` rows individually; use the bundle switches, and change `queuedMessageLimit` in the profile's `cordis.patch.yml`.
- 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.
<a id="dev-note"></a>
### Dev Note
@@ -1,6 +1,6 @@
---
description: "针对用户明确表达的需求推荐内置官方插件,并通过会话内卡片启用。"
kind: "package-bundle"
kind: "package-reference"
---
# @deepseek-ai/dsh-plugin-recommendations
@@ -25,9 +25,9 @@ kind: "package-bundle"
<a id="use-this-package"></a>
## 使用此包
打开插件 → 更多 → 实验性插件,启用「插件推荐」。内置 profile 默认不启用这个实验 bundle。启用后它会固定显示在插件主列表;关闭后只回到二级页。这个 bundle 同时启用共享的[操作卡片服务](../action-cards/README.zh.md)及其浏览器端;该服务的 `queuedMessageLimit` 决定排队等待后续轮次的消息达到多少条时本轮不再出卡。本 bundle 没有自己的配置项。
打开插件 → 更多 → 实验性插件,启用[个性化推荐](../personalized-recommendations/README.zh.md),其中的「插件推荐」行就是本包。内置 profile 默认不启用这个实验 bundle。启用后它会固定显示在插件主列表;关闭后只回到二级页。个性化推荐 bundle 同时启用共享的[操作卡片服务](../action-cards/README.zh.md)及其浏览器端;该服务的 `queuedMessageLimit` 决定排队等待后续轮次的消息达到多少条时本轮不再出卡。本插件没有自己的配置项。
本实验可以单独使用,也可以和「个性化页面」同时开启,两个包互不依赖。两个 bundle 插入相同的卡片服务行,Loader 对每个行 id 只保留一个条目。两个 bundle 都启用时,这两行不能单独开关或配置:请使用 bundle 开关,`queuedMessageLimit` 需在 profile 的 `cordis.patch.yml` 中修改(见[已知限制](#known-limitations-and-deferred-work))。两种卡片合计每轮最多一张:本轮第一个被接受的 `propose_action` 调用成为卡片。多个独立需求时,指引优先遵循用户明确的优先级,否则现成插件优先于新建页面;其余需求在回答正文中说明。
同一 bundle 的「个性化页面」行关闭后,本实验仍可使用,两个包互不依赖。两种卡片合计每轮最多一张:本轮第一个被接受的 `propose_action` 调用成为卡片。多个独立需求时,指引优先遵循用户明确的优先级,否则现成插件优先于新建页面;其余需求在回答正文中说明。
### 结果与恢复
@@ -69,11 +69,11 @@ Host 入口是函数插件。它的 `apply` 通过 `ctx.plugin` 组装内部的
#### 模型可见内容
用户问 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`;调用本身就是提交,用户点击启用前什么都不会改变。只有被接受的调用结果会说明回答怎样结尾:`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 只会邀请用户点击已经出现的卡片。点击后的下一次请求会带共享的状态通知和本种类的说明:它是事实,不是继续工作的要求,启用一个插件也不授权该插件之外的任何事。
用户问 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 只会邀请用户点击已经出现的卡片。点击后的下一次请求会带共享的状态通知和本种类的说明:它是事实,不是继续工作的要求,启用一个插件也不授权该插件之外的任何事。
#### Token 影响
启用期间,每次请求只多一条 495 字符的目录条目(在 skill 目录 500 字符的上限内);共享的卡片工具只在加载过卡片 skill 之后才声明。一次 skill 加载追加约 8.5K 字符:2.7K 字符的规则,加上目录;15 个可推荐插件时目录约 5.5K 字符,并随插件数量增长。一次推荐需要加载一次 skill,并在最终回答前调用一次 `propose_action`;被接受的调用结果 169 字符,按每 4 字符 1 个 token 计约 42 个 token。状态变化的卡片会在下一次请求追加一条简短通知。
启用期间,每次请求只多一条 495 字符的目录条目(在 skill 目录 500 字符的上限内);共享的卡片工具只在加载过卡片 skill 之后才声明。一次 skill 加载追加约 20.9K 字符:2.9K 字符的规则,加上目录;14 个可推荐插件(标题和描述含 8 种发布语言)时目录约 17.7K 字符,并随插件数量增长。一次推荐需要加载一次 skill,并在最终回答前调用一次 `propose_action`;被接受的调用结果 169 字符,按每 4 字符 1 个 token 计约 42 个 token。状态变化的卡片会在下一次请求追加一条简短通知。
#### KV Cache 影响
@@ -88,7 +88,7 @@ Skill 内容和提案调用追加到对话记录;公共 system prompt 不变
- 只推荐已随安装提供、可选的官方插件;缺失的包和第三方注册源不在范围内。
- 推荐需要在线的根会话;不会主动寻找推荐机会。
- 历史决定不会持续跟踪之后在插件页做的修改。
- 平台目前不支持两个可选 bundle 以可定位的方式共享同一依赖行。同时启用「个性化页面」时,插件页不能单独开关或配置共享的 `action-cards` 和 `client-ui-action-cards` 两行;请使用 bundle 开关,`queuedMessageLimit` 需在 profile 的 `cordis.patch.yml` 中修改。
- 目录按 bundle 报告启用状态:只关闭「个性化页面」行时,个性化推荐仍显示为已启用,skill 可能因此告诉想要页面的用户它已开启。
<a id="dev-note"></a>
### 开发备注
@@ -1,9 +0,0 @@
- insert:
- id: action-cards
name: '@deepseek-ai/dsh-action-cards'
- id: client-ui-action-cards
name: '@deepseek-ai/dsh-client-ui-action-cards'
- id: plugin-recommendations
name: '@deepseek-ai/dsh-plugin-recommendations'
- id: client-ui-plugin-recommendations
name: '@deepseek-ai/dsh-client-ui-plugin-recommendations'
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Plugin-Empfehlungen",
"description": "Entdecken Sie integrierte Plugins für Bedürfnisse, die Sie beschreiben, wenn Sie nicht wissen, welche Funktionen verfügbar sind. Nach der Antwort erscheint eine Karte zum Aktivieren; die Aktivierung erfordert Ihren Klick."
"description": "Wenn Sie fragen, wie etwas in dieser App geht, oder sagen, dass eine Funktion fehlt, empfiehlt es unter der Antwort das integrierte Plugin, das sie bereitstellt; Aktivieren schaltet es für alle Gespräche ein, und Sie können es auf der Seite „Plugins“ wieder ausschalten."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Plugin recommendations",
"description": "Discover built-in plugins for needs you describe when you are unsure which features are available. An Enable card appears after the answer; activation requires your click."
"description": "When you ask how to do something in this app or say a capability is missing, recommends the built-in plugin that provides it below the answer; Enable turns it on for every conversation, and you can turn it off on the Plugins page."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Recomendaciones de complementos",
"description": "Descubre complementos integrados para las necesidades que describes cuando no sabes qué funciones están disponibles. Después de la respuesta aparece una tarjeta Habilitar; la activación requiere tu clic."
"description": "Cuando preguntas cómo hacer algo en esta aplicación o dices que falta una función, recomienda debajo de la respuesta el complemento integrado que la ofrece; Habilitar lo activa en todas las conversaciones y puedes desactivarlo en la página Complementos."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Recommandations de plugins",
"description": "Découvrez les plugins intégrés adaptés aux besoins que vous décrivez lorsque vous ne savez pas quelles fonctionnalités sont disponibles. Une carte Activer apparaît après la réponse ; l’activation nécessite votre clic."
"description": "Lorsque vous demandez comment faire quelque chose dans cette application ou signalez qu’une fonctionnalité manque, recommande sous la réponse le plugin intégré qui la fournit ; Activer l’active pour toutes les conversations, et vous pouvez le désactiver dans la page Plugins."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "プラグイン推薦",
"description": "どの機能が使えるか分からないとき、あなたが述べたニーズに合う内蔵プラグインを推薦します。回答の後に有効化カードが表示され、有効化にはクリックが必要です。"
"description": "このアプリで何かをする方法を尋ねたときや機能が足りないと言ったときに、それを提供する内蔵プラグインを回答の下で推薦します。「有効にする」をクリックするとすべての会話で有効になり、プラグイン画面でオフにできます。"
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Recomendações de plug-ins",
"description": "Descubra plug-ins integrados para as necessidades que você descreve quando não sabe quais recursos estão disponíveis. Um cartão Ativar aparece após a resposta; a ativação exige o seu clique."
"description": "Quando você pergunta como fazer algo neste aplicativo ou diz que falta um recurso, recomenda abaixo da resposta o plug-in integrado que o oferece; Ativar o liga em todas as conversas, e você pode desligá-lo na página Plug-ins."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Рекомендации плагинов",
"description": "Находите встроенные плагины для описанных вами потребностей, когда вы не знаете, какие функции доступны. После ответа появляется карточка «Включить»; плагин включается только по вашему нажатию."
"description": "Когда вы спрашиваете, как что-то сделать в этом приложении, или говорите, что не хватает какой-то возможности, рекомендует под ответом встроенный плагин, который ее предоставляет; «Включить» включает его для всех разговоров, а выключить его можно на странице «Плагины»."
}
}
@@ -1,6 +1,6 @@
{
"meta": {
"title": "插件推荐",
"description": "不清楚现有哪些功能可用时,根据你明确表达的需求推荐内置插件,并在对话末尾提供启用卡片;由你点击后开启。"
"description": "问到本应用怎么做某件事或缺少某项能力时,在回答下方推荐对应的内置插件;点击启用后对所有会话生效,可在插件页关闭。"
}
}
@@ -33,7 +33,6 @@
"types": "./lib/typert.remote-client.d.ts",
"default": "./lib/typert.remote-client.js"
},
"./cordis.patch.yml": "./cordis.patch.yml",
"./skills/*.md": "./skills/*.md"
},
"license": "MIT",
@@ -64,7 +63,6 @@
"icon.svg",
"locale/*.json",
"lib/index.js",
"cordis.patch.yml",
"skills/*.md",
"lib/types/**/*.js",
"lib/types/**/*.d.ts",
@@ -73,10 +71,5 @@
"lib/typert.remote-client.js",
"lib/typert.remote-client.d.ts"
],
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
},
"icon": "./icon.svg"
}
@@ -20,4 +20,4 @@ A turn shows at most one card across every card skill. When the user asks for se
## Offer without enabling
To offer, call `propose_action` once, in the same turn as loading this skill, with `action: "enable_official_plugin"`, `title` set to the plugin's catalog title in the user's language, `brief` set to one sentence in the user's language that ties the plugin to their need, and `details` holding `name`, the exact catalog name. The call is the submission: the card appears below your answer; a refused call creates no card, and its error says why. Then write your final answer: answer any direct question; the call's result says how to end the answer. Do not narrate the call, repeat the catalog description, add setup steps the catalog does not state, claim the plugin is enabled, or ask the user to report back. The card opens the plugin's details; enabling applies to every conversation and can be undone there. The user's choice reaches you later as a status notice; enabling a plugin authorizes nothing beyond it.
To offer, call `propose_action` once, in the same turn as loading this skill, with `action: "enable_official_plugin"`, `title` set to the plugin's catalog title in the user's language, `brief` set to one sentence in the user's language that ties the plugin to their need, and `details` holding `name`, the exact catalog name. The call is the submission: the card appears below your answer; a refused call creates no card, and its error says why. Then write your final answer: answer any direct question; the call's result says how to end the answer. The card shows only the plugin's catalog title and description, so the answer says in one sentence, in the user's language, how the plugin meets their need; `brief` is that same sentence. Do not narrate the call, repeat the catalog description, add setup steps the catalog does not state, claim the plugin is enabled, or ask the user to report back. The card opens the plugin's details; enabling applies to every conversation and can be undone there. The user's choice reaches you later as a status notice; enabling a plugin authorizes nothing beyond it.
@@ -39,7 +39,7 @@ A turn shows at most one card across every card skill. When the user asks for se
## Offer without enabling
To offer, call \`propose_action\` once, in the same turn as loading this skill, with \`action: "enable_official_plugin"\`, \`title\` set to the plugin's catalog title in the user's language, \`brief\` set to one sentence in the user's language that ties the plugin to their need, and \`details\` holding \`name\`, the exact catalog name. The call is the submission: the card appears below your answer; a refused call creates no card, and its error says why. Then write your final answer: answer any direct question; the call's result says how to end the answer. Do not narrate the call, repeat the catalog description, add setup steps the catalog does not state, claim the plugin is enabled, or ask the user to report back. The card opens the plugin's details; enabling applies to every conversation and can be undone there. The user's choice reaches you later as a status notice; enabling a plugin authorizes nothing beyond it.
To offer, call \`propose_action\` once, in the same turn as loading this skill, with \`action: "enable_official_plugin"\`, \`title\` set to the plugin's catalog title in the user's language, \`brief\` set to one sentence in the user's language that ties the plugin to their need, and \`details\` holding \`name\`, the exact catalog name. The call is the submission: the card appears below your answer; a refused call creates no card, and its error says why. Then write your final answer: answer any direct question; the call's result says how to end the answer. The card shows only the plugin's catalog title and description, so the answer says in one sentence, in the user's language, how the plugin meets their need; \`brief\` is that same sentence. Do not narrate the call, repeat the catalog description, add setup steps the catalog does not state, claim the plugin is enabled, or ask the user to report back. The card opens the plugin's details; enabling applies to every conversation and can be undone there. The user's choice reaches you later as a status notice; enabling a plugin authorizes nothing beyond it.
## Official plugins
@@ -33,7 +33,7 @@ class Model extends LlmAdapter {
}
}
it.each(['normal', 'consumer-unload', 'shared-rows'] as const)('enables through the real Loader/HMR queue: %s', async (scenario) => {
it.each(['normal', 'consumer-unload'] as const)('enables through the real Loader/HMR queue: %s', async (scenario) => {
const home = await mkdtemp(join(tmpdir(), 'dsh-recommendations-'))
let owner: Context | undefined
onTestFinished(async () => { await owner?.fiber.dispose(); await rm(home, { recursive: true, force: true }) })
@@ -41,8 +41,7 @@ it.each(['normal', 'consumer-unload', 'shared-rows'] as const)('enables through
const anchor = join(home, 'package.json')
const name = '@deepseek-ai/dsh-experimental-session-search'
await writeFile(anchor, JSON.stringify({ name: 'installation', dependencies: { [name]: '1.0.0' } }))
// The Personalized pages bundle inserts the same card-service row; the Loader keeps one entry for the id.
initProfile(dir, scenario === 'shared-rows' ? ['fixture-base', 'fixture-pages'] : ['fixture-base'])
initProfile(dir, ['fixture-base'])
async function bundle(root: string, name: string, patch: string) {
const path = join(root, 'node_modules', name)
await mkdir(path, { recursive: true })
@@ -54,7 +53,6 @@ it.each(['normal', 'consumer-unload', 'shared-rows'] as const)('enables through
return path
}
await bundle(dir, 'fixture-base', await readFile(new URL('./fixtures/cordis.yml', import.meta.url), 'utf8'))
await bundle(dir, 'fixture-pages', JSON.stringify([{ insert: [{ id: 'action-cards', name: 'cordis:actionCards' }] }]))
const plugin = await bundle(home, name, JSON.stringify([{ insert: [{ id: 'recommendation-probe', name: './plugin.mjs' }] }]))
await writeFile(join(plugin, 'plugin.mjs'), 'export function apply(ctx) { ctx.provide("recommendationProbe", true) }\n')
await writeFile(join(dir, 'cordis.yml'), '[]\n')
@@ -110,17 +108,6 @@ it.each(['normal', 'consumer-unload', 'shared-rows'] as const)('enables through
expect(model.requests).toHaveLength(0)
return
}
if (scenario === 'shared-rows') {
// The composed configuration holds the shared row twice, so the Plugins page cannot address it; the bundles still switch.
const shared = async () => (await ctx.pluginManager.listPlugins()).find(plugin => plugin.moduleName === 'cordis:actionCards')
expect((await shared())?.readOnlyReason).toBe('unaddressable')
const service = [...ctx.loader.entries()].find(entry => entry.options.id === 'action-cards')?.fiber
expect((await ctx.pluginManager.setBundleEnabled('fixture-pages', false)).application).toBe('applied')
expect((await shared())?.readOnlyReason).toBeUndefined()
expect([...ctx.loader.entries()].find(entry => entry.options.id === 'action-cards')?.fiber).toBe(service)
expect((await ctx.pluginManager.setBundleEnabled('fixture-pages', true)).application).toBe('applied')
expect((await shared())?.readOnlyReason).toBe('unaddressable')
}
const enabled = { callId, action: ACTION, phase: 'enabled' }
expect(await ctx.actionCards.act(agent, callId, 'enable')).toEqual({ status: 'settled', decision: enabled })
expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toContain(name)
@@ -53,8 +53,7 @@ export const DEFAULT_PROFILE_BUNDLES: readonly string[] = ['@deepseek-ai/dsh-bas
* [admission](../../../../.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md)).
*/
export const OPTIONAL_BUNDLES: readonly string[] = [
'@deepseek-ai/dsh-creation-recommendations',
'@deepseek-ai/dsh-plugin-recommendations',
'@deepseek-ai/dsh-personalized-recommendations',
'@deepseek-ai/dsh-cua-bundle',
'@deepseek-ai/dsh-experimental-session-search',
'@deepseek-ai/dsh-experimental-ralph-bundle',
@@ -47,8 +47,7 @@ export const EXTENSION_BUNDLES: readonly string[] = [
* which includes every eligible bundle, ranks them last in this order.
*/
export const MORE_BUNDLES: readonly string[] = [
'@deepseek-ai/dsh-creation-recommendations',
'@deepseek-ai/dsh-plugin-recommendations',
'@deepseek-ai/dsh-personalized-recommendations',
'@deepseek-ai/dsh-experimental-inspector-profile',
'@deepseek-ai/dsh-cua-bundle',
'@deepseek-ai/dsh-experimental-tool-worktree',
@@ -63,8 +62,7 @@ export const MORE_BUNDLES: readonly string[] = [
/** Official bundles the plugin manager marks with the Experimental badge. */
export const EXPERIMENTAL_BUNDLES: ReadonlySet<string> = new Set([
'@deepseek-ai/dsh-creation-recommendations',
'@deepseek-ai/dsh-plugin-recommendations',
'@deepseek-ai/dsh-personalized-recommendations',
'@deepseek-ai/dsh-cua-bundle',
'@deepseek-ai/dsh-experimental-agent-team-profile',
'@deepseek-ai/dsh-experimental-voice-input-bundle',
+73 -6
View File
@@ -325,9 +325,6 @@ importers:
'@deepseek-ai/dsh-cordis-client-runner':
specifier: workspace:*
version: link:../../packages/extensions/cordis-client-runner
'@deepseek-ai/dsh-creation-recommendations':
specifier: workspace:*
version: link:../../packages/experimental/creation-recommendations
'@deepseek-ai/dsh-cua-bundle':
specifier: workspace:*
version: link:../../packages/experimental/cua-bundle
@@ -406,15 +403,15 @@ importers:
'@deepseek-ai/dsh-persona':
specifier: workspace:*
version: link:../../packages/preset/persona
'@deepseek-ai/dsh-personalized-recommendations':
specifier: workspace:*
version: link:../../packages/experimental/personalized-recommendations
'@deepseek-ai/dsh-plan-mode':
specifier: workspace:*
version: link:../../packages/plan/plan-mode
'@deepseek-ai/dsh-plugin-manager':
specifier: workspace:*
version: link:../../packages/boot/plugin-manager
'@deepseek-ai/dsh-plugin-recommendations':
specifier: workspace:*
version: link:../../packages/experimental/plugin-recommendations
'@deepseek-ai/dsh-pwsh-local':
specifier: workspace:*
version: link:../../packages/shell/pwsh-local
@@ -9806,6 +9803,76 @@ importers:
specifier: ^4.3.2
version: 4.3.2
packages/experimental/personalized-recommendations:
dependencies:
'@deepseek-ai/dsh-action-cards':
specifier: workspace:*
version: link:../action-cards
'@deepseek-ai/dsh-client-ui-action-cards':
specifier: workspace:*
version: link:../client-ui-action-cards
'@deepseek-ai/dsh-client-ui-creation-recommendations':
specifier: workspace:*
version: link:../client-ui-creation-recommendations
'@deepseek-ai/dsh-client-ui-plugin-recommendations':
specifier: workspace:*
version: link:../client-ui-plugin-recommendations
'@deepseek-ai/dsh-creation-recommendations':
specifier: workspace:*
version: link:../creation-recommendations
'@deepseek-ai/dsh-plugin-recommendations':
specifier: workspace:*
version: link:../plugin-recommendations
devDependencies:
'@deepseek-ai/cordis':
specifier: workspace:~
version: link:../../../vendor/cordis
'@deepseek-ai/cordis-plugin-include':
specifier: workspace:~
version: link:../../../vendor/include
'@deepseek-ai/cordis-plugin-timer':
specifier: workspace:~
version: link:../../../vendor/timer
'@deepseek-ai/dsh-agent':
specifier: workspace:*
version: link:../../core/agent
'@deepseek-ai/dsh-api-remote-access':
specifier: workspace:*
version: link:../../api/remote-access
'@deepseek-ai/dsh-app-boot':
specifier: workspace:*
version: link:../../boot/app-boot
'@deepseek-ai/dsh-hmr':
specifier: workspace:*
version: link:../../boot/hmr
'@deepseek-ai/dsh-llm':
specifier: workspace:*
version: link:../../llm/llm
'@deepseek-ai/dsh-plugin-manager':
specifier: workspace:*
version: link:../../boot/plugin-manager
'@deepseek-ai/dsh-session':
specifier: workspace:*
version: link:../../core/session
'@deepseek-ai/dsh-session-projection':
specifier: workspace:*
version: link:../../session/session-projection
'@deepseek-ai/dsh-skill':
specifier: workspace:*
version: link:../../skill/skill
'@deepseek-ai/dsh-system-prompt':
specifier: workspace:*
version: link:../../core/system-prompt
'@deepseek-ai/dsh-tool-executor-native':
specifier: workspace:*
version: link:../../core/tool-executor-native
'@deepseek-ai/dsh-tools':
specifier: workspace:*
version: link:../../core/tools
js-yaml:
specifier: ^4.3.2
version: 4.3.2
packages/experimental/plugin-market-bundle:
dependencies:
'@deepseek-ai/dsh-client-ui-plugin-market':
+1
View File
@@ -24,6 +24,7 @@ export const EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS: Readonly<Record<string, strin
'packages/experimental/client-ui-creation-recommendations': '@deepseek-ai/dsh-client-ui-creation-recommendations',
'packages/experimental/plugin-recommendations': '@deepseek-ai/dsh-plugin-recommendations',
'packages/experimental/client-ui-plugin-recommendations': '@deepseek-ai/dsh-client-ui-plugin-recommendations',
'packages/experimental/personalized-recommendations': '@deepseek-ai/dsh-personalized-recommendations',
} satisfies Record<`packages/experimental/${string}`, `@deepseek-ai/dsh-${string}`>
/** npm namespace for experimental packages without retained-name exceptions. */
+1 -1
View File
@@ -252,7 +252,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
await ctx.plugin(RemoteAccess)
await ctx.plugin(ActionCards)
},
note: 'Declared only to an Agent that loaded the skill of a registered card kind; the kind packages register their actions, and the Personalized pages and Plugin recommendations bundles supply the shipped kinds.',
note: 'Declared only to an Agent that loaded the skill of a registered card kind; the kind packages register their actions, and the Personalized recommendations bundle supplies the shipped kinds.',
},
{
pkg: '@deepseek-ai/dsh-tool-ask-user',
+1
View File
@@ -79,6 +79,7 @@ describe('release families', () => {
'@deepseek-ai/dsh-hooks-codex',
'@deepseek-ai/dsh-experimental-inspector-profile',
'@deepseek-ai/dsh-experimental-inspector',
'@deepseek-ai/dsh-personalized-recommendations',
'@deepseek-ai/dsh-experimental-plugin-market-bundle',
'@deepseek-ai/dsh-plugin-recommendations',
'@deepseek-ai/dsh-experimental-ptc-runtime-python',
@@ -59,6 +59,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/experimental/client-ui-action-cards': { kind: 'indirect', reason: 'Card actions go to the Host card service, which owns the logged tool, decisions and status notice.' },
'packages/experimental/client-ui-creation-recommendations': { kind: 'indirect', reason: 'The page card body starts a Creator through the Host page kind; the Host owns its logged task.' },
'packages/experimental/client-ui-plugin-recommendations': { kind: 'indirect', reason: 'The plugin card body enables through the Host plugin kind; the Host card service owns the logged tool, decisions and status notice.' },
'packages/experimental/personalized-recommendations': { kind: 'indirect', reason: 'The bundle composes the card service and both card kinds; their packages own the skills, the logged tool and the status notice.' },
'packages/experimental/voice-input-bundle': { kind: 'none', reason: 'Composes dictation and preparation plugins without adding any model-facing contribution.' },
'packages/telemetry/otel': { kind: 'none', reason: 'The service delivers caller-selected records without changing model context.' },
'packages/host/product-telemetry-otel': { kind: 'none', reason: 'The plugin exports explicit analytics records without contributing model context.' },
File diff suppressed because one or more lines are too long
@@ -2,5 +2,5 @@
- text: Recommended plugin Official
- 'button "View details: Voice input"':
- strong: Voice input
- text: Speak long requests instead of typing them; transcription runs on this computer. Transcribe recordings locally with SenseVoice; first use requires installing dependencies.
- text: Transcribe recordings locally with SenseVoice; first use requires installing dependencies.
- status: Dismissed
@@ -2,7 +2,7 @@
- text: Recommended plugin Official
- 'button "View details: Voice input"':
- strong: Voice input
- text: Speak long requests instead of typing them; transcription runs on this computer. Transcribe recordings locally with SenseVoice; first use requires installing dependencies.
- text: Transcribe recordings locally with SenseVoice; first use requires installing dependencies.
- text: Available in all conversations when enabled
- button "Not now"
- button "Enable"
File diff suppressed because one or more lines are too long
+1
View File
@@ -469,6 +469,7 @@
"@deepseek-ai/dsh-package-manifest": ["./packages/util/package-manifest/src"],
"@deepseek-ai/dsh-permission-presets": ["./packages/interaction/permission-presets/src"],
"@deepseek-ai/dsh-persona": ["./packages/preset/persona/src"],
"@deepseek-ai/dsh-personalized-recommendations": ["./packages/experimental/personalized-recommendations/src"],
"@deepseek-ai/dsh-plan-mode": ["./packages/plan/plan-mode/src"],
"@deepseek-ai/dsh-plugin-detail": ["./packages/util/plugin-detail/src"],
"@deepseek-ai/dsh-plugin-market": ["./packages/boot/plugin-market/src"],
+1
View File
@@ -259,6 +259,7 @@
{ "path": "./packages/experimental/action-cards" },
{ "path": "./packages/experimental/creation-recommendations" },
{ "path": "./packages/experimental/plugin-recommendations" },
{ "path": "./packages/experimental/personalized-recommendations" },
{ "path": "./packages/experimental/client-ui-cot-translation/tsconfig.host.json" },
{ "path": "./packages/experimental/cot-translation-bundle" },