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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ /* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */
2
+ import type {
3
+ RemoteResult,
4
+ TypertRemoteInfo,
5
+ TypertRemoteMethod,
6
+ } from '@deepseek-ai/dsh-typert-protocol'
7
+ import type { ToolCallId } from '@deepseek-ai/dsh-llm/brand'
8
+ import type { RecommendationDetails } from '@deepseek-ai/dsh-plugin-recommendations/types'
9
+ import type { SessionId } from '@deepseek-ai/dsh-session/types'
10
+
11
+ declare module '@deepseek-ai/dsh-typert-protocol' {
12
+ interface TypertRemoteNamespace$706c7567696e5265636f6d6d656e646174696f6e73 {
13
+ describe: (agentId: SessionId, callId: ToolCallId) => Promise<RemoteResult<RecommendationDetails>>
14
+ }
15
+ interface TypertRemoteMap {
16
+ 'pluginRecommendations/describe': (agentId: SessionId, callId: ToolCallId) => Promise<RemoteResult<RecommendationDetails>>
17
+ }
18
+ interface TypertRemoteNamespaceMap {
19
+ 'pluginRecommendations': TypertRemoteNamespace$706c7567696e5265636f6d6d656e646174696f6e73
20
+ }
21
+ interface TypertRemoteScopeMap {
22
+ 'agent:pluginRecommendations/describe': (callId: ToolCallId) => Promise<RemoteResult<RecommendationDetails>>
23
+ }
24
+ }
25
+
26
+ export declare const TYPERT_REMOTE: TypertRemoteInfo<{
27
+ readonly 'pluginRecommendations/describe': TypertRemoteMethod<{ readonly 'agentId': SessionId; readonly 'callId': ToolCallId }, RecommendationDetails>
28
+ }>
29
+ export default TYPERT_REMOTE
30
+ //# sourceMappingURL=typert.remote-client.d.ts.map
@@ -0,0 +1,90 @@
1
+ /* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */
2
+ import { z } from 'zod'
3
+
4
+ import { createTypertRemoteInfo } from '@deepseek-ai/dsh-typert-protocol/remote-info'
5
+
6
+ let _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_0$schema$value
7
+ const _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_0$schema = () => (_deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_0$schema$value ??= z.string())
8
+ let _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_1$schema$value
9
+ const _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_1$schema = () => (_deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_1$schema$value ??= z.string())
10
+ let _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_result$schema$value
11
+ const _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_result$schema = () => (_deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_result$schema$value ??= z.union([z.object({
12
+ 'status': z.literal("available").readonly(),
13
+ 'title': z.union([z.string(), z.intersection(z.object({
14
+ 'en': z.string().readonly(),
15
+ }), z.record(z.string(), z.string()).readonly())]).readonly(),
16
+ 'description': z.union([z.string(), z.intersection(z.object({
17
+ 'en': z.string().readonly(),
18
+ }), z.record(z.string(), z.string()).readonly())]).readonly(),
19
+ 'enabled': z.boolean().readonly(),
20
+ }), z.object({
21
+ 'status': z.literal("refused").readonly(),
22
+ 'reason': z.union([z.literal("unknown"), z.literal("unavailable"), z.literal("not-live-root"), z.literal("closing")]).readonly(),
23
+ })]))
24
+ let _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_0_strict$schema$value
25
+ const _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_0_strict$schema = () => (_deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_0_strict$schema$value ??= z.string())
26
+ let _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_1_strict$schema$value
27
+ const _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_1_strict$schema = () => (_deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_1_strict$schema$value ??= z.string())
28
+
29
+ export const TYPERT_REMOTE = createTypertRemoteInfo({
30
+ package: '@deepseek-ai/dsh-plugin-recommendations',
31
+ methods: {
32
+ 'pluginRecommendations/describe': {
33
+ service: 'pluginRecommendations',
34
+ description: 'Read the current catalog text for a logged card.',
35
+ invocation: { kind: 'direct' },
36
+ scope: {
37
+ context: 'agent',
38
+ wire: 'agentId',
39
+ },
40
+ parameters: [
41
+ {
42
+ name: 'agent',
43
+ wire: 'agentId',
44
+ description: 'Exact live root resolved by the Gateway from the Session id.',
45
+ source: 'lookup',
46
+ lookup: 'agent',
47
+ codec: {
48
+ mode: 'strict',
49
+ typeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
50
+ create: _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_0$schema,
51
+ jsonSchema: {"type":"string"},
52
+ },
53
+ strictCodec: {
54
+ mode: 'strict',
55
+ typeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
56
+ create: _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_0_strict$schema,
57
+ jsonSchema: {"type":"string"},
58
+ },
59
+ },
60
+ {
61
+ name: 'callId',
62
+ description: 'The card\'s `propose_action` call id, never a package supplied by the browser.',
63
+ source: 'json',
64
+ codec: {
65
+ mode: 'strict',
66
+ typeSymbol: '@deepseek-ai/dsh-llm/brand#ToolCallId',
67
+ create: _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_1$schema,
68
+ jsonSchema: {"type":"string"},
69
+ },
70
+ strictCodec: {
71
+ mode: 'strict',
72
+ typeSymbol: '@deepseek-ai/dsh-llm/brand#ToolCallId',
73
+ create: _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_parameter_1_strict$schema,
74
+ jsonSchema: {"type":"string"},
75
+ },
76
+ },
77
+ ],
78
+ result: {
79
+ mode: 'strict',
80
+ typeSymbol: '@deepseek-ai/dsh-plugin-recommendations/types#RecommendationDetails',
81
+ create: _deepseek_ai_dsh_plugin_recommendations_pluginRecommendations_describe_result$schema,
82
+ description: 'the plugin\'s title, description and selection, or a refusal when the card names no available official plugin.',
83
+ jsonSchemaError: 'typert tool schema: selected dictionaries require string keys and unconstrained JSON values',
84
+ },
85
+ sourceLocation: {"file":"packages/experimental/plugin-recommendations/src/index.ts","line":88,"column":9},
86
+ },
87
+ },
88
+ }, z)
89
+
90
+ export default TYPERT_REMOTE
@@ -0,0 +1,46 @@
1
+ import { Service, type Context } from '@deepseek-ai/cordis';
2
+ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
3
+ import type { ToolCallId } from '@deepseek-ai/dsh-llm';
4
+ import type { Agent } from '@deepseek-ai/dsh-agent';
5
+ import type { RecommendationDetails } from './types.ts';
6
+ export type * from './types.ts';
7
+ export declare const name = "plugin-recommendations";
8
+ /** Services the plugin uses; `actionCards` owns the card tool, card state and user actions. */
9
+ export declare const inject: string[];
10
+ declare module '@deepseek-ai/cordis' {
11
+ interface Context {
12
+ pluginRecommendations: PluginRecommendations;
13
+ }
14
+ }
15
+ /** Catalog lookups for card text and the enabling behind the `enable_official_plugin` card. */
16
+ export declare class PluginRecommendations extends TypertRemoteService {
17
+ static inject: string[];
18
+ /** Card reconciliations and enabling in flight; unloading waits for them. */
19
+ private readonly operations;
20
+ private readonly stopped;
21
+ private closing;
22
+ private readonly owner;
23
+ constructor(ctx: Context);
24
+ protected [Service.init](): void;
25
+ /**
26
+ * Read the current catalog text for a logged card.
27
+ * @param agent - Exact live root resolved by the Gateway from the Session id.
28
+ * @param callId - The card's `propose_action` call id, never a package supplied by the browser.
29
+ * @returns the plugin's title, description and selection, or a refusal when the card names no available official plugin.
30
+ */
31
+ describe(agent: Agent, callId: ToolCallId): Promise<RecommendationDetails>;
32
+ private isStopping;
33
+ /** Track an operation until it settles, so unloading waits for it. */
34
+ private tracked;
35
+ /** Record `enabled` on an unconfirmed card whose interrupted enable has since turned its plugin on. */
36
+ private confirm;
37
+ /** Enable the card's plugin once; an enabled card settles unchanged, and a failed or unconfirmed one can be retried. */
38
+ private enable;
39
+ }
40
+ /**
41
+ * Register the official-plugins skill, the `enable_official_plugin` card kind and the catalog-text Remote;
42
+ * disposing the plugin stops local follow-up of pending enabling and removes all three.
43
+ * @param ctx - Owning plugin context.
44
+ */
45
+ export declare function apply(ctx: Context): Promise<void>;
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,194 @@
1
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
2
+ var useValue = arguments.length > 2;
3
+ for (var i = 0; i < initializers.length; i++) {
4
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
5
+ }
6
+ return useValue ? value : void 0;
7
+ };
8
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
9
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
10
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
11
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
12
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
13
+ var _, done = false;
14
+ for (var i = decorators.length - 1; i >= 0; i--) {
15
+ var context = {};
16
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
17
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
18
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
19
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
20
+ if (kind === "accessor") {
21
+ if (result === void 0) continue;
22
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
23
+ if (_ = accept(result.get)) descriptor.get = _;
24
+ if (_ = accept(result.set)) descriptor.set = _;
25
+ if (_ = accept(result.init)) initializers.unshift(_);
26
+ }
27
+ else if (_ = accept(result)) {
28
+ if (kind === "field") initializers.unshift(_);
29
+ else descriptor[key] = _;
30
+ }
31
+ }
32
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
+ done = true;
34
+ };
35
+ /** Explicit-need official plugin recommendations: an `enable_official_plugin` card offers one plugin, and only a human click enables it. */
36
+ import { z } from 'zod';
37
+ import { Service } from '@deepseek-ai/cordis';
38
+ import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
39
+ import { isPluginStopping } from '@deepseek-ai/dsh-action-cards';
40
+ import { eligible, registerOfficialPluginsSkill } from "./skill.js";
41
+ export const name = 'plugin-recommendations';
42
+ /** Services the plugin uses; `actionCards` owns the card tool, card state and user actions. */
43
+ export const inject = ['pluginManager', 'agents', 'skills', 'actionCards'];
44
+ /** Card action this plugin registers. */
45
+ const ACTION = 'enable_official_plugin';
46
+ const detailsSchema = z.object({ name: z.string().trim().min(1) });
47
+ /** Own entries only: browser-supplied ids such as `toString` never reach inherited object members. */
48
+ function ownEntry(record, key) {
49
+ return Object.hasOwn(record, key) ? record[key] : undefined;
50
+ }
51
+ function settled(decision) {
52
+ return { status: 'settled', decision };
53
+ }
54
+ function refused(reason) {
55
+ return { status: 'refused', reason };
56
+ }
57
+ /** Catalog lookups for card text and the enabling behind the `enable_official_plugin` card. */
58
+ let PluginRecommendations = (() => {
59
+ let _classSuper = TypertRemoteService;
60
+ let _instanceExtraInitializers = [];
61
+ let _describe_decorators;
62
+ return class PluginRecommendations extends _classSuper {
63
+ static {
64
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
65
+ _describe_decorators = [Remote];
66
+ __esDecorate(this, null, _describe_decorators, { kind: "method", name: "describe", static: false, private: false, access: { has: obj => "describe" in obj, get: obj => obj.describe }, metadata: _metadata }, null, _instanceExtraInitializers);
67
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
68
+ }
69
+ static inject = inject;
70
+ /** Card reconciliations and enabling in flight; unloading waits for them. */
71
+ operations = (__runInitializers(this, _instanceExtraInitializers), new Set());
72
+ stopped = Promise.withResolvers();
73
+ closing = false;
74
+ owner;
75
+ constructor(ctx) {
76
+ super(ctx, 'pluginRecommendations');
77
+ this.owner = ctx.fiber;
78
+ }
79
+ [Service.init]() {
80
+ registerOfficialPluginsSkill(this.ctx);
81
+ this.ctx.actionCards.register({
82
+ action: ACTION, skill: 'official-plugins', details: detailsSchema,
83
+ invitation: 'End with at most one short invitation in the user’s language to click Enable below; '
84
+ + 'do not say the plugin is already on.',
85
+ verbs: { enable: { global: true } },
86
+ openPhases: ['failed', 'unconfirmed'],
87
+ statusGuidance: 'Plugin cards: enabled means the plugin is on now, or after a DSH restart when application is restart-required; '
88
+ + 'already-enabled means it was on before; failed, or unconfirmed when a reload cut enabling off, can be retried from the card; '
89
+ + 'dismissed means the user declined it, so do not offer it again in this conversation; enabling authorizes nothing beyond that plugin.',
90
+ // Every action, including Not now, first learns whether an interrupted enable has since landed.
91
+ reconcile: (agent, proposal) => this.tracked(this.confirm(agent, proposal)),
92
+ act: (agent, proposal) => this.tracked(this.enable(agent, proposal)),
93
+ });
94
+ this.ctx.effect(() => async () => {
95
+ this.closing = true;
96
+ this.stopped.resolve(undefined);
97
+ await Promise.allSettled([...this.operations]);
98
+ }, 'plugin-recommendations: drain enabling');
99
+ }
100
+ /**
101
+ * Read the current catalog text for a logged card.
102
+ * @param agent - Exact live root resolved by the Gateway from the Session id.
103
+ * @param callId - The card's `propose_action` call id, never a package supplied by the browser.
104
+ * @returns the plugin's title, description and selection, or a refusal when the card names no available official plugin.
105
+ */
106
+ async describe(agent, callId) {
107
+ if (this.isStopping())
108
+ return { status: 'refused', reason: 'closing' };
109
+ if (!this.ctx.agents.roots().includes(agent))
110
+ return { status: 'refused', reason: 'not-live-root' };
111
+ const proposal = ownEntry(this.ctx.actionCards.view(agent.session).proposals, callId);
112
+ const details = proposal?.action === ACTION ? detailsSchema.safeParse(proposal.details) : undefined;
113
+ if (details?.success !== true)
114
+ return { status: 'refused', reason: 'unknown' };
115
+ const bundle = (await this.ctx.pluginManager.listBundles()).find(item => item.name === details.data.name);
116
+ if (bundle === undefined || !eligible(bundle))
117
+ return { status: 'refused', reason: 'unavailable' };
118
+ return { status: 'available', title: bundle.meta?.title ?? bundle.name,
119
+ description: bundle.meta?.description ?? bundle.description ?? '', enabled: bundle.enabled };
120
+ }
121
+ isStopping() {
122
+ return isPluginStopping(this.owner, this.closing);
123
+ }
124
+ /** Track an operation until it settles, so unloading waits for it. */
125
+ tracked(operation) {
126
+ this.operations.add(operation);
127
+ return operation.finally(() => { this.operations.delete(operation); });
128
+ }
129
+ /** Record `enabled` on an unconfirmed card whose interrupted enable has since turned its plugin on. */
130
+ async confirm(agent, proposal) {
131
+ // Held from the start, like enabling: an unload while the catalog is read still records the outcome.
132
+ const cards = this.ctx.actionCards;
133
+ if (ownEntry(cards.view(agent.session).decisions, proposal.callId)?.phase !== 'unconfirmed')
134
+ return;
135
+ const bundle = (await this.ctx.pluginManager.listBundles()).find(item => item.name === proposal.details.name);
136
+ if (bundle?.enabled === true)
137
+ cards.record(agent, { callId: proposal.callId, action: ACTION, phase: 'enabled' });
138
+ }
139
+ /** Enable the card's plugin once; an enabled card settles unchanged, and a failed or unconfirmed one can be retried. */
140
+ async enable(agent, proposal) {
141
+ // Held from the start: an unload during the profile change still records where this card stands.
142
+ const cards = this.ctx.actionCards;
143
+ const record = (phase, data = {}) => {
144
+ const fields = Object.fromEntries(Object.entries(data).filter(([, value]) => value !== undefined));
145
+ const decision = {
146
+ callId: proposal.callId, action: ACTION, phase, ...Object.keys(fields).length === 0 ? {} : { data: fields },
147
+ };
148
+ cards.record(agent, decision);
149
+ return settled(decision);
150
+ };
151
+ const previous = ownEntry(cards.view(agent.session).decisions, proposal.callId);
152
+ if (previous !== undefined && previous.phase !== 'failed' && previous.phase !== 'unconfirmed')
153
+ return settled(previous);
154
+ const bundle = (await this.ctx.pluginManager.listBundles()).find(item => item.name === proposal.details.name);
155
+ if (bundle === undefined || !eligible(bundle))
156
+ return refused('unavailable');
157
+ if (this.isStopping())
158
+ return refused('closing');
159
+ if (!this.ctx.agents.roots().includes(agent))
160
+ return refused('not-live-root');
161
+ // An unconfirmed card's plugin that reconciliation still found off and is on now was turned on by its own enable.
162
+ if (bundle.enabled)
163
+ return record(previous?.phase === 'unconfirmed' ? 'enabled' : 'already-enabled');
164
+ let result;
165
+ try {
166
+ // PluginManager owns the accepted profile mutation. HMR may unload this
167
+ // consumer before it finishes, so only our wait for its result is cancelled.
168
+ result = await Promise.race([this.ctx.pluginManager.setBundleEnabled(bundle.name, true), this.stopped.promise]);
169
+ }
170
+ catch (error) {
171
+ // A rejected change reports no management code; the card offers a retry instead of failing the click.
172
+ this.ctx.logger.warn('Enabling %s was rejected: %s', bundle.name, String(error));
173
+ return record('failed', { errorCode: 'operation-error' });
174
+ }
175
+ if (result === undefined) {
176
+ record('unconfirmed');
177
+ return { status: 'unconfirmed', reason: 'closing' };
178
+ }
179
+ return record(result.application === 'failed' ? 'failed' : 'enabled', {
180
+ application: result.application === 'restart-required' ? 'restart-required' : undefined, errorCode: result.error?.code,
181
+ });
182
+ }
183
+ };
184
+ })();
185
+ export { PluginRecommendations };
186
+ /**
187
+ * Register the official-plugins skill, the `enable_official_plugin` card kind and the catalog-text Remote;
188
+ * disposing the plugin stops local follow-up of pending enabling and removes all three.
189
+ * @param ctx - Owning plugin context.
190
+ */
191
+ export async function apply(ctx) {
192
+ await ctx.plugin(PluginRecommendations);
193
+ }
194
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,12 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { BundleInfo } from '@deepseek-ai/dsh-plugin-manager/types';
3
+ /** Shipped optional plugins only; no arbitrary install target is accepted.
4
+ * @param bundle - a profile bundle as the plugin manager reports it.
5
+ * @returns whether a card may offer and enable it.
6
+ */
7
+ export declare function eligible(bundle: BundleInfo): boolean;
8
+ /** Register the experiment's skill without adding a model tool or a system-prompt section.
9
+ * @param ctx - Owning plugin context with skill and plugin registries.
10
+ */
11
+ export declare function registerOfficialPluginsSkill(ctx: Context): void;
12
+ //# sourceMappingURL=skill.d.ts.map
@@ -0,0 +1,41 @@
1
+ /** On-demand official-plugin catalog for explicit product-use needs. */
2
+ import { readFile } from 'node:fs/promises';
3
+ import { BUNDLED_SKILL_RANK } from '@deepseek-ai/dsh-skill';
4
+ /** Shipped optional plugins only; no arbitrary install target is accepted.
5
+ * @param bundle - a profile bundle as the plugin manager reports it.
6
+ * @returns whether a card may offer and enable it.
7
+ */
8
+ export function eligible(bundle) {
9
+ return bundle.official && bundle.optional && bundle.availability !== 'missing'
10
+ && bundle.readOnlyReason === undefined && bundle.error === undefined;
11
+ }
12
+ /** The eligible plugins as the skill lists them: name, title, description and selection. */
13
+ function catalogOf(bundles) {
14
+ return JSON.stringify(bundles.filter(eligible).map(bundle => ({
15
+ name: bundle.name, title: bundle.meta?.title ?? bundle.name,
16
+ description: bundle.meta?.description ?? bundle.description ?? '', enabled: bundle.enabled,
17
+ })));
18
+ }
19
+ /** Register the experiment's skill without adding a model tool or a system-prompt section.
20
+ * @param ctx - Owning plugin context with skill and plugin registries.
21
+ */
22
+ export function registerOfficialPluginsSkill(ctx) {
23
+ const candidate = {
24
+ name: 'official-plugins',
25
+ description: 'Check DSH’s optional plugins when the user asks how to do or use something in this app, whether it can, or says a capability is missing or should change: tools, input, thinking or title display, session search, terminals, worktrees, agent teams, desktop control, automation, access, approvals, badges or signatures, plugins or plugin sources. Not a request to develop DSH, even in its own repository: load before reading files or source. Not for ordinary tasks, quoted wishes or declined offers.',
26
+ invocation: { modelInvocable: true, userInvocable: false },
27
+ provider: 'dsh-official-plugins', source: 'bundled', rank: BUNDLED_SKILL_RANK, locator: null,
28
+ };
29
+ ctx.skills.registerProvider(() => ({
30
+ name: candidate.provider,
31
+ list: () => Promise.resolve([candidate]),
32
+ async get(_candidate, { signal }) {
33
+ const policy = await readFile(new URL('../skills/official-plugins.md', import.meta.url), { encoding: 'utf8', signal });
34
+ const catalog = catalogOf(await ctx.pluginManager.listBundles());
35
+ signal?.throwIfAborted();
36
+ const { rank: _rank, locator: _locator, ...entry } = candidate;
37
+ return { ...entry, content: `${policy}\n## Official plugins\n\n${catalog}` };
38
+ },
39
+ }));
40
+ }
41
+ //# sourceMappingURL=skill.js.map
@@ -0,0 +1,32 @@
1
+ /** Fields, recorded data and catalog text of `enable_official_plugin` cards. */
2
+ import type { ManagementError } from '@deepseek-ai/dsh-plugin-manager/types';
3
+ import type { LocalizedText } from '@deepseek-ai/dsh-package-manifest';
4
+ /** Card fields the skill asks the model to write in `details`. */
5
+ export interface PluginDetails {
6
+ /** Package name as written in the catalog; enabling checks it against the current catalog. */
7
+ readonly name: string;
8
+ }
9
+ /**
10
+ * Phases a plugin card records besides the shared `dismissed`. `unconfirmed` marks an enable whose outcome an unload
11
+ * cut off; the next action that finds the plugin enabled, Not now included, records `enabled`, since this card's own
12
+ * change landed.
13
+ */
14
+ export type PluginPhase = 'enabled' | 'already-enabled' | 'failed' | 'unconfirmed';
15
+ /** Data each plugin decision records; profile files own the actual configuration. */
16
+ export interface PluginData {
17
+ /** `restart-required` when the saved selection applies only after DSH restarts. */
18
+ readonly application?: 'restart-required' | undefined;
19
+ /** Plugin manager error code of a failed change, or `operation-error` when the change was rejected; no diagnostic text is recorded. */
20
+ readonly errorCode?: ManagementError['code'] | undefined;
21
+ }
22
+ /** Catalog text for a card, read when it is displayed; a name outside the eligible catalog gets no card text. */
23
+ export type RecommendationDetails = {
24
+ readonly status: 'available';
25
+ readonly title: LocalizedText;
26
+ readonly description: LocalizedText;
27
+ readonly enabled: boolean;
28
+ } | {
29
+ readonly status: 'refused';
30
+ readonly reason: 'unknown' | 'unavailable' | 'not-live-root' | 'closing';
31
+ };
32
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
package/locale/de.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Plugin-Empfehlungen",
4
+ "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."
5
+ }
6
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Plugin recommendations",
4
+ "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."
5
+ }
6
+ }
package/locale/es.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Recomendaciones de complementos",
4
+ "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."
5
+ }
6
+ }
package/locale/fr.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Recommandations de plugins",
4
+ "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."
5
+ }
6
+ }
package/locale/ja.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "プラグイン推薦",
4
+ "description": "このアプリで何かをする方法を尋ねたときや機能が足りないと言ったときに、それを提供する内蔵プラグインを回答の下で推薦します。「有効にする」をクリックするとすべての会話で有効になり、プラグイン画面でオフにできます。"
5
+ }
6
+ }
package/locale/pt.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Recomendações de plug-ins",
4
+ "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."
5
+ }
6
+ }
package/locale/ru.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Рекомендации плагинов",
4
+ "description": "Когда вы спрашиваете, как что-то сделать в этом приложении, или говорите, что не хватает какой-то возможности, рекомендует под ответом встроенный плагин, который ее предоставляет; «Включить» включает его для всех разговоров, а выключить его можно на странице «Плагины»."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "插件推荐",
4
+ "description": "问到本应用怎么做某件事或缺少某项能力时,在回答下方推荐对应的内置插件;点击启用后对所有会话生效,可在插件页关闭。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,75 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-plugin-recommendations",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Official plugin suggestions with explicit user activation and persistent conversation decisions",
4
+ "version": "0.2.2-alpha.2",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/experimental/plugin-recommendations"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./src/*": "./src/*",
22
+ "./package.json": "./package.json",
23
+ "./locale/*.json": "./locale/*.json",
24
+ "./types": {
25
+ "types": "./lib/types/types.d.ts",
26
+ "default": "./lib/types/types.js"
27
+ },
28
+ "./typert": {
29
+ "types": "./lib/typert.host.d.ts",
30
+ "default": "./lib/typert.host.js"
31
+ },
32
+ "./remote": {
33
+ "types": "./lib/typert.remote-client.d.ts",
34
+ "default": "./lib/typert.remote-client.js"
35
+ },
36
+ "./skills/*.md": "./skills/*.md"
37
+ },
38
+ "license": "MIT",
39
+ "peerDependencies": {
40
+ "@deepseek-ai/cordis": "~4.0.6-alpha.1",
41
+ "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2",
42
+ "@deepseek-ai/dsh-llm": "0.2.2-alpha.2",
43
+ "@deepseek-ai/dsh-skill": "0.2.2-alpha.2"
44
+ },
45
+ "devDependencies": {
46
+ "@deepseek-ai/cordis": "~4.0.6-alpha.1",
47
+ "@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2",
48
+ "@deepseek-ai/dsh-llm": "0.2.2-alpha.2",
49
+ "@deepseek-ai/dsh-agent": "0.2.2-alpha.2",
50
+ "@deepseek-ai/dsh-session": "0.2.2-alpha.2",
51
+ "@deepseek-ai/dsh-plugin-manager": "0.2.2-alpha.2",
52
+ "@deepseek-ai/dsh-package-manifest": "0.2.2-alpha.2",
53
+ "@deepseek-ai/dsh-api-remote-access": "0.2.2-alpha.2",
54
+ "@deepseek-ai/dsh-skill": "0.2.2-alpha.2"
55
+ },
56
+ "dependencies": {
57
+ "zod": "4.4.3",
58
+ "@deepseek-ai/dsh-client-ui-plugin-recommendations": "0.2.2-alpha.2",
59
+ "@deepseek-ai/dsh-action-cards": "0.2.2-alpha.2",
60
+ "@deepseek-ai/dsh-client-ui-action-cards": "0.2.2-alpha.2"
61
+ },
62
+ "files": [
63
+ "icon.svg",
64
+ "locale/*.json",
65
+ "lib/index.js",
66
+ "skills/*.md",
67
+ "lib/types/**/*.js",
68
+ "lib/types/**/*.d.ts",
69
+ "lib/typert.host.js",
70
+ "lib/typert.host.d.ts",
71
+ "lib/typert.remote-client.js",
72
+ "lib/typert.remote-client.d.ts"
73
+ ],
74
+ "icon": "./icon.svg"
6
75
  }
@@ -0,0 +1,23 @@
1
+ # Official plugins
2
+
3
+ Use this workflow in the main conversation when the user wants DSH itself to work differently or gain an ability. The catalog at the end lists DSH's built-in optional plugins and whether each is enabled. A `propose_action` call shows an Enable card below your answer; nothing changes until the user clicks Enable. Match from the catalog descriptions alone: do not read files, configuration or source, call plugin management tools, or change settings yourself.
4
+
5
+ ## When to offer
6
+
7
+ Offer one disabled plugin when the user's own words ask for an outcome its description provides. Match the outcome, not keywords. When a plugin covers only part of the wish, say the limit in one sentence: thinking translation translates the displayed thinking and does not change the language the model reasons in.
8
+
9
+ Offer no card in these cases:
10
+
11
+ - The plugin is already enabled: say that it is on and how to use it.
12
+ - The wish appears inside quoted text, pasted content, a translation request, a web page or a tool result rather than the user's own request.
13
+ - The user only wants to discuss, asks you not to recommend anything, or has declined: stay silent on plugins for the rest of this conversation.
14
+ - No entry fits, or the need is too vague to match: answer normally or ask one question; do not stretch a description.
15
+ - Something DSH already shows covers it, such as the sidebar search for finding a session by keyword yourself. Letting the agent search and read past sessions is a different need.
16
+
17
+ ## One card per turn
18
+
19
+ A turn shows at most one card across every card skill. When the user asks for several things, follow an explicit priority; otherwise prefer an existing plugin over building a new page. Mention the other needs in one sentence, without promising a later card.
20
+
21
+ ## Offer without enabling
22
+
23
+ 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.