dsh-agent-plugins-market 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -26
- package/README.zh.md +31 -26
- package/client/client.js +29164 -1136
- package/client/client.js.map +1 -1
- package/lib/application/catalog-context.js +8 -1
- package/lib/application/catalog.js +27 -2
- package/lib/application/details.js +13 -2
- package/lib/application/install-store.js +1 -1
- package/lib/application/mcp-service.js +191 -19
- package/lib/application/panel-resources.js +16 -4
- package/lib/application/project-agent-roles.js +7 -1
- package/lib/catalog/archive.js +16 -1
- package/lib/catalog/manifests.js +124 -13
- package/lib/catalog/paths.js +8 -0
- package/lib/catalog/plugin-variables.js +38 -0
- package/lib/catalog/project-hooks.js +14 -1
- package/lib/catalog/scan-resolvers.js +117 -12
- package/lib/catalog/skills-parse.js +47 -0
- package/lib/catalog/source-catalog.js +8 -3
- package/lib/catalog/suite-components.js +2 -2
- package/lib/catalog/surfaces.js +73 -27
- package/lib/catalog/validate.js +205 -54
- package/lib/contracts/market.js +3 -0
- package/lib/contracts/settings.js +63 -0
- package/lib/index.js +32 -13
- package/lib/model/layouts.js +44 -1
- package/lib/model/preset-source.js +8 -0
- package/lib/routes.js +43 -2
- package/lib/runtime/agent-role-router.js +203 -56
- package/lib/runtime/commands-mounts.js +50 -9
- package/lib/runtime/deadline.js +32 -6
- package/lib/runtime/dynamic-context.js +140 -0
- package/lib/runtime/feedback-tool.js +7 -3
- package/lib/runtime/host-locale.js +45 -15
- package/lib/runtime/lsp-mounts.js +23 -2
- package/lib/runtime/mcp-backend.js +14 -15
- package/lib/runtime/mcp-client/config.js +8 -0
- package/lib/runtime/mcp-client/connection.js +9 -8
- package/lib/runtime/mcp-client/host-seams.js +8 -88
- package/lib/runtime/mcp-client/oauth.js +82 -14
- package/lib/runtime/mcp-client/tools.js +2 -1
- package/lib/runtime/mcp-config.js +182 -35
- package/lib/runtime/mcp-direct-config.js +49 -6
- package/lib/runtime/mcp-mounts.js +15 -3
- package/lib/runtime/mcp-overrides.js +131 -1
- package/lib/runtime/mcp-redaction.js +8 -0
- package/lib/runtime/mcp-status.js +21 -5
- package/lib/runtime/project-runtime.js +23 -9
- package/lib/runtime/reconcile-scheduler.js +6 -8
- package/lib/runtime/reconciler.js +2 -1
- package/lib/runtime/regions.js +0 -19
- package/lib/runtime/server-config.js +49 -8
- package/lib/runtime/settings-namespace.js +21 -13
- package/lib/runtime/skills-provider.js +33 -11
- package/lib/runtime/source-auto-update.js +10 -14
- package/lib/runtime/subagent-catalog.js +62 -22
- package/lib/runtime/timer-seat.js +80 -0
- package/lib/runtime/tool-registry-observer.js +24 -20
- package/lib/runtime/user-commands.js +5 -1
- package/lib/runtime/user-panels.js +110 -41
- package/lib/runtime/user-store.js +114 -58
- package/lib/types/application/catalog-context.d.ts +6 -0
- package/lib/types/application/catalog.d.ts +19 -1
- package/lib/types/application/install-store.d.ts +1 -1
- package/lib/types/application/mcp-service.d.ts +38 -3
- package/lib/types/application/ports.d.ts +1 -1
- package/lib/types/application/queries.d.ts +8 -1
- package/lib/types/catalog/manifests.d.ts +6 -2
- package/lib/types/catalog/paths.d.ts +6 -0
- package/lib/types/catalog/plugin-variables.d.ts +16 -0
- package/lib/types/catalog/skills-parse.d.ts +13 -0
- package/lib/types/catalog/source-catalog.d.ts +7 -2
- package/lib/types/catalog/surfaces.d.ts +14 -4
- package/lib/types/catalog/validate.d.ts +72 -11
- package/lib/types/contracts/market.d.ts +77 -0
- package/lib/types/contracts/mcp-status.d.ts +11 -0
- package/lib/types/contracts/settings.d.ts +59 -0
- package/lib/types/model/layouts.d.ts +30 -0
- package/lib/types/model/preset-source.d.ts +18 -0
- package/lib/types/model/types.d.ts +37 -1
- package/lib/types/runtime/agent-role-router.d.ts +96 -18
- package/lib/types/runtime/commands-mounts.d.ts +15 -7
- package/lib/types/runtime/deadline.d.ts +10 -11
- package/lib/types/runtime/dynamic-context.d.ts +81 -0
- package/lib/types/runtime/host-locale.d.ts +20 -2
- package/lib/types/runtime/lsp-mounts.d.ts +7 -0
- package/lib/types/runtime/mcp-backend.d.ts +8 -15
- package/lib/types/runtime/mcp-client/config.d.ts +8 -0
- package/lib/types/runtime/mcp-client/host-seams.d.ts +20 -64
- package/lib/types/runtime/mcp-client/oauth.d.ts +4 -0
- package/lib/types/runtime/mcp-config.d.ts +75 -13
- package/lib/types/runtime/mcp-direct-config.d.ts +22 -0
- package/lib/types/runtime/mcp-overrides.d.ts +44 -0
- package/lib/types/runtime/mcp-redaction.d.ts +6 -0
- package/lib/types/runtime/project-runtime.d.ts +10 -3
- package/lib/types/runtime/reconcile-scheduler.d.ts +1 -1
- package/lib/types/runtime/regions.d.ts +1 -4
- package/lib/types/runtime/server-config.d.ts +26 -1
- package/lib/types/runtime/settings-namespace.d.ts +10 -10
- package/lib/types/runtime/skills-provider.d.ts +11 -2
- package/lib/types/runtime/source-auto-update.d.ts +3 -4
- package/lib/types/runtime/subagent-catalog.d.ts +14 -2
- package/lib/types/runtime/timer-seat.d.ts +51 -0
- package/lib/types/runtime/tool-registry-observer.d.ts +14 -6
- package/lib/types/runtime/user-panels.d.ts +65 -12
- package/lib/types/runtime/user-store.d.ts +65 -16
- package/package.json +23 -6
- package/schemas/1.0.0/mcp.schema.json +0 -22
- package/schemas/1.1.0/mcp.schema.json +120 -0
- package/schemas/1.1.0/plugin.schema.json +65 -0
- package/schemas/README.md +25 -19
- package/schemas/agent-plugins/spec.md +23 -9
- package/schemas/com.deepseek.harness/namespace.schema.json +42 -0
- package/schemas/com.deepseek.harness/spec.md +52 -0
|
@@ -7,24 +7,29 @@
|
|
|
7
7
|
* `.agents/skills` (200) but beat custom (300); user suites (450) lose to
|
|
8
8
|
* the user's own `~/.dsh/skills` (400) and beat `~/.agents/skills` (500).
|
|
9
9
|
*
|
|
10
|
-
* Bodies are rewritten on load:
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Bodies are rewritten on load: the path variables Claude Code authors write
|
|
11
|
+
* into skill prose (`${CLAUDE_PLUGIN_ROOT}`, `${CLAUDE_PLUGIN_DATA}`,
|
|
12
|
+
* `${CLAUDE_PROJECT_DIR}`, `${CLAUDE_SKILL_DIR}`) are substituted, `` !`cmd` ``
|
|
13
|
+
* dynamic-context placeholders run, and the resource base points at the skill
|
|
14
|
+
* directory, so CC-authored skills work verbatim under the harness.
|
|
14
15
|
*/
|
|
15
16
|
import { readFile } from 'node:fs/promises';
|
|
16
17
|
import { parseSkillFrontmatter, stripFrontmatter } from '../catalog/skills-parse.js';
|
|
17
18
|
import { parseFrontmatterRecord } from './user-store.js';
|
|
18
|
-
import {
|
|
19
|
+
import { expandPluginPaths, pluginRootOf } from '../catalog/plugin-variables.js';
|
|
20
|
+
import { suiteDataDir } from '../catalog/paths.js';
|
|
21
|
+
import { injectDynamicContext } from './dynamic-context.js';
|
|
19
22
|
export const SUITE_PROJECT_SOURCE = 'agent-plugin-project';
|
|
20
23
|
export const SUITE_USER_SOURCE = 'agent-plugin-user';
|
|
21
24
|
const PROJECT_RANK = 250;
|
|
22
25
|
const USER_RANK = 450;
|
|
23
26
|
export class SuiteSkillProvider {
|
|
24
27
|
manager;
|
|
28
|
+
options;
|
|
25
29
|
name = 'agent-plugin';
|
|
26
|
-
constructor(manager) {
|
|
30
|
+
constructor(manager, options = {}) {
|
|
27
31
|
this.manager = manager;
|
|
32
|
+
this.options = options;
|
|
28
33
|
}
|
|
29
34
|
async list(options) {
|
|
30
35
|
const located = await this.locate(options.cwd);
|
|
@@ -42,7 +47,7 @@ export class SuiteSkillProvider {
|
|
|
42
47
|
}
|
|
43
48
|
return unique.map(entry => this.candidateFor(entry));
|
|
44
49
|
}
|
|
45
|
-
async get(candidate,
|
|
50
|
+
async get(candidate, options) {
|
|
46
51
|
const locator = candidate.locator;
|
|
47
52
|
let text;
|
|
48
53
|
try {
|
|
@@ -61,9 +66,21 @@ export class SuiteSkillProvider {
|
|
|
61
66
|
const parsed = parseSkillFrontmatter(text, candidate.name);
|
|
62
67
|
if (typeof parsed === 'string')
|
|
63
68
|
return undefined;
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
.
|
|
69
|
+
const authored = [stripFrontmatter(text), ...(locator.skillInstructions === undefined ? [] : [locator.skillInstructions])].join('\n\n');
|
|
70
|
+
const withPaths = expandPluginPaths(authored, {
|
|
71
|
+
...(locator.suiteRoot === undefined ? {} : { root: locator.suiteRoot }),
|
|
72
|
+
...(locator.data === undefined ? {} : { data: locator.data }),
|
|
73
|
+
skillDir: locator.directory,
|
|
74
|
+
...(options.cwd === undefined ? {} : { projectDir: options.cwd })
|
|
75
|
+
});
|
|
76
|
+
const shell = this.options.shell?.();
|
|
77
|
+
const content = shell === undefined
|
|
78
|
+
? withPaths
|
|
79
|
+
: await injectDynamicContext(withPaths, {
|
|
80
|
+
shell,
|
|
81
|
+
...(options.cwd === undefined ? {} : { workdir: options.cwd }),
|
|
82
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
83
|
+
});
|
|
67
84
|
return {
|
|
68
85
|
name: parsed.name,
|
|
69
86
|
description: candidate.description,
|
|
@@ -92,7 +109,12 @@ export class SuiteSkillProvider {
|
|
|
92
109
|
...(entry.suite.manifest.skillInstructions === undefined ? {} : { skillInstructions: entry.suite.manifest.skillInstructions }),
|
|
93
110
|
file: entry.skill.file,
|
|
94
111
|
directory: entry.skill.directory,
|
|
95
|
-
|
|
112
|
+
...(pluginRootOf(entry.suite) === undefined
|
|
113
|
+
? {}
|
|
114
|
+
: {
|
|
115
|
+
suiteRoot: entry.suite.root,
|
|
116
|
+
...(this.options.dataRoot === undefined ? {} : { data: suiteDataDir(this.options.dataRoot, entry.suite.sourceId, entry.suite.id) })
|
|
117
|
+
})
|
|
96
118
|
},
|
|
97
119
|
path: entry.skill.file,
|
|
98
120
|
resourceBase: { kind: 'directory', path: entry.skill.directory }
|
|
@@ -6,17 +6,18 @@
|
|
|
6
6
|
* {@link AUTO_UPDATE_INTERVAL_MS} and then waits again; the first pass runs a
|
|
7
7
|
* full interval after the switch flips, so enabling the feature never starts
|
|
8
8
|
* network traffic as a surprise. A pass that is still running when the next
|
|
9
|
-
* tick arrives is skipped rather than queued
|
|
10
|
-
*
|
|
9
|
+
* tick arrives is skipped rather than queued. The repetition rides the fiber's
|
|
10
|
+
* timer seat, so it dies with this plugin.
|
|
11
11
|
* @module runtime/source-auto-update
|
|
12
12
|
*/
|
|
13
|
+
import { repeat } from './timer-seat.js';
|
|
13
14
|
/** How often the background updater refreshes every source. */
|
|
14
15
|
export const AUTO_UPDATE_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
15
16
|
export class SourceAutoUpdater {
|
|
16
17
|
ctx;
|
|
17
18
|
refresh;
|
|
18
19
|
intervalMs;
|
|
19
|
-
|
|
20
|
+
stop;
|
|
20
21
|
inFlight = false;
|
|
21
22
|
constructor(ctx, refresh, intervalMs = AUTO_UPDATE_INTERVAL_MS) {
|
|
22
23
|
this.ctx = ctx;
|
|
@@ -25,30 +26,25 @@ export class SourceAutoUpdater {
|
|
|
25
26
|
}
|
|
26
27
|
/** Whether a refresh timer is currently armed. */
|
|
27
28
|
get enabled() {
|
|
28
|
-
return this.
|
|
29
|
+
return this.stop !== undefined;
|
|
29
30
|
}
|
|
30
31
|
/** Arm or disarm the interval; repeated calls with the same state do nothing. */
|
|
31
32
|
setEnabled(enabled) {
|
|
32
33
|
if (enabled === this.enabled)
|
|
33
34
|
return;
|
|
34
35
|
if (!enabled) {
|
|
35
|
-
this.
|
|
36
|
+
this.stop?.();
|
|
37
|
+
this.stop = undefined;
|
|
36
38
|
this.ctx.logger?.info?.('[dsh-agent-plugins-market] background source updates off');
|
|
37
39
|
return;
|
|
38
40
|
}
|
|
39
|
-
this.
|
|
40
|
-
this.timer.unref?.();
|
|
41
|
+
this.stop = repeat(this.ctx, () => void this.run(), this.intervalMs);
|
|
41
42
|
this.ctx.logger?.info?.(`[dsh-agent-plugins-market] background source updates on: every ${Math.round(this.intervalMs / 60_000)} minutes`);
|
|
42
43
|
}
|
|
43
44
|
/** Disarm the interval; an in-flight pass finishes on its own. */
|
|
44
45
|
dispose() {
|
|
45
|
-
this.
|
|
46
|
-
|
|
47
|
-
clearTimer() {
|
|
48
|
-
if (this.timer === undefined)
|
|
49
|
-
return;
|
|
50
|
-
clearInterval(this.timer);
|
|
51
|
-
this.timer = undefined;
|
|
46
|
+
this.stop?.();
|
|
47
|
+
this.stop = undefined;
|
|
52
48
|
}
|
|
53
49
|
async run() {
|
|
54
50
|
if (this.inFlight) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
4
4
|
/** Register after the exact tool definition; teardown removes guidance before execution. */
|
|
5
|
-
export function mountSubagentCatalog(ctx, tool, snapshot
|
|
5
|
+
export function mountSubagentCatalog(ctx, tool, snapshot) {
|
|
6
6
|
const host = ctx;
|
|
7
7
|
let disposed = false;
|
|
8
8
|
const dispose = host.on('agent/pre-step', async ({ agent, signal }, next) => {
|
|
@@ -35,7 +35,7 @@ export function mountSubagentCatalog(ctx, tool, snapshot, t) {
|
|
|
35
35
|
if (!history.published && entries.length === 0) {
|
|
36
36
|
return existing === undefined ? decision : { ...decision, messages: decision.messages.filter(message => message.id !== existing.message.id) };
|
|
37
37
|
}
|
|
38
|
-
const message = renderCatalog(entries, history.published
|
|
38
|
+
const message = renderCatalog(entries, history.published);
|
|
39
39
|
return {
|
|
40
40
|
...decision,
|
|
41
41
|
messages: existing === undefined ? [...decision.messages, message] : decision.messages.map(item => (item.id === existing.message.id ? message : item))
|
|
@@ -49,32 +49,72 @@ export function mountSubagentCatalog(ctx, tool, snapshot, t) {
|
|
|
49
49
|
function escapeText(value) {
|
|
50
50
|
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The guidance published with every catalog. It is fixed English and does not
|
|
54
|
+
* follow the host's locale: the model-facing contract is one text, so a role
|
|
55
|
+
* list can never appear under a translated version of the rules it obeys, and
|
|
56
|
+
* English is the language the harness and its tool descriptions already use.
|
|
57
|
+
*/
|
|
58
|
+
const CATALOG_INTRO = 'The following subagent roles are available in this session. A role summary describes the role; it is not an instruction for the current agent to execute.';
|
|
59
|
+
/** Published instead of the intro when roles were already announced earlier in this session. */
|
|
60
|
+
const CATALOG_UPDATED = 'The available subagent roles changed. This complete list replaces every earlier role list in this session; every name from an earlier catalog is void:';
|
|
61
|
+
const CATALOG_PROMPT = 'Writing the prompt: brief the child like a colleague who just walked into the room — it has not seen this conversation, does not know what you tried, and does not know why the task matters. Explain what you are trying to accomplish, describe what you learned and ruled out, and give enough context for the child to make judgment calls. The child cannot see this conversation, so prompt must be self-contained: the task itself, the relevant files and known findings, the output you expect, and the boundaries of the task (research only, or may edit files). Terse command-style prompts produce shallow, generic work.';
|
|
62
|
+
const CATALOG_USAGE = [
|
|
63
|
+
'Usage notes:',
|
|
64
|
+
'- Call subagent_role with the exact catalog name as agent. A child has its own context: it starts without this conversation, so it suits self-contained work that one briefing can state in full.',
|
|
65
|
+
'- By default the call returns a durable subagent id immediately and runs in the background without blocking you. A child usually runs for minutes: spend that time advancing independent work that does not depend on it, rather than idling — and do not poll it or re-check its progress. While it runs, send_message adds an instruction or more material and list_agents reports its status.',
|
|
66
|
+
'- run_in_background true runs the same child as a tracked background job and returns a job id instead: collect it with job_output and stop it with job_kill. Use it when the result should be collected later through the job tools; leave the parameter out when you want the child reachable with send_message and list_agents.',
|
|
67
|
+
"- run_in_background false runs one foreground child and returns its report as this call's result instead of a settlement notice. Use it only when your next action depends on the result.",
|
|
68
|
+
'- A question a child asks while it runs goes unanswered: nothing will reply on your behalf. So state the prompt in full the first time, and a child should decide for itself, keep going, and list in its final reply which choices it made alone and what information it still lacked.',
|
|
69
|
+
"- A background child's output is not visible to the user. When it finishes, summarize the result to the user yourself.",
|
|
70
|
+
"- The child's settlement notice arrives as a user message, not as a tool result. Until it arrives, do not assume or predict its findings, and do not deliver anything that depends on them; act on them once it lands, by verifying or delegating further as needed.",
|
|
71
|
+
"- The child's final reply is its own report: check its assertions against the files themselves, and treat its statements about its own configuration the same way — a child cannot see how its role instructions were installed.",
|
|
72
|
+
"- The route parameters override the route the role card declares; omit them to use the card's route, or the parent route when the card declares none.",
|
|
73
|
+
'- Do not duplicate work a child is already doing.',
|
|
74
|
+
'- When several children run at once, give each its own git worktree and name the files it owns in prompt.'
|
|
75
|
+
].join('\n');
|
|
76
|
+
const CATALOG_WHEN_NOT_TO_USE = "When NOT to use a role child: no listed role matches; your next action needs the result; or another child is already doing this work. When no role matches, do not substitute a similarly named one — delegate through one of the host's general delegation channels instead. Do not load these roles through skill or slash commands.";
|
|
77
|
+
/**
|
|
78
|
+
* Model-facing catalog text: the role list followed by the same guidance on
|
|
79
|
+
* first publication and on every update, so a replacement never arrives without
|
|
80
|
+
* the rules that govern it. The guidance covers how to brief a child, how to
|
|
81
|
+
* work with one while it runs, and when a role child is the wrong answer; the
|
|
82
|
+
* per-channel arbitration between the host's delegation tools stays in the tool
|
|
83
|
+
* descriptions the harness owns.
|
|
84
|
+
*
|
|
85
|
+
* Role lines carry a name and a description only. The configured route stays in
|
|
86
|
+
* the durable entries for execution and change detection; publishing it would
|
|
87
|
+
* tell the model which model each role runs on without changing what it does.
|
|
88
|
+
*/
|
|
89
|
+
export function renderCatalogText(entries, update) {
|
|
90
|
+
const lines = entries.map(entry => `- \`${escapeText(entry.name)}\`: ${escapeText(entry.description)}`);
|
|
91
|
+
return [
|
|
92
|
+
'<system-reminder>',
|
|
93
|
+
update ? CATALOG_UPDATED : CATALOG_INTRO,
|
|
94
|
+
'<available_subagents>',
|
|
95
|
+
...lines,
|
|
96
|
+
'</available_subagents>',
|
|
97
|
+
CATALOG_PROMPT,
|
|
98
|
+
CATALOG_USAGE,
|
|
99
|
+
CATALOG_WHEN_NOT_TO_USE,
|
|
100
|
+
'</system-reminder>'
|
|
101
|
+
].join('\n');
|
|
102
|
+
}
|
|
103
|
+
function renderCatalog(entries, update) {
|
|
57
104
|
return createUserMessage({
|
|
58
|
-
content: [
|
|
59
|
-
{
|
|
60
|
-
type: 'text',
|
|
61
|
-
text: [
|
|
62
|
-
'<system-reminder>',
|
|
63
|
-
t(update ? 'subagentCatalogUpdated' : 'subagentCatalogIntro'),
|
|
64
|
-
'<available_subagents>',
|
|
65
|
-
...lines,
|
|
66
|
-
'</available_subagents>',
|
|
67
|
-
t(entries.length === 0 ? 'subagentCatalogEmpty' : 'subagentCatalogCall'),
|
|
68
|
-
'</system-reminder>'
|
|
69
|
-
].join('\n')
|
|
70
|
-
}
|
|
71
|
-
],
|
|
105
|
+
content: [{ type: 'text', text: renderCatalogText(entries, update) }],
|
|
72
106
|
source: { kind: 'subagent-catalog', form: 'catalog', ...(update ? { update: true } : {}), entries }
|
|
73
107
|
});
|
|
74
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Change detection follows what the model and the executor actually see. A role's
|
|
111
|
+
* display title never reaches the model-facing catalog, so renaming it must not
|
|
112
|
+
* re-publish; the durable role id and the configured route stay in the digest
|
|
113
|
+
* because swapping either behind an unchanged line changes what the child runs.
|
|
114
|
+
*/
|
|
75
115
|
function digestEntries(entries) {
|
|
76
116
|
return createHash('sha256')
|
|
77
|
-
.update(entries.map(entry => JSON.stringify([entry.name, entry.roleId, entry.
|
|
117
|
+
.update(entries.map(entry => JSON.stringify([entry.name, entry.roleId, entry.description, entry.provider, entry.model, entry.reasoningEffort])).join('\n'))
|
|
78
118
|
.digest('hex');
|
|
79
119
|
}
|
|
80
120
|
/** Resumed and forked seeds may carry unvalidated source fields; ignore unreadable catalogs. */
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
function seatOf(ctx) {
|
|
2
|
+
const seat = ctx.get('timer');
|
|
3
|
+
if (seat === undefined)
|
|
4
|
+
return undefined;
|
|
5
|
+
return typeof seat.interval === 'function' && typeof seat.timeout === 'function' && typeof seat.debounce === 'function' ? seat : undefined;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Repeat `callback` every `delayMs` until the returned disposer runs or the
|
|
9
|
+
* fiber disposes.
|
|
10
|
+
* @param ctx - the owning plugin context.
|
|
11
|
+
* @param callback - the repeating work.
|
|
12
|
+
* @param delayMs - the period.
|
|
13
|
+
* @returns the disposer that stops the repetition.
|
|
14
|
+
*/
|
|
15
|
+
export function repeat(ctx, callback, delayMs) {
|
|
16
|
+
const seat = seatOf(ctx);
|
|
17
|
+
if (seat !== undefined)
|
|
18
|
+
return seat.interval(callback, delayMs);
|
|
19
|
+
const timer = setInterval(callback, delayMs);
|
|
20
|
+
timer.unref?.();
|
|
21
|
+
return () => {
|
|
22
|
+
clearInterval(timer);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Run `callback` once after `delayMs`, unless the returned disposer runs first
|
|
27
|
+
* or the fiber disposes.
|
|
28
|
+
* @param ctx - the owning plugin context.
|
|
29
|
+
* @param callback - the deferred work.
|
|
30
|
+
* @param delayMs - the delay.
|
|
31
|
+
* @returns the disposer that cancels the pending run.
|
|
32
|
+
*/
|
|
33
|
+
export function defer(ctx, callback, delayMs) {
|
|
34
|
+
const seat = seatOf(ctx);
|
|
35
|
+
if (seat !== undefined)
|
|
36
|
+
return seat.timeout(callback, delayMs);
|
|
37
|
+
const timer = setTimeout(callback, delayMs);
|
|
38
|
+
timer.unref?.();
|
|
39
|
+
return () => {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Collapse a burst of calls into one run of `callback`, `delayMs` after the
|
|
45
|
+
* last call, until the trigger is disposed or the fiber disposes.
|
|
46
|
+
* @param ctx - the owning plugin context.
|
|
47
|
+
* @param callback - the coalesced work.
|
|
48
|
+
* @param delayMs - the quiet period.
|
|
49
|
+
* @returns the trigger to call per event and to dispose at teardown.
|
|
50
|
+
*/
|
|
51
|
+
export function coalesce(ctx, callback, delayMs) {
|
|
52
|
+
const seat = seatOf(ctx);
|
|
53
|
+
if (seat !== undefined) {
|
|
54
|
+
const debounced = seat.debounce(callback, delayMs);
|
|
55
|
+
// The seat returns the debounced function itself, carrying its own
|
|
56
|
+
// disposer; capture that disposer before this wrapper replaces the field.
|
|
57
|
+
const stopSeat = debounced.dispose;
|
|
58
|
+
const trigger = debounced;
|
|
59
|
+
trigger.dispose = () => {
|
|
60
|
+
stopSeat?.call(debounced);
|
|
61
|
+
};
|
|
62
|
+
return trigger;
|
|
63
|
+
}
|
|
64
|
+
let timer;
|
|
65
|
+
const trigger = (() => {
|
|
66
|
+
if (timer !== undefined)
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
timer = setTimeout(() => {
|
|
69
|
+
timer = undefined;
|
|
70
|
+
callback();
|
|
71
|
+
}, delayMs);
|
|
72
|
+
timer.unref?.();
|
|
73
|
+
});
|
|
74
|
+
trigger.dispose = () => {
|
|
75
|
+
if (timer !== undefined)
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
timer = undefined;
|
|
78
|
+
};
|
|
79
|
+
return trigger;
|
|
80
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Reading the model-facing MCP tools the host registry currently exposes. */
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* The MCP tools the host currently publishes to the model.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Read through the tools service's own listing API rather than its internal
|
|
6
|
+
* layer structure. `schemas()` deep-clones each tool's parameter schema, so a
|
|
7
|
+
* failure inside it degrades to an empty observation instead of taking the
|
|
8
|
+
* status surface down.
|
|
9
|
+
* @param tools - the host tools service, when it is mounted.
|
|
10
|
+
* @returns one entry per `mcp__`-namespaced tool.
|
|
8
11
|
*/
|
|
9
|
-
export function inspectToolRegistry(
|
|
10
|
-
|
|
12
|
+
export function inspectToolRegistry(tools) {
|
|
13
|
+
const service = tools;
|
|
14
|
+
if (service === undefined || typeof service !== 'object' || service === null)
|
|
11
15
|
return [];
|
|
12
|
-
|
|
13
|
-
if (typeof layers !== 'object' || layers === null)
|
|
16
|
+
if (typeof service.schemas !== 'function')
|
|
14
17
|
return [];
|
|
15
|
-
const merge = layers.merge;
|
|
16
|
-
if (typeof merge !== 'function')
|
|
17
|
-
return [];
|
|
18
|
-
const empty = { entries: () => [] };
|
|
19
18
|
try {
|
|
20
|
-
const visible = merge.call(layers, undefined, layer => layer.tools ?? empty);
|
|
21
19
|
const output = [];
|
|
22
|
-
for (const
|
|
23
|
-
if (!name.startsWith('mcp__'))
|
|
20
|
+
for (const schema of service.schemas()) {
|
|
21
|
+
if (!schema.name.startsWith('mcp__'))
|
|
24
22
|
continue;
|
|
25
|
-
const description = typeof
|
|
26
|
-
|
|
27
|
-
:
|
|
28
|
-
|
|
23
|
+
const description = typeof schema.description === 'string' ? schema.description : undefined;
|
|
24
|
+
output.push({
|
|
25
|
+
name: schema.name,
|
|
26
|
+
...(description === undefined ? {} : { description }),
|
|
27
|
+
...(schema.parameters === undefined ? {} : { parameters: schema.parameters })
|
|
28
|
+
});
|
|
29
29
|
}
|
|
30
30
|
return output;
|
|
31
31
|
}
|
|
@@ -33,3 +33,7 @@ export function inspectToolRegistry(runtime) {
|
|
|
33
33
|
return [];
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
/** Read the tools service off the plugin context, when the host mounts one. */
|
|
37
|
+
export function toolsServiceOf(ctx) {
|
|
38
|
+
return ctx.get?.('tools');
|
|
39
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* change, keyed `user/<name>`.
|
|
7
7
|
* @module runtime/user-commands
|
|
8
8
|
*/
|
|
9
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
9
10
|
import { USER_ENTRY_NAME } from './user-store.js';
|
|
10
11
|
/** Register/unregister user-panel commands to match the panel's enabled entries. */
|
|
11
12
|
export class UserCommandMountRegistry {
|
|
@@ -63,7 +64,10 @@ export class UserCommandMountRegistry {
|
|
|
63
64
|
handler: invocation => {
|
|
64
65
|
const agent = invocation.agent;
|
|
65
66
|
const text = spec.body.replaceAll('$ARGUMENTS', invocation.rawInput.trim());
|
|
66
|
-
agent.followup({
|
|
67
|
+
agent.followup(createUserMessage({
|
|
68
|
+
content: [{ type: 'text', text }],
|
|
69
|
+
source: { kind: 'plugin', plugin: 'dsh-agent-plugins-market' }
|
|
70
|
+
}));
|
|
67
71
|
return { kind: 'success', text: this.t('commandAcknowledged', { command: spec.name }) };
|
|
68
72
|
}
|
|
69
73
|
});
|