@kolisachint/hoocode-agent 0.4.131 → 0.4.133
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/CHANGELOG.md +34 -0
- package/dist/core/extensions/plugins/authoring.d.ts +67 -0
- package/dist/core/extensions/plugins/authoring.d.ts.map +1 -1
- package/dist/core/extensions/plugins/authoring.js +0 -0
- package/dist/core/extensions/plugins/authoring.js.map +1 -1
- package/dist/core/tools/plugin-tool-names.d.ts +2 -1
- package/dist/core/tools/plugin-tool-names.d.ts.map +1 -1
- package/dist/core/tools/plugin-tool-names.js +11 -3
- package/dist/core/tools/plugin-tool-names.js.map +1 -1
- package/dist/core/tools/plugins.d.ts.map +1 -1
- package/dist/core/tools/plugins.js +10 -6
- package/dist/core/tools/plugins.js.map +1 -1
- package/dist/core/tools/propose-plugin.d.ts +26 -16
- package/dist/core/tools/propose-plugin.d.ts.map +1 -1
- package/dist/core/tools/propose-plugin.js +276 -143
- package/dist/core/tools/propose-plugin.js.map +1 -1
- package/dist/extensions/core/ask-options.d.ts.map +1 -1
- package/dist/extensions/core/ask-options.js +42 -0
- package/dist/extensions/core/ask-options.js.map +1 -1
- package/dist/extensions/core/loop.d.ts +12 -0
- package/dist/extensions/core/loop.d.ts.map +1 -1
- package/dist/extensions/core/loop.js +35 -5
- package/dist/extensions/core/loop.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Capability authoring tools (spec §3)
|
|
3
|
-
* kept as *separate tools* by design — not collapsed into one:
|
|
2
|
+
* Capability authoring tools (spec §3), refactored to a single risk-gated path.
|
|
4
3
|
*
|
|
5
|
-
* ProposePlugin
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* ProposePlugin author a NEW plugin from any capability mix — skills,
|
|
5
|
+
* commands, subagents, hooks, MCP servers. The risk gate is
|
|
6
|
+
* *computed from content*, not pre-declared by tool choice:
|
|
7
|
+
* passive content (skills, commands, read-only subagents) is
|
|
8
|
+
* authored autonomously; executable content (hooks, MCP servers,
|
|
9
|
+
* mutating/high-privilege subagents) auto-triggers a "show the
|
|
10
|
+
* code + tool grant → human confirms → activate" gate in the
|
|
11
|
+
* same call. A mixed plugin (skill + hook) is authored in one
|
|
12
|
+
* call, and a hook can never be mis-routed through a "passive"
|
|
13
|
+
* tool because the gate keys off what the draft contains.
|
|
14
|
+
* UpdatePlugin merge inline-authored capabilities into an EXISTING local
|
|
15
|
+
* plugin. Nothing is fetched from a remote, so the supply-chain
|
|
16
|
+
* "benign v1 → hostile v2" risk that keeps a marketplace
|
|
17
|
+
* UpdatePlugin out of the model's hands does not apply here;
|
|
18
|
+
* executable additions still pass through the same confirm gate.
|
|
12
19
|
*
|
|
13
20
|
* Both author into `.agents/plugins/<id>/` in the requested vendor layouts
|
|
14
21
|
* (Claude Code + GitHub Copilot by default) via the format registry, so results
|
|
@@ -19,10 +26,10 @@
|
|
|
19
26
|
* both tools — so a low-trust authored agent cannot bootstrap privilege.
|
|
20
27
|
*/
|
|
21
28
|
import { Type } from "typebox";
|
|
22
|
-
import { classifyAllowlist, pluginExists, resolveAuthoringPlatforms, writePluginDraft, } from "../extensions/plugins/authoring.js";
|
|
29
|
+
import { classifyAllowlist, getPlugin, isAuthoredPlugin, mergePluginDraft, pluginExists, removeFromPlugin, resolveAuthoringPlatforms, writePluginDraft, } from "../extensions/plugins/authoring.js";
|
|
23
30
|
import { defineTool } from "../extensions/types.js";
|
|
24
|
-
import {
|
|
25
|
-
export {
|
|
31
|
+
import { PROPOSE_PLUGIN_TOOL_NAME, REMOVE_PLUGIN_CAPABILITY_TOOL_NAME, UPDATE_PLUGIN_TOOL_NAME, } from "./plugin-tool-names.js";
|
|
32
|
+
export { PROPOSE_PLUGIN_TOOL_NAME, REMOVE_PLUGIN_CAPABILITY_TOOL_NAME, UPDATE_PLUGIN_TOOL_NAME, } from "./plugin-tool-names.js";
|
|
26
33
|
const platformSchema = Type.Union([Type.Literal("claude"), Type.Literal("github"), Type.Literal("agents")], {
|
|
27
34
|
description: "Target format: claude (Claude Code), github (GitHub Copilot), or agents (native).",
|
|
28
35
|
});
|
|
@@ -39,7 +46,10 @@ const commandSchema = Type.Object({
|
|
|
39
46
|
const subagentSchema = Type.Object({
|
|
40
47
|
name: Type.String({ description: "Subagent name." }),
|
|
41
48
|
description: Type.Optional(Type.String({ description: "When to dispatch this subagent." })),
|
|
42
|
-
tools: Type.Optional(Type.String({
|
|
49
|
+
tools: Type.Optional(Type.String({
|
|
50
|
+
description: "Comma-separated allowed-tools, e.g. 'read, grep, glob'. Read-only grants are autonomous; " +
|
|
51
|
+
"mutating/exec/network grants (Bash, Write, Edit, MCP) or '*' require human confirmation. Omit for none.",
|
|
52
|
+
})),
|
|
43
53
|
model: Type.Optional(Type.String({ description: "Model override, or 'inherit'." })),
|
|
44
54
|
body: Type.String({ description: "System-prompt / instruction body (markdown)." }),
|
|
45
55
|
}, { additionalProperties: false });
|
|
@@ -55,177 +65,300 @@ const mcpServerSchema = Type.Object({
|
|
|
55
65
|
args: Type.Optional(Type.Array(Type.String(), { description: "Command arguments." })),
|
|
56
66
|
env: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Environment variables." })),
|
|
57
67
|
}, { additionalProperties: false });
|
|
68
|
+
/** The capability params shared by ProposePlugin (create) and UpdatePlugin (merge). */
|
|
69
|
+
const capabilityProps = {
|
|
70
|
+
description: Type.Optional(Type.String({ description: "Plugin description." })),
|
|
71
|
+
version: Type.Optional(Type.String({ description: "Plugin version, e.g. '0.1.0'." })),
|
|
72
|
+
platforms: Type.Optional(Type.Array(platformSchema, {
|
|
73
|
+
description: "Formats to scaffold into. Default: the session's --support-platform targets, else claude + github " +
|
|
74
|
+
"(UpdatePlugin defaults to the plugin's existing platforms).",
|
|
75
|
+
})),
|
|
76
|
+
skills: Type.Optional(Type.Array(skillSchema)),
|
|
77
|
+
commands: Type.Optional(Type.Array(commandSchema)),
|
|
78
|
+
subagents: Type.Optional(Type.Array(subagentSchema, {
|
|
79
|
+
description: "Subagents. Read-only allowlists are autonomous; mutating ones trigger human confirmation.",
|
|
80
|
+
})),
|
|
81
|
+
hooks: Type.Optional(Type.Array(hookSchema, { description: "Shell hooks (executable — trigger confirmation)." })),
|
|
82
|
+
mcpServers: Type.Optional(Type.Array(mcpServerSchema, { description: "MCP servers (executable — trigger confirmation)." })),
|
|
83
|
+
};
|
|
58
84
|
function resolvePlatforms(input) {
|
|
59
85
|
// Explicit tool param → session --support-platform targets → claude + github.
|
|
60
86
|
return resolveAuthoringPlatforms(input);
|
|
61
87
|
}
|
|
62
|
-
function
|
|
63
|
-
return
|
|
88
|
+
function draftFrom(id, params, platforms) {
|
|
89
|
+
return {
|
|
90
|
+
id,
|
|
91
|
+
version: params.version,
|
|
92
|
+
description: params.description,
|
|
93
|
+
supportPlatform: platforms,
|
|
94
|
+
skills: params.skills,
|
|
95
|
+
commands: params.commands,
|
|
96
|
+
agents: params.subagents,
|
|
97
|
+
hooks: params.hooks,
|
|
98
|
+
mcpServers: params.mcpServers,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Total capabilities carried by the draft (empty arrays count as nothing). */
|
|
102
|
+
function capabilityCount(params) {
|
|
103
|
+
return ((params.skills?.length ?? 0) +
|
|
104
|
+
(params.commands?.length ?? 0) +
|
|
105
|
+
(params.subagents?.length ?? 0) +
|
|
106
|
+
(params.hooks?.length ?? 0) +
|
|
107
|
+
(params.mcpServers?.length ?? 0));
|
|
108
|
+
}
|
|
109
|
+
/** The subagents whose allowlist makes them mutating/high-privilege (need the confirm gate). */
|
|
110
|
+
function mutatingSubagents(params) {
|
|
111
|
+
return (params.subagents ?? []).filter((sa) => classifyAllowlist(sa.tools).risk === "mutating");
|
|
112
|
+
}
|
|
113
|
+
/** True when the draft carries anything executable — hooks, MCP servers, or a mutating subagent. */
|
|
114
|
+
function hasExecutable(params) {
|
|
115
|
+
return ((params.hooks?.length ?? 0) > 0 || (params.mcpServers?.length ?? 0) > 0 || mutatingSubagents(params).length > 0);
|
|
116
|
+
}
|
|
117
|
+
/** Reject if any subagent carries a plugin-system tool (privilege-amplification guardrail). Returns the message, or null. */
|
|
118
|
+
function guardrailViolation(params) {
|
|
119
|
+
for (const sa of params.subagents ?? []) {
|
|
120
|
+
const cls = classifyAllowlist(sa.tools);
|
|
121
|
+
if (cls.pluginTools.length > 0) {
|
|
122
|
+
return (`Subagent "${sa.name}" requests plugin-system tools (${cls.pluginTools.join(", ")}). ` +
|
|
123
|
+
"Authored subagents may never carry capability-acquisition tools.");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
/** Build the human-facing review text: the executable code and every mutating tool grant. */
|
|
129
|
+
function buildReview(id, params) {
|
|
130
|
+
const lines = [`Plugin "${id}" wants to install executable capabilities:`];
|
|
131
|
+
for (const h of params.hooks ?? []) {
|
|
132
|
+
lines.push(` hook [${h.event}${h.matcher ? ` matcher=${h.matcher}` : ""}]: ${h.command}`);
|
|
133
|
+
}
|
|
134
|
+
for (const s of params.mcpServers ?? []) {
|
|
135
|
+
lines.push(` mcp server "${s.name}": ${s.command}${s.args?.length ? ` ${s.args.join(" ")}` : ""}`);
|
|
136
|
+
}
|
|
137
|
+
for (const sa of mutatingSubagents(params)) {
|
|
138
|
+
lines.push(` subagent "${sa.name}" tools: ${sa.tools ?? "(none)"} (${classifyAllowlist(sa.tools).reason})`);
|
|
139
|
+
}
|
|
140
|
+
return lines.join("\n");
|
|
141
|
+
}
|
|
142
|
+
function summarizeWrite(id, platforms, files, dest, verb) {
|
|
143
|
+
return (`${verb} plugin "${id}" (${platforms.join(", ")}) with ${files.length} file(s) at ${dest}:\n` +
|
|
64
144
|
files.map((f) => ` ${f}`).join("\n") +
|
|
65
145
|
`\nRemove it with UninstallPlugin.`);
|
|
66
146
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
147
|
+
function reject(id, message) {
|
|
148
|
+
return { content: [{ type: "text", text: message }], details: { id, authored: false } };
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Run the shared "executable capabilities → show → confirm" gate. Returns:
|
|
152
|
+
* - `{ ok: true }` when there is nothing executable, or the human confirmed;
|
|
153
|
+
* - a tool result (authored:false) when there is no UI to confirm on, or the
|
|
154
|
+
* human declined.
|
|
155
|
+
*/
|
|
156
|
+
async function passExecutableGate(id, params, ctx) {
|
|
157
|
+
if (!hasExecutable(params))
|
|
158
|
+
return { ok: true, gated: false };
|
|
159
|
+
const review = buildReview(id, params);
|
|
160
|
+
ctx.ui.notify(review, "warning");
|
|
161
|
+
if (!ctx.hasUI) {
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
result: reject(id, "Authoring executable capabilities requires human confirmation, which is unavailable in this mode. " +
|
|
165
|
+
`Not activated.\n${review}`),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const confirmed = await ctx.ui.confirm(`Author executable plugin "${id}"?`, `${review}\n\nThis installs and can run the code above. Activate it?`);
|
|
169
|
+
if (!confirmed) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
result: {
|
|
173
|
+
content: [{ type: "text", text: `Declined — plugin "${id}" was not authored.` }],
|
|
174
|
+
details: { id, authored: false, confirmed: false },
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return { ok: true, gated: true };
|
|
179
|
+
}
|
|
180
|
+
// ── ProposePlugin (create) ────────────────────────────────────────────────────
|
|
181
|
+
const proposeParams = Type.Object({ id: Type.String({ description: "Plugin id (directory + manifest name)." }), ...capabilityProps }, { additionalProperties: false });
|
|
79
182
|
export function createProposePluginToolDefinition() {
|
|
80
183
|
return defineTool({
|
|
81
184
|
name: PROPOSE_PLUGIN_TOOL_NAME,
|
|
82
185
|
label: PROPOSE_PLUGIN_TOOL_NAME,
|
|
83
|
-
description: "Author a
|
|
84
|
-
|
|
186
|
+
description: "Author a NEW plugin to fill a capability gap when no marketplace plugin fits. Accepts any capability mix — " +
|
|
187
|
+
"skills, slash commands, subagents, hooks, MCP servers. Passive content (skills, commands, read-only " +
|
|
188
|
+
"subagents) is authored autonomously; executable content (hooks, MCP servers, mutating subagents) is shown " +
|
|
189
|
+
"and requires human confirmation before it activates. To change an existing plugin, use UpdatePlugin.",
|
|
190
|
+
promptSnippet: "Author a new plugin to fill a capability gap (passive is autonomous; executable asks to confirm).",
|
|
85
191
|
promptGuidelines: [
|
|
86
|
-
"Sense reusability proactively: when you complete a multi-step recipe you
|
|
87
|
-
"
|
|
88
|
-
"A subagent that needs Bash/Write/Edit/MCP or tools:* is mutating — author it with ProposeExecutablePlugin (human confirmation), not here.",
|
|
192
|
+
"Sense reusability proactively: when you complete a multi-step recipe you'd plausibly repeat (or repeat the same pattern twice in one session) and SearchPlugins finds nothing that covers it, author it with ProposePlugin. Passive skills/commands activate immediately and are reversible with UninstallPlugin — announce what you created and why.",
|
|
193
|
+
"One tool for the whole plugin: put skills + a hook in a single call. The risk gate is computed from content — you don't pre-classify. Read-only subagents and skills/commands go straight through; hooks, MCP servers, or a subagent needing Bash/Write/Edit/MCP or tools:* pause for human confirmation.",
|
|
89
194
|
"Never grant a subagent any plugin-system tool (InstallPlugin, ProposePlugin, ...); that is always rejected.",
|
|
195
|
+
"Publishing a proven-useful plugin to a marketplace stays a human action — do not do it autonomously.",
|
|
90
196
|
],
|
|
91
197
|
parameters: proposeParams,
|
|
92
198
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return reject(`Subagent "${sa.name}" requests plugin-system tools (${cls.pluginTools.join(", ")}). ` +
|
|
99
|
-
"Authored subagents may never carry capability-acquisition tools.", params.id);
|
|
100
|
-
}
|
|
101
|
-
if (cls.risk === "mutating") {
|
|
102
|
-
return reject(`Subagent "${sa.name}" has a mutating allowlist (${cls.reason}). ` +
|
|
103
|
-
"Use ProposeExecutablePlugin so the human can review and confirm the tool grant.", params.id);
|
|
104
|
-
}
|
|
199
|
+
const violation = guardrailViolation(params);
|
|
200
|
+
if (violation)
|
|
201
|
+
return reject(params.id, violation);
|
|
202
|
+
if (capabilityCount(params) === 0) {
|
|
203
|
+
return reject(params.id, "Nothing to author. Provide skills, commands, subagents, hooks, or mcpServers.");
|
|
105
204
|
}
|
|
106
205
|
if (pluginExists(ctx.cwd, params.id)) {
|
|
107
|
-
return reject(`A plugin named "${params.id}" already exists.
|
|
206
|
+
return reject(params.id, `A plugin named "${params.id}" already exists. Use UpdatePlugin to change it, or pick another id.`);
|
|
108
207
|
}
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
agents: params.subagents,
|
|
117
|
-
};
|
|
118
|
-
const result = writePluginDraft(ctx.cwd, draft, platforms);
|
|
119
|
-
// Passive capabilities activate live — the authored skill/command/subagent
|
|
120
|
-
// is usable on the very next model request, in this same turn.
|
|
208
|
+
const gate = await passExecutableGate(params.id, params, ctx);
|
|
209
|
+
if (!gate.ok)
|
|
210
|
+
return gate.result;
|
|
211
|
+
const platforms = resolvePlatforms(params.platforms);
|
|
212
|
+
const result = writePluginDraft(ctx.cwd, draftFrom(params.id, params, platforms), platforms);
|
|
213
|
+
// Passive capabilities activate live — usable on the very next model request,
|
|
214
|
+
// this same turn; hooks/MCP servers activate via the reload once the turn ends.
|
|
121
215
|
const activation = ctx.activatePlugin(result.dest);
|
|
122
|
-
const text = `${summarizeWrite(params.id, platforms, result.files, result.dest)}\n${activation.message}`;
|
|
216
|
+
const text = `${summarizeWrite(params.id, platforms, result.files, result.dest, "Authored")}\n${activation.message}`;
|
|
123
217
|
ctx.ui.notify(`Authored plugin "${params.id}" (${platforms.join(", ")}).`, "info");
|
|
124
|
-
return {
|
|
218
|
+
return {
|
|
219
|
+
content: [{ type: "text", text }],
|
|
220
|
+
details: { id: params.id, authored: true, confirmed: gate.gated },
|
|
221
|
+
};
|
|
125
222
|
},
|
|
126
223
|
});
|
|
127
224
|
}
|
|
128
|
-
// ──
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
description: Type.Optional(Type.String({ description: "Plugin description." })),
|
|
132
|
-
version: Type.Optional(Type.String({ description: "Plugin version, e.g. '0.1.0'." })),
|
|
133
|
-
platforms: Type.Optional(Type.Array(platformSchema, {
|
|
134
|
-
description: "Formats to scaffold into. Default: the session's --support-platform targets, else claude + github.",
|
|
135
|
-
})),
|
|
136
|
-
hooks: Type.Optional(Type.Array(hookSchema)),
|
|
137
|
-
mcpServers: Type.Optional(Type.Array(mcpServerSchema)),
|
|
138
|
-
subagents: Type.Optional(Type.Array(subagentSchema, { description: "Subagents with mutating/exec/network or tools:* allowlists." })),
|
|
139
|
-
}, { additionalProperties: false });
|
|
140
|
-
/** Build the human-facing review text: the executable code and every tool grant. */
|
|
141
|
-
function buildReview(params) {
|
|
142
|
-
const lines = [`Plugin "${params.id}" wants to install executable capabilities:`];
|
|
143
|
-
for (const h of params.hooks ?? []) {
|
|
144
|
-
lines.push(` hook [${h.event}${h.matcher ? ` matcher=${h.matcher}` : ""}]: ${h.command}`);
|
|
145
|
-
}
|
|
146
|
-
for (const s of params.mcpServers ?? []) {
|
|
147
|
-
lines.push(` mcp server "${s.name}": ${s.command}${s.args?.length ? ` ${s.args.join(" ")}` : ""}`);
|
|
148
|
-
}
|
|
149
|
-
for (const sa of params.subagents ?? []) {
|
|
150
|
-
lines.push(` subagent "${sa.name}" tools: ${sa.tools ?? "(none)"}`);
|
|
151
|
-
}
|
|
152
|
-
return lines.join("\n");
|
|
153
|
-
}
|
|
154
|
-
export function createProposeExecutablePluginToolDefinition() {
|
|
225
|
+
// ── UpdatePlugin (merge into an existing local plugin) ─────────────────────────
|
|
226
|
+
const updateParams = Type.Object({ id: Type.String({ description: "Id of the existing local plugin to update." }), ...capabilityProps }, { additionalProperties: false });
|
|
227
|
+
export function createUpdatePluginToolDefinition() {
|
|
155
228
|
return defineTool({
|
|
156
|
-
name:
|
|
157
|
-
label:
|
|
158
|
-
description: "
|
|
159
|
-
|
|
229
|
+
name: UPDATE_PLUGIN_TOOL_NAME,
|
|
230
|
+
label: UPDATE_PLUGIN_TOOL_NAME,
|
|
231
|
+
description: "Merge inline-authored capabilities into an EXISTING locally AUTHORED plugin (marketplace-installed plugins " +
|
|
232
|
+
"are refused). Skills/commands/subagents are added or replaced by name; hooks and MCP servers are unioned " +
|
|
233
|
+
"with what's already there; metadata is overwritten only where you supply it. Additive only — remove a " +
|
|
234
|
+
"capability with RemovePluginCapability. Nothing is fetched from a remote. Passive " +
|
|
235
|
+
"additions apply autonomously; executable additions (hooks, MCP servers, mutating subagents) require human " +
|
|
236
|
+
"confirmation. Use ProposePlugin to create.",
|
|
237
|
+
promptSnippet: "Add/replace capabilities in a plugin you authored (additive; executable additions ask to confirm).",
|
|
160
238
|
promptGuidelines: [
|
|
161
|
-
"
|
|
239
|
+
"Use UpdatePlugin to grow a plugin you already authored — e.g. add a skill to it, or attach a hook. Supply only the delta; existing capabilities are preserved (a matching name replaces just that one). It cannot remove a capability — use RemovePluginCapability for that.",
|
|
240
|
+
"Hooks cannot be modified in place: they have no name, so supplying a changed command ADDS a second hook alongside the old one (both fire). To change a hook, RemovePluginCapability the old one first, then add the new one here.",
|
|
241
|
+
"Only executable *additions* trigger confirmation — adding a passive skill to an already-executable plugin does not re-prompt.",
|
|
162
242
|
"Never grant a subagent any plugin-system tool (InstallPlugin, ProposePlugin, ...); that is always rejected.",
|
|
163
|
-
"Publishing a proven-useful plugin to a marketplace stays a human action — do not do it autonomously.",
|
|
164
243
|
],
|
|
165
|
-
parameters:
|
|
244
|
+
parameters: updateParams,
|
|
166
245
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"Authored subagents may never carry capability-acquisition tools.", params.id);
|
|
174
|
-
}
|
|
246
|
+
const violation = guardrailViolation(params);
|
|
247
|
+
if (violation)
|
|
248
|
+
return reject(params.id, violation);
|
|
249
|
+
const existing = getPlugin(ctx.cwd, params.id);
|
|
250
|
+
if (!existing) {
|
|
251
|
+
return reject(params.id, `No plugin named "${params.id}" is installed. Use ProposePlugin to create it first.`);
|
|
175
252
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
253
|
+
// Authored-only: marketplace installs land in the same directory but don't
|
|
254
|
+
// round-trip losslessly through our emitters (see mergePluginDraft).
|
|
255
|
+
if (!isAuthoredPlugin(ctx.cwd, params.id)) {
|
|
256
|
+
return reject(params.id, `Plugin "${params.id}" was not authored in this workspace (likely installed from a marketplace). ` +
|
|
257
|
+
"UpdatePlugin only modifies locally authored plugins — updating a marketplace plugin is a human " +
|
|
258
|
+
"action (uninstall it and install a newer version instead).");
|
|
181
259
|
}
|
|
182
|
-
if (
|
|
183
|
-
return
|
|
260
|
+
if (capabilityCount(params) === 0 && !params.version && !params.description && !params.platforms) {
|
|
261
|
+
return reject(params.id, "Nothing to update. Provide skills, commands, subagents, hooks, mcpServers, platforms, or metadata.");
|
|
184
262
|
}
|
|
185
|
-
//
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
263
|
+
// Gate on the DELTA only — existing executables aren't re-confirmed.
|
|
264
|
+
const gate = await passExecutableGate(params.id, params, ctx);
|
|
265
|
+
if (!gate.ok)
|
|
266
|
+
return gate.result;
|
|
267
|
+
const result = mergePluginDraft(ctx.cwd, params.id, draftFrom(params.id, params, existing.supportPlatform), params.platforms);
|
|
268
|
+
const platforms = result.plugin?.supportPlatform ?? existing.supportPlatform;
|
|
269
|
+
const activation = ctx.activatePlugin(result.dest);
|
|
270
|
+
const text = `${summarizeWrite(params.id, platforms, result.files, result.dest, "Updated")}\n${activation.message}`;
|
|
271
|
+
ctx.ui.notify(`Updated plugin "${params.id}" (${platforms.join(", ")}).`, "info");
|
|
272
|
+
return {
|
|
273
|
+
content: [{ type: "text", text }],
|
|
274
|
+
details: { id: params.id, authored: true, confirmed: gate.gated },
|
|
275
|
+
};
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
// ── RemovePluginCapability (subtract from an authored plugin) ─────────────────
|
|
280
|
+
const hookRemovalSchema = Type.Object({
|
|
281
|
+
event: Type.String({ description: "Event of the hook(s) to remove, e.g. PreToolUse." }),
|
|
282
|
+
matcher: Type.Optional(Type.String({ description: "Narrow to hooks with exactly this matcher." })),
|
|
283
|
+
command: Type.Optional(Type.String({ description: "Narrow to hooks with exactly this command." })),
|
|
284
|
+
}, { additionalProperties: false });
|
|
285
|
+
const removeParams = Type.Object({
|
|
286
|
+
id: Type.String({ description: "Id of the authored plugin to remove capabilities from." }),
|
|
287
|
+
skills: Type.Optional(Type.Array(Type.String(), { description: "Skill names to remove." })),
|
|
288
|
+
commands: Type.Optional(Type.Array(Type.String(), { description: "Command names to remove." })),
|
|
289
|
+
subagents: Type.Optional(Type.Array(Type.String(), { description: "Subagent names to remove." })),
|
|
290
|
+
mcpServers: Type.Optional(Type.Array(Type.String(), { description: "MCP server names to remove." })),
|
|
291
|
+
hooks: Type.Optional(Type.Array(hookRemovalSchema, {
|
|
292
|
+
description: "Hooks to remove, matched by event and narrowed by matcher/command when provided.",
|
|
293
|
+
})),
|
|
294
|
+
}, { additionalProperties: false });
|
|
295
|
+
export function createRemovePluginCapabilityToolDefinition() {
|
|
296
|
+
return defineTool({
|
|
297
|
+
name: REMOVE_PLUGIN_CAPABILITY_TOOL_NAME,
|
|
298
|
+
label: REMOVE_PLUGIN_CAPABILITY_TOOL_NAME,
|
|
299
|
+
description: "Remove named capabilities from a locally AUTHORED plugin — skills, commands, subagents, and MCP servers by " +
|
|
300
|
+
"name; hooks by event (narrowed by matcher/command). The subtractive half of UpdatePlugin. Removal is " +
|
|
301
|
+
"low-risk and autonomous (deleting capabilities cannot execute code). To remove the whole plugin, use " +
|
|
302
|
+
"UninstallPlugin; marketplace-installed plugins are refused here.",
|
|
303
|
+
promptSnippet: "Remove capabilities from a plugin you authored (low risk; autonomous).",
|
|
304
|
+
promptGuidelines: [
|
|
305
|
+
"Removal runs autonomously (the low-risk direction) — announce what you removed and why.",
|
|
306
|
+
"To CHANGE a hook (hooks have no name to replace by): RemovePluginCapability the old hook, then UpdatePlugin the new one (which asks for confirmation).",
|
|
307
|
+
],
|
|
308
|
+
parameters: removeParams,
|
|
309
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
310
|
+
const noDetails = (msg) => ({
|
|
311
|
+
content: [{ type: "text", text: msg }],
|
|
312
|
+
details: { id: params.id, removed: [], missing: [] },
|
|
313
|
+
});
|
|
314
|
+
const existing = getPlugin(ctx.cwd, params.id);
|
|
315
|
+
if (!existing) {
|
|
316
|
+
return noDetails(`No plugin named "${params.id}" is installed.`);
|
|
191
317
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
content: [{ type: "text", text: `Declined — plugin "${params.id}" was not authored.` }],
|
|
196
|
-
details: { id: params.id, authored: false, confirmed: false },
|
|
197
|
-
};
|
|
318
|
+
if (!isAuthoredPlugin(ctx.cwd, params.id)) {
|
|
319
|
+
return noDetails(`Plugin "${params.id}" was not authored in this workspace (likely installed from a marketplace). ` +
|
|
320
|
+
"RemovePluginCapability only edits locally authored plugins — use UninstallPlugin to remove it entirely.");
|
|
198
321
|
}
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
322
|
+
const requested = (params.skills?.length ?? 0) +
|
|
323
|
+
(params.commands?.length ?? 0) +
|
|
324
|
+
(params.subagents?.length ?? 0) +
|
|
325
|
+
(params.mcpServers?.length ?? 0) +
|
|
326
|
+
(params.hooks?.length ?? 0);
|
|
327
|
+
if (requested === 0) {
|
|
328
|
+
return noDetails("Nothing to remove. Name skills, commands, subagents, mcpServers, or hooks.");
|
|
329
|
+
}
|
|
330
|
+
const result = removeFromPlugin(ctx.cwd, params.id, {
|
|
331
|
+
skills: params.skills,
|
|
332
|
+
commands: params.commands,
|
|
333
|
+
subagents: params.subagents,
|
|
205
334
|
mcpServers: params.mcpServers,
|
|
206
|
-
|
|
207
|
-
};
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
335
|
+
hooks: params.hooks,
|
|
336
|
+
});
|
|
337
|
+
const lines = [];
|
|
338
|
+
if (result.removed.length > 0) {
|
|
339
|
+
lines.push(`Removed from plugin "${params.id}":`, ...result.removed.map((r) => ` ${r}`));
|
|
340
|
+
}
|
|
341
|
+
if (result.missing.length > 0) {
|
|
342
|
+
lines.push(`Not found (nothing removed):`, ...result.missing.map((m) => ` ${m}`));
|
|
343
|
+
}
|
|
344
|
+
const text = lines.join("\n");
|
|
345
|
+
// Removal takes effect through the reload path, same as UninstallPlugin.
|
|
346
|
+
if (result.removed.length > 0)
|
|
347
|
+
ctx.requestReloadWhenIdle();
|
|
348
|
+
ctx.ui.notify(text, result.removed.length > 0 ? "info" : "warning");
|
|
214
349
|
return {
|
|
215
350
|
content: [{ type: "text", text }],
|
|
216
|
-
details: { id: params.id,
|
|
351
|
+
details: { id: params.id, removed: result.removed, missing: result.missing },
|
|
217
352
|
};
|
|
218
353
|
},
|
|
219
354
|
});
|
|
220
355
|
}
|
|
221
|
-
|
|
222
|
-
return { content: [{ type: "text", text: message }], details: { id, authored: false } };
|
|
223
|
-
}
|
|
224
|
-
function rejectExec(message, id) {
|
|
225
|
-
return { content: [{ type: "text", text: message }], details: { id, authored: false, confirmed: false } };
|
|
226
|
-
}
|
|
227
|
-
/** Both authoring tool definitions, for registration on the top-level agent. */
|
|
356
|
+
/** All three authoring tool definitions, for registration on the top-level agent. */
|
|
228
357
|
export function createProposePluginToolDefinitions() {
|
|
229
|
-
return [
|
|
358
|
+
return [
|
|
359
|
+
createProposePluginToolDefinition(),
|
|
360
|
+
createUpdatePluginToolDefinition(),
|
|
361
|
+
createRemovePluginCapabilityToolDefinition(),
|
|
362
|
+
];
|
|
230
363
|
}
|
|
231
364
|
//# sourceMappingURL=propose-plugin.js.map
|