wave-agent-sdk 1.2.0 → 1.3.0
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/dist/agent.d.ts +58 -4
- package/dist/agent.js +91 -19
- package/dist/builtin/index.js +2 -0
- package/dist/builtin/skills/settings.js +1 -12
- package/dist/builtin/skills/wave-daemon.d.ts +1 -0
- package/dist/builtin/skills/wave-daemon.js +194 -0
- package/dist/constants/images.d.ts +26 -0
- package/dist/constants/images.js +26 -0
- package/dist/constants/index.d.ts +16 -0
- package/dist/constants/index.js +16 -0
- package/dist/constants/memory.d.ts +26 -0
- package/dist/constants/memory.js +34 -0
- package/dist/constants/messages.d.ts +11 -0
- package/dist/constants/messages.js +11 -0
- package/dist/constants/plugins.d.ts +8 -0
- package/dist/constants/plugins.js +8 -0
- package/dist/constants/tools.d.ts +1 -0
- package/dist/constants/tools.js +1 -0
- package/dist/core/plugin.d.ts +53 -13
- package/dist/core/plugin.js +134 -26
- package/dist/core/session.d.ts +1 -1
- package/dist/core/session.js +1 -1
- package/dist/exec/catalog.d.ts +140 -0
- package/dist/exec/catalog.js +470 -0
- package/dist/exec/catalogAnnouncement.d.ts +89 -0
- package/dist/exec/catalogAnnouncement.js +293 -0
- package/dist/exec/constants.d.ts +51 -0
- package/dist/exec/constants.js +51 -0
- package/dist/exec/execRuntime.d.ts +55 -0
- package/dist/exec/execRuntime.js +217 -0
- package/dist/exec/workerSource.d.ts +28 -0
- package/dist/exec/workerSource.js +299 -0
- package/dist/host/index.d.ts +23 -0
- package/dist/host/index.js +23 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/managers/MemoryRuleManager.d.ts +6 -0
- package/dist/managers/MemoryRuleManager.js +12 -0
- package/dist/managers/aiManager.d.ts +35 -1
- package/dist/managers/aiManager.js +190 -21
- package/dist/managers/backgroundTaskManager.js +14 -0
- package/dist/managers/hookManager.d.ts +13 -0
- package/dist/managers/hookManager.js +31 -4
- package/dist/managers/liveConfigManager.d.ts +33 -0
- package/dist/managers/liveConfigManager.js +103 -8
- package/dist/managers/lspManager.d.ts +9 -0
- package/dist/managers/lspManager.js +47 -18
- package/dist/managers/mcpManager.d.ts +45 -10
- package/dist/managers/mcpManager.js +103 -1
- package/dist/managers/messageManager.d.ts +48 -5
- package/dist/managers/messageManager.js +107 -21
- package/dist/managers/permissionManager.d.ts +40 -0
- package/dist/managers/permissionManager.js +63 -8
- package/dist/managers/pluginManager.d.ts +46 -2
- package/dist/managers/pluginManager.js +117 -11
- package/dist/managers/pluginScopeManager.d.ts +15 -2
- package/dist/managers/pluginScopeManager.js +20 -1
- package/dist/managers/skillManager.d.ts +19 -0
- package/dist/managers/skillManager.js +44 -0
- package/dist/managers/slashCommandManager.d.ts +10 -0
- package/dist/managers/slashCommandManager.js +35 -3
- package/dist/managers/subagentManager.d.ts +8 -0
- package/dist/managers/subagentManager.js +20 -0
- package/dist/managers/toolManager.d.ts +29 -3
- package/dist/managers/toolManager.js +87 -13
- package/dist/prompts/autoMemory.d.ts +9 -0
- package/dist/prompts/autoMemory.js +30 -31
- package/dist/prompts/autoMemoryExtraction.d.ts +4 -0
- package/dist/prompts/autoMemoryExtraction.js +8 -111
- package/dist/prompts/memoryTypes.d.ts +63 -0
- package/dist/prompts/memoryTypes.js +191 -0
- package/dist/services/GitService.d.ts +7 -0
- package/dist/services/GitService.js +23 -0
- package/dist/services/MarketplaceService.d.ts +101 -17
- package/dist/services/MarketplaceService.js +318 -119
- package/dist/services/artifactContent.d.ts +84 -0
- package/dist/services/artifactContent.js +204 -0
- package/dist/services/artifactSession.d.ts +6 -0
- package/dist/services/artifactSession.js +17 -0
- package/dist/services/autoMemoryService.js +5 -13
- package/dist/services/configurationService.d.ts +60 -9
- package/dist/services/configurationService.js +129 -54
- package/dist/services/contentSummarizer.d.ts +15 -0
- package/dist/services/contentSummarizer.js +45 -0
- package/dist/services/execAvailability.d.ts +9 -0
- package/dist/services/execAvailability.js +32 -0
- package/dist/services/fileWatcher.js +61 -6
- package/dist/services/initializationService.js +19 -15
- package/dist/services/interactionService.d.ts +9 -1
- package/dist/services/interactionService.js +28 -8
- package/dist/services/jsonlHandler.d.ts +84 -0
- package/dist/services/jsonlHandler.js +209 -14
- package/dist/services/memory.d.ts +3 -1
- package/dist/services/memory.js +13 -9
- package/dist/services/officialMarketplaceMirror.js +3 -2
- package/dist/services/pluginLoader.d.ts +12 -4
- package/dist/services/pluginLoader.js +38 -7
- package/dist/services/remoteSettingsService.js +16 -2
- package/dist/services/session.d.ts +74 -0
- package/dist/services/session.js +144 -3
- package/dist/services/sessionEntries.d.ts +2 -0
- package/dist/services/sessionEntries.js +20 -0
- package/dist/stdio/index.d.ts +3 -1
- package/dist/stdio/index.js +3 -1
- package/dist/stdio/notificationRouter.js +1 -0
- package/dist/stdio/stdioAgent.d.ts +14 -7
- package/dist/stdio/stdioAgent.js +19 -0
- package/dist/tools/artifactTool.js +406 -273
- package/dist/tools/bashTool.js +8 -6
- package/dist/tools/editTool.js +6 -3
- package/dist/tools/execTool.d.ts +2 -0
- package/dist/tools/execTool.js +165 -0
- package/dist/tools/grepTool.js +7 -1
- package/dist/tools/readTool.js +30 -2
- package/dist/tools/types.d.ts +34 -8
- package/dist/tools/webFetchTool.js +15 -166
- package/dist/tools/workflowTool.js +40 -8
- package/dist/tools/writeTool.js +6 -3
- package/dist/types/agent.d.ts +20 -5
- package/dist/types/configuration.d.ts +39 -1
- package/dist/types/marketplace.d.ts +40 -2
- package/dist/types/mcp.d.ts +39 -0
- package/dist/types/permissions.d.ts +22 -0
- package/dist/types/permissions.js +17 -0
- package/dist/types/plugins.d.ts +26 -2
- package/dist/types/skills.d.ts +15 -0
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +10 -0
- package/dist/utils/containerSetup.js +43 -0
- package/dist/utils/convertMessagesForAPI.d.ts +7 -1
- package/dist/utils/convertMessagesForAPI.js +64 -14
- package/dist/utils/fileChangeReminder.d.ts +20 -0
- package/dist/utils/fileChangeReminder.js +153 -0
- package/dist/utils/fileSearch.js +4 -3
- package/dist/utils/fileUtils.d.ts +33 -0
- package/dist/utils/fileUtils.js +81 -0
- package/dist/utils/frontmatterYaml.d.ts +33 -0
- package/dist/utils/frontmatterYaml.js +192 -0
- package/dist/utils/imageBudget.d.ts +85 -0
- package/dist/utils/imageBudget.js +109 -0
- package/dist/utils/imageDimensions.d.ts +83 -0
- package/dist/utils/imageDimensions.js +232 -0
- package/dist/utils/imageProcessor.d.ts +66 -0
- package/dist/utils/imageProcessor.js +84 -0
- package/dist/utils/imageRewrite.d.ts +29 -0
- package/dist/utils/imageRewrite.js +251 -0
- package/dist/utils/markdownParser.d.ts +5 -1
- package/dist/utils/markdownParser.js +9 -51
- package/dist/utils/mcpInstructions.d.ts +61 -0
- package/dist/utils/mcpInstructions.js +126 -0
- package/dist/utils/mcpUtils.d.ts +7 -0
- package/dist/utils/mcpUtils.js +11 -2
- package/dist/utils/memoryAge.d.ts +32 -0
- package/dist/utils/memoryAge.js +47 -0
- package/dist/utils/memoryEntrypoint.d.ts +20 -0
- package/dist/utils/memoryEntrypoint.js +49 -0
- package/dist/utils/memoryIndex.d.ts +30 -0
- package/dist/utils/memoryIndex.js +76 -0
- package/dist/utils/messageOperations.d.ts +6 -2
- package/dist/utils/messageOperations.js +40 -29
- package/dist/utils/nestedMemory.d.ts +22 -0
- package/dist/utils/nestedMemory.js +61 -0
- package/dist/utils/npmTarball.d.ts +19 -0
- package/dist/utils/npmTarball.js +92 -0
- package/dist/utils/pluginSource.d.ts +37 -0
- package/dist/utils/pluginSource.js +73 -0
- package/dist/utils/ripgrep.d.ts +18 -4
- package/dist/utils/ripgrep.js +56 -4
- package/dist/utils/runtimeDeps.d.ts +35 -0
- package/dist/utils/runtimeDeps.js +426 -0
- package/dist/utils/skillParser.js +22 -52
- package/dist/utils/subagentParser.js +39 -43
- package/dist/utils/userSettings.d.ts +90 -0
- package/dist/utils/userSettings.js +291 -0
- package/package.json +10 -7
|
@@ -4,10 +4,27 @@ import * as path from "path";
|
|
|
4
4
|
import { existsSync, readdirSync } from "fs";
|
|
5
5
|
import { getBuiltinPluginsDir } from "../utils/configPaths.js";
|
|
6
6
|
import { MarketplaceService } from "../services/MarketplaceService.js";
|
|
7
|
+
import { DEFAULT_PLUGIN_VERSION } from "../constants/plugins.js";
|
|
7
8
|
export class PluginManager {
|
|
9
|
+
serialize(task) {
|
|
10
|
+
const result = this.loadChain.then(task, task);
|
|
11
|
+
// Keep the chain alive after a failure so later reloads still run.
|
|
12
|
+
this.loadChain = result.then(() => undefined, () => undefined);
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
8
15
|
constructor(container, options) {
|
|
9
16
|
this.container = container;
|
|
10
17
|
this.plugins = new Map();
|
|
18
|
+
/** Explicit configs from the last load, replayed by reloadAllPlugins(). */
|
|
19
|
+
this.lastLoadConfigs = [];
|
|
20
|
+
/** Failures collected by the current loadPlugins() run. */
|
|
21
|
+
this.loadFailures = [];
|
|
22
|
+
/**
|
|
23
|
+
* Serializes plugin (re)loads. A managed-settings sync (or an early
|
|
24
|
+
* `/reload-plugins`) can ask for a reload while the startup load is still
|
|
25
|
+
* running; interleaving them would leave half-unloaded capability registries.
|
|
26
|
+
*/
|
|
27
|
+
this.loadChain = Promise.resolve();
|
|
11
28
|
this.workdir = options.workdir;
|
|
12
29
|
this.enabledPlugins = options.enabledPlugins || {};
|
|
13
30
|
}
|
|
@@ -51,12 +68,6 @@ export class PluginManager {
|
|
|
51
68
|
this.enabledPlugins = this.configurationService.getMergedEnabledPlugins(this.workdir);
|
|
52
69
|
}
|
|
53
70
|
const marketplaceService = new MarketplaceService(this.workdir, this.configurationService);
|
|
54
|
-
// Trigger auto-update for marketplaces in the background
|
|
55
|
-
if (!process.env.VITEST) {
|
|
56
|
-
marketplaceService.autoUpdateAll().catch((error) => {
|
|
57
|
-
logger?.error("Background marketplace auto-update failed:", error);
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
71
|
let installedRegistry = await marketplaceService.getInstalledPlugins();
|
|
61
72
|
const knownMarketplaces = await marketplaceService.listMarketplaces();
|
|
62
73
|
// Identify missing enabled plugins and auto-install them if marketplace is known
|
|
@@ -74,7 +85,7 @@ export class PluginManager {
|
|
|
74
85
|
const isMarketplaceKnown = knownMarketplaces.some((m) => m.name === marketplaceName);
|
|
75
86
|
if (isMarketplaceKnown) {
|
|
76
87
|
// Pre-check: verify the plugin still exists in the marketplace manifest
|
|
77
|
-
// before acquiring the lock in installPlugin (which can block ~8s during
|
|
88
|
+
// before acquiring the lock in installPlugin (which can block ~8s during a marketplace refresh)
|
|
78
89
|
const marketplace = knownMarketplaces.find((m) => m.name === marketplaceName);
|
|
79
90
|
if (!marketplace)
|
|
80
91
|
continue;
|
|
@@ -119,12 +130,18 @@ export class PluginManager {
|
|
|
119
130
|
}
|
|
120
131
|
// Refresh registry after potential auto-installs
|
|
121
132
|
installedRegistry = await marketplaceService.getInstalledPlugins();
|
|
133
|
+
// 同一插件在本机可能有多条安装记录(各作用域/各项目各一条,spec plugin
|
|
134
|
+
// A-015);按插件去重,否则同一个 cachePath 会被加载多次。
|
|
135
|
+
const loaded = new Set();
|
|
122
136
|
for (const p of installedRegistry.plugins) {
|
|
123
137
|
const pluginId = `${p.name}@${p.marketplace}`;
|
|
124
138
|
if (this.enabledPlugins[pluginId] !== true) {
|
|
125
139
|
logger?.debug(`Plugin ${pluginId} is not enabled via configuration`);
|
|
126
140
|
continue;
|
|
127
141
|
}
|
|
142
|
+
if (loaded.has(pluginId))
|
|
143
|
+
continue;
|
|
144
|
+
loaded.add(pluginId);
|
|
128
145
|
await this.loadSinglePlugin(p.cachePath);
|
|
129
146
|
}
|
|
130
147
|
}
|
|
@@ -145,12 +162,15 @@ export class PluginManager {
|
|
|
145
162
|
}
|
|
146
163
|
const plugin = {
|
|
147
164
|
...manifest,
|
|
165
|
+
// A manifest without `version` is legal (spec plugin A-022); the default
|
|
166
|
+
// matches what the install path files under `installed_plugins.json`.
|
|
167
|
+
version: manifest.version || DEFAULT_PLUGIN_VERSION,
|
|
148
168
|
path: absolutePath,
|
|
149
169
|
commands: PluginLoader.loadCommands(absolutePath),
|
|
150
170
|
skills: await PluginLoader.loadSkills(absolutePath),
|
|
151
171
|
agents: await PluginLoader.loadAgents(absolutePath),
|
|
152
172
|
lspConfig: await PluginLoader.loadLspConfig(absolutePath),
|
|
153
|
-
mcpConfig: await PluginLoader.loadMcpConfig(absolutePath),
|
|
173
|
+
mcpConfig: await PluginLoader.loadMcpConfig(absolutePath, manifest.mcpServers),
|
|
154
174
|
hooksConfig: await PluginLoader.loadHooksConfig(absolutePath),
|
|
155
175
|
};
|
|
156
176
|
// Register components with managers
|
|
@@ -168,8 +188,15 @@ export class PluginManager {
|
|
|
168
188
|
}
|
|
169
189
|
if (this.mcpManager && plugin.mcpConfig) {
|
|
170
190
|
for (const [name, config] of Object.entries(plugin.mcpConfig.mcpServers)) {
|
|
171
|
-
|
|
172
|
-
|
|
191
|
+
// One unusable server declaration must not take the rest of the plugin
|
|
192
|
+
// down with it (spec plugin A-023).
|
|
193
|
+
try {
|
|
194
|
+
const configWithPluginRoot = { ...config, pluginRoot: plugin.path };
|
|
195
|
+
this.mcpManager.addServer(name, configWithPluginRoot);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
logger?.warn(`Failed to register MCP server '${name}' from plugin ${plugin.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
199
|
+
}
|
|
173
200
|
}
|
|
174
201
|
}
|
|
175
202
|
if (this.hookManager && plugin.hooksConfig) {
|
|
@@ -179,17 +206,31 @@ export class PluginManager {
|
|
|
179
206
|
this.subagentManager.registerPluginAgents(plugin.name, plugin.agents);
|
|
180
207
|
}
|
|
181
208
|
this.plugins.set(manifest.name, plugin);
|
|
182
|
-
logger?.debug(`Loaded plugin: ${manifest.name} v${
|
|
209
|
+
logger?.debug(`Loaded plugin: ${manifest.name} v${plugin.version}`);
|
|
183
210
|
}
|
|
184
211
|
catch (error) {
|
|
212
|
+
this.loadFailures.push({
|
|
213
|
+
path: absolutePath,
|
|
214
|
+
error: error instanceof Error ? error.message : String(error),
|
|
215
|
+
});
|
|
185
216
|
logger?.error(`Failed to load plugin from ${absolutePath}`, error);
|
|
186
217
|
}
|
|
187
218
|
}
|
|
188
219
|
/**
|
|
189
220
|
* Load plugins from configuration
|
|
190
221
|
* @param configs Array of plugin configurations
|
|
222
|
+
* @returns the plugins that failed to load
|
|
191
223
|
*/
|
|
192
224
|
async loadPlugins(configs) {
|
|
225
|
+
return this.serialize(() => this.doLoadPlugins(configs));
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The actual load. Reached through [loadPlugins] (which serializes concurrent
|
|
229
|
+
* requests) and directly from [reloadAllPlugins], which already holds the slot.
|
|
230
|
+
*/
|
|
231
|
+
async doLoadPlugins(configs) {
|
|
232
|
+
this.lastLoadConfigs = configs;
|
|
233
|
+
this.loadFailures = [];
|
|
193
234
|
// Load plugins from configuration (e.g. --plugin-dir) first to give them higher priority
|
|
194
235
|
for (const config of configs) {
|
|
195
236
|
if (config.type !== "local") {
|
|
@@ -205,6 +246,71 @@ export class PluginManager {
|
|
|
205
246
|
await this.loadInstalledPlugins();
|
|
206
247
|
// Load built-in plugins bundled with the SDK (lowest priority)
|
|
207
248
|
await this.loadBuiltinPlugins();
|
|
249
|
+
return [...this.loadFailures];
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Unload a single plugin: drop everything it contributed from the six
|
|
253
|
+
* capability registries, then forget it. Other plugins and the running
|
|
254
|
+
* session are untouched.
|
|
255
|
+
* @returns true if the plugin was loaded
|
|
256
|
+
*/
|
|
257
|
+
async unloadPlugin(name) {
|
|
258
|
+
const plugin = this.plugins.get(name);
|
|
259
|
+
if (!plugin) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
// Managers key their entries off the plugin name (commands/skills/agents)
|
|
263
|
+
// or the plugin root path (hooks/LSP/MCP), so each needs its own removal.
|
|
264
|
+
this.slashCommandManager?.unregisterPluginCommands(plugin.name);
|
|
265
|
+
this.skillManager?.unregisterPluginSkills(plugin.name);
|
|
266
|
+
this.subagentManager?.unregisterPluginAgents(plugin.name);
|
|
267
|
+
this.hookManager?.unregisterPluginHooks(plugin.path);
|
|
268
|
+
await this.lspManager?.unregisterServersForPlugin(plugin.path);
|
|
269
|
+
this.mcpManager?.removeServersForPlugin(plugin.path);
|
|
270
|
+
// Revoke the builtin helper-script grants this plugin added, so disabling
|
|
271
|
+
// it does not leave a standing permission behind.
|
|
272
|
+
for (const rule of PluginManager.BUILTIN_PLUGIN_ALLOW_RULES[plugin.name] ||
|
|
273
|
+
[]) {
|
|
274
|
+
this.permissionManager?.removeInstanceAllowedRule(rule);
|
|
275
|
+
}
|
|
276
|
+
this.plugins.delete(name);
|
|
277
|
+
logger?.debug(`Unloaded plugin: ${name}`);
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Re-read every plugin in place — the `/reload-plugins` command.
|
|
282
|
+
*
|
|
283
|
+
* Unloads everything currently loaded, re-reads `enabledPlugins` from the
|
|
284
|
+
* configuration chain (so a settings edit is honored), then replays the
|
|
285
|
+
* original load order (explicit configs → installed → builtin). The session,
|
|
286
|
+
* transcript and sessionId are untouched; only the capability registries are
|
|
287
|
+
* swapped, which is what makes this an in-place reload instead of a rebuild.
|
|
288
|
+
*
|
|
289
|
+
* Unload must happen before any caller-side mutation of the plugin cache dir:
|
|
290
|
+
* hooks, MCP, LSP, skills and agents all bake absolute plugin paths at
|
|
291
|
+
* registration time, so an entry left pointing at a deleted directory is
|
|
292
|
+
* worse than a merely stale one.
|
|
293
|
+
*/
|
|
294
|
+
async reloadAllPlugins() {
|
|
295
|
+
return this.serialize(async () => {
|
|
296
|
+
for (const name of Array.from(this.plugins.keys())) {
|
|
297
|
+
await this.unloadPlugin(name);
|
|
298
|
+
}
|
|
299
|
+
this.enabledPlugins = this.refreshEnabledPlugins();
|
|
300
|
+
const failures = await this.doLoadPlugins(this.lastLoadConfigs);
|
|
301
|
+
return {
|
|
302
|
+
plugins: Array.from(this.plugins.keys()),
|
|
303
|
+
failures,
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Re-read enabled plugins from the merged configuration chain, falling back
|
|
309
|
+
* to the last known value when the service is unavailable.
|
|
310
|
+
*/
|
|
311
|
+
refreshEnabledPlugins() {
|
|
312
|
+
const merged = this.configurationService?.getMergedEnabledPlugins(this.workdir);
|
|
313
|
+
return merged ?? this.enabledPlugins;
|
|
208
314
|
}
|
|
209
315
|
/**
|
|
210
316
|
* Load built-in plugins bundled with the SDK (e.g. sdd).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConfigurationService } from "../services/configurationService.js";
|
|
2
2
|
import { PluginManager } from "./pluginManager.js";
|
|
3
|
-
import { Logger } from "../types/index.js";
|
|
3
|
+
import { Logger, PluginInstallLocation } from "../types/index.js";
|
|
4
4
|
import { Scope } from "../types/configuration.js";
|
|
5
5
|
export interface PluginScopeManagerOptions {
|
|
6
6
|
workdir: string;
|
|
@@ -31,9 +31,22 @@ export declare class PluginScopeManager {
|
|
|
31
31
|
* Priority: local > project > user
|
|
32
32
|
*/
|
|
33
33
|
findPluginScope(pluginId: string): Scope | null;
|
|
34
|
+
/**
|
|
35
|
+
* The install location a record for `scope` is keyed by (spec plugin A-015):
|
|
36
|
+
* 用户作用域是本机全局的(不带 projectPath),项目/本地作用域归属当前仓库。
|
|
37
|
+
*/
|
|
38
|
+
getInstallLocation(scope: Scope): PluginInstallLocation;
|
|
39
|
+
/**
|
|
40
|
+
* Remove a plugin's enabled record from a single scope, leaving every other
|
|
41
|
+
* scope (including other projects' project/local records) untouched — the
|
|
42
|
+
* uninstall semantics of spec plugin A-015. Use [removePluginFromAllScopes]
|
|
43
|
+
* only for 「更换安装作用域」.
|
|
44
|
+
*/
|
|
45
|
+
removePluginFromScope(scope: Scope, pluginId: string): Promise<void>;
|
|
34
46
|
/**
|
|
35
47
|
* Remove a plugin from all scopes (user, project, local)
|
|
36
|
-
* This is useful when
|
|
48
|
+
* This is useful when moving a plugin to another scope: the old scope must
|
|
49
|
+
* not keep the plugin (spec plugin「设置页插件市场」场景 11).
|
|
37
50
|
*/
|
|
38
51
|
removePluginFromAllScopes(pluginId: string): Promise<void>;
|
|
39
52
|
/**
|
|
@@ -56,9 +56,28 @@ export class PluginScopeManager {
|
|
|
56
56
|
}
|
|
57
57
|
return null;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* The install location a record for `scope` is keyed by (spec plugin A-015):
|
|
61
|
+
* 用户作用域是本机全局的(不带 projectPath),项目/本地作用域归属当前仓库。
|
|
62
|
+
*/
|
|
63
|
+
getInstallLocation(scope) {
|
|
64
|
+
return scope === "user" ? { scope } : { scope, projectPath: this.workdir };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Remove a plugin's enabled record from a single scope, leaving every other
|
|
68
|
+
* scope (including other projects' project/local records) untouched — the
|
|
69
|
+
* uninstall semantics of spec plugin A-015. Use [removePluginFromAllScopes]
|
|
70
|
+
* only for 「更换安装作用域」.
|
|
71
|
+
*/
|
|
72
|
+
async removePluginFromScope(scope, pluginId) {
|
|
73
|
+
await this.configurationService.removeEnabledPlugin(this.workdir, scope, pluginId);
|
|
74
|
+
this.logger?.info(`Removed plugin ${pluginId} from ${scope} scope`);
|
|
75
|
+
this.refreshPluginManager();
|
|
76
|
+
}
|
|
59
77
|
/**
|
|
60
78
|
* Remove a plugin from all scopes (user, project, local)
|
|
61
|
-
* This is useful when
|
|
79
|
+
* This is useful when moving a plugin to another scope: the old scope must
|
|
80
|
+
* not keep the plugin (spec plugin「设置页插件市场」场景 11).
|
|
62
81
|
*/
|
|
63
82
|
async removePluginFromAllScopes(pluginId) {
|
|
64
83
|
const scopes = ["user", "project", "local"];
|
|
@@ -103,6 +103,17 @@ export declare class SkillManager extends EventEmitter {
|
|
|
103
103
|
content: string;
|
|
104
104
|
skill?: undefined;
|
|
105
105
|
}>;
|
|
106
|
+
/**
|
|
107
|
+
* Render a skill's body the way the model must see it: frontmatter stripped,
|
|
108
|
+
* parameters substituted, `${WAVE_SKILL_DIR}` / `${CLAUDE_SKILL_DIR}` /
|
|
109
|
+
* `${WAVE_PLUGIN_ROOT}` replaced with real paths, and the
|
|
110
|
+
* `Base directory for this skill: <path>` header prepended.
|
|
111
|
+
*
|
|
112
|
+
* Public because the post-compaction re-injection has to render the same
|
|
113
|
+
* text when it cannot reuse the content recorded at invocation time
|
|
114
|
+
* (spec core/message-compact 场景 4).
|
|
115
|
+
*/
|
|
116
|
+
renderSkillContent(skill: Skill, argsString?: string): string;
|
|
106
117
|
/**
|
|
107
118
|
* Prepare skill content with arguments but without bash execution
|
|
108
119
|
*/
|
|
@@ -119,6 +130,14 @@ export declare class SkillManager extends EventEmitter {
|
|
|
119
130
|
* Register skills provided by a plugin
|
|
120
131
|
*/
|
|
121
132
|
registerPluginSkills(pluginName: string, skills: Skill[]): void;
|
|
133
|
+
/**
|
|
134
|
+
* Drop every skill contributed by a plugin, addressed by its plugin name.
|
|
135
|
+
* Clears the plugin-specific maps too: refreshSkills() re-seeds the visible
|
|
136
|
+
* maps from them, so clearing only those would resurrect the skills on the
|
|
137
|
+
* next disk refresh. Called by the in-place plugin reload path before
|
|
138
|
+
* re-registering.
|
|
139
|
+
*/
|
|
140
|
+
unregisterPluginSkills(pluginName: string): void;
|
|
122
141
|
/**
|
|
123
142
|
* Directory roots that may hold deletable copies of a skill in the given
|
|
124
143
|
* discovery scope. Personal skills are scanned from three user-level dirs
|
|
@@ -422,6 +422,19 @@ export class SkillManager extends EventEmitter {
|
|
|
422
422
|
};
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Render a skill's body the way the model must see it: frontmatter stripped,
|
|
427
|
+
* parameters substituted, `${WAVE_SKILL_DIR}` / `${CLAUDE_SKILL_DIR}` /
|
|
428
|
+
* `${WAVE_PLUGIN_ROOT}` replaced with real paths, and the
|
|
429
|
+
* `Base directory for this skill: <path>` header prepended.
|
|
430
|
+
*
|
|
431
|
+
* Public because the post-compaction re-injection has to render the same
|
|
432
|
+
* text when it cannot reuse the content recorded at invocation time
|
|
433
|
+
* (spec core/message-compact 场景 4).
|
|
434
|
+
*/
|
|
435
|
+
renderSkillContent(skill, argsString = "") {
|
|
436
|
+
return this.prepareSkillContent(skill, argsString);
|
|
437
|
+
}
|
|
425
438
|
/**
|
|
426
439
|
* Prepare skill content with arguments but without bash execution
|
|
427
440
|
*/
|
|
@@ -496,6 +509,37 @@ export class SkillManager extends EventEmitter {
|
|
|
496
509
|
}
|
|
497
510
|
logger?.debug(`Registered ${skills.length} plugin skills from ${pluginName}. Total skills: ${this.skillMetadata.size}`);
|
|
498
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* Drop every skill contributed by a plugin, addressed by its plugin name.
|
|
514
|
+
* Clears the plugin-specific maps too: refreshSkills() re-seeds the visible
|
|
515
|
+
* maps from them, so clearing only those would resurrect the skills on the
|
|
516
|
+
* next disk refresh. Called by the in-place plugin reload path before
|
|
517
|
+
* re-registering.
|
|
518
|
+
*/
|
|
519
|
+
unregisterPluginSkills(pluginName) {
|
|
520
|
+
const prefix = `${pluginName}:`;
|
|
521
|
+
for (const name of Array.from(this.pluginSkillMetadata.keys())) {
|
|
522
|
+
if (name.startsWith(prefix)) {
|
|
523
|
+
this.pluginSkillMetadata.delete(name);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
for (const name of Array.from(this.pluginSkillContent.keys())) {
|
|
527
|
+
if (name.startsWith(prefix)) {
|
|
528
|
+
this.pluginSkillContent.delete(name);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
for (const name of Array.from(this.skillMetadata.keys())) {
|
|
532
|
+
if (name.startsWith(prefix)) {
|
|
533
|
+
this.skillMetadata.delete(name);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
for (const name of Array.from(this.skillContent.keys())) {
|
|
537
|
+
if (name.startsWith(prefix)) {
|
|
538
|
+
this.skillContent.delete(name);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
logger?.debug(`Unregistered plugin skills from ${pluginName}`);
|
|
542
|
+
}
|
|
499
543
|
/**
|
|
500
544
|
* Directory roots that may hold deletable copies of a skill in the given
|
|
501
545
|
* discovery scope. Personal skills are scanned from three user-level dirs
|
|
@@ -9,6 +9,9 @@ export declare class SlashCommandManager {
|
|
|
9
9
|
private commands;
|
|
10
10
|
private customCommands;
|
|
11
11
|
private skillCommandIds;
|
|
12
|
+
/** Ids registered by plugins, so they can be dropped per-plugin and are not
|
|
13
|
+
* mistaken for disk-sourced custom commands. */
|
|
14
|
+
private pluginCommandIds;
|
|
12
15
|
private workdir;
|
|
13
16
|
private currentCommandAbortController;
|
|
14
17
|
constructor(container: Container, options: SlashCommandManagerOptions);
|
|
@@ -29,6 +32,13 @@ export declare class SlashCommandManager {
|
|
|
29
32
|
* Register commands from a plugin with namespacing
|
|
30
33
|
*/
|
|
31
34
|
registerPluginCommands(pluginName: string, commands: CustomSlashCommand[]): void;
|
|
35
|
+
/**
|
|
36
|
+
* Drop every slash command contributed by a plugin, addressed by its plugin
|
|
37
|
+
* name. Removes the registry entry and the customCommands source entry.
|
|
38
|
+
* Called by the in-place plugin reload path before re-registering.
|
|
39
|
+
* @returns the number of commands removed
|
|
40
|
+
*/
|
|
41
|
+
unregisterPluginCommands(pluginName: string): number;
|
|
32
42
|
/**
|
|
33
43
|
* Reload custom commands (useful for development)
|
|
34
44
|
*/
|
|
@@ -9,6 +9,9 @@ export class SlashCommandManager {
|
|
|
9
9
|
this.commands = new Map();
|
|
10
10
|
this.customCommands = new Map();
|
|
11
11
|
this.skillCommandIds = new Set();
|
|
12
|
+
/** Ids registered by plugins, so they can be dropped per-plugin and are not
|
|
13
|
+
* mistaken for disk-sourced custom commands. */
|
|
14
|
+
this.pluginCommandIds = new Set();
|
|
12
15
|
this.currentCommandAbortController = null;
|
|
13
16
|
this.workdir = options.workdir;
|
|
14
17
|
}
|
|
@@ -233,6 +236,7 @@ export class SlashCommandManager {
|
|
|
233
236
|
const namespacedId = `${pluginName}:${command.id}`;
|
|
234
237
|
const namespacedName = `${pluginName}:${command.name}`;
|
|
235
238
|
this.customCommands.set(namespacedId, command);
|
|
239
|
+
this.pluginCommandIds.add(namespacedId);
|
|
236
240
|
// Generate description: prioritize custom description, otherwise use default description
|
|
237
241
|
const description = command.description ||
|
|
238
242
|
`Plugin command: ${namespacedName}${hasParameterPlaceholders(command.content) ? " (supports parameters)" : ""}`;
|
|
@@ -252,15 +256,43 @@ export class SlashCommandManager {
|
|
|
252
256
|
}
|
|
253
257
|
logger?.debug(`Registered ${commands.length} commands from plugin '${pluginName}'`);
|
|
254
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Drop every slash command contributed by a plugin, addressed by its plugin
|
|
261
|
+
* name. Removes the registry entry and the customCommands source entry.
|
|
262
|
+
* Called by the in-place plugin reload path before re-registering.
|
|
263
|
+
* @returns the number of commands removed
|
|
264
|
+
*/
|
|
265
|
+
unregisterPluginCommands(pluginName) {
|
|
266
|
+
const prefix = `${pluginName}:`;
|
|
267
|
+
let removed = 0;
|
|
268
|
+
for (const commandId of Array.from(this.pluginCommandIds)) {
|
|
269
|
+
if (!commandId.startsWith(prefix)) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
this.pluginCommandIds.delete(commandId);
|
|
273
|
+
this.customCommands.delete(commandId);
|
|
274
|
+
this.unregisterCommand(commandId);
|
|
275
|
+
removed += 1;
|
|
276
|
+
}
|
|
277
|
+
if (removed > 0) {
|
|
278
|
+
logger?.debug(`Unregistered ${removed} commands from plugin '${pluginName}'`);
|
|
279
|
+
}
|
|
280
|
+
return removed;
|
|
281
|
+
}
|
|
255
282
|
/**
|
|
256
283
|
* Reload custom commands (useful for development)
|
|
257
284
|
*/
|
|
258
285
|
reloadCustomCommands() {
|
|
259
|
-
//
|
|
260
|
-
|
|
286
|
+
// Reload only disk-sourced custom commands. Plugin commands live in the
|
|
287
|
+
// same map but are owned by the plugin lifecycle, so re-reading the disk
|
|
288
|
+
// must not drop them.
|
|
289
|
+
for (const [commandId] of this.customCommands) {
|
|
290
|
+
if (this.pluginCommandIds.has(commandId)) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
261
293
|
this.unregisterCommand(commandId);
|
|
294
|
+
this.customCommands.delete(commandId);
|
|
262
295
|
}
|
|
263
|
-
this.customCommands.clear();
|
|
264
296
|
// Reload
|
|
265
297
|
this.loadCustomCommands();
|
|
266
298
|
}
|
|
@@ -120,6 +120,14 @@ export declare class SubagentManager {
|
|
|
120
120
|
* Names each agent as `pluginName:agentName` to avoid collisions.
|
|
121
121
|
*/
|
|
122
122
|
registerPluginAgents(pluginName: string, agents: SubagentConfiguration[]): void;
|
|
123
|
+
/**
|
|
124
|
+
* Drop every subagent contributed by a plugin, addressed by its plugin name.
|
|
125
|
+
* Running instances are deliberately left alone — an in-place reload must not
|
|
126
|
+
* interrupt work already in flight. Called by the plugin reload path before
|
|
127
|
+
* re-registering.
|
|
128
|
+
* @returns the number of configurations removed
|
|
129
|
+
*/
|
|
130
|
+
unregisterPluginAgents(pluginName: string): number;
|
|
123
131
|
/**
|
|
124
132
|
* Create a new subagent instance with isolated managers
|
|
125
133
|
*/
|
|
@@ -183,6 +183,22 @@ export class SubagentManager {
|
|
|
183
183
|
return a.name.localeCompare(b.name);
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Drop every subagent contributed by a plugin, addressed by its plugin name.
|
|
188
|
+
* Running instances are deliberately left alone — an in-place reload must not
|
|
189
|
+
* interrupt work already in flight. Called by the plugin reload path before
|
|
190
|
+
* re-registering.
|
|
191
|
+
* @returns the number of configurations removed
|
|
192
|
+
*/
|
|
193
|
+
unregisterPluginAgents(pluginName) {
|
|
194
|
+
if (this.cachedConfigurations === null) {
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
const prefix = `${pluginName}:`;
|
|
198
|
+
const before = this.cachedConfigurations.length;
|
|
199
|
+
this.cachedConfigurations = this.cachedConfigurations.filter((config) => !config.name.startsWith(prefix));
|
|
200
|
+
return before - this.cachedConfigurations.length;
|
|
201
|
+
}
|
|
186
202
|
/**
|
|
187
203
|
* Create a new subagent instance with isolated managers
|
|
188
204
|
*/
|
|
@@ -232,6 +248,10 @@ export class SubagentManager {
|
|
|
232
248
|
const subagentPermissionManager = new PermissionManager(subagentContainer, {
|
|
233
249
|
configuredPermissionMode: parameters.permissionModeOverride ??
|
|
234
250
|
parentPermissionManager?.getConfiguredPermissionMode(),
|
|
251
|
+
// Inherit the parent session's bypass authorization: the subagent is
|
|
252
|
+
// created mid-session, so re-deriving it from its own mode would
|
|
253
|
+
// (wrongly) deny an authorized session's plan-mode subagent.
|
|
254
|
+
bypassAuthorization: parentPermissionManager?.getBypassAuthorization?.(),
|
|
235
255
|
allowedRules: parentPermissionManager?.getAllowedRules(),
|
|
236
256
|
deniedRules: parentPermissionManager?.getDeniedRules(),
|
|
237
257
|
instanceAllowedRules: parentPermissionManager?.getInstanceAllowedRules?.(),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ToolContext, ToolPlugin, ToolResult } from "../tools/types.js";
|
|
2
|
+
import type { RenderedCatalog } from "../exec/catalog.js";
|
|
2
3
|
import { PermissionManager } from "./permissionManager.js";
|
|
3
4
|
import { ChatCompletionFunctionTool } from "openai/resources.js";
|
|
4
5
|
import type { PermissionMode } from "../types/index.js";
|
|
@@ -56,15 +57,21 @@ declare class ToolManager {
|
|
|
56
57
|
/**
|
|
57
58
|
* Re-evaluate feature-gated built-in tools after a live configuration
|
|
58
59
|
* reload. Currently gates the Artifact tool on settings.json
|
|
59
|
-
* `enableArtifact`. Safe to call multiple
|
|
60
|
-
* from the registry first, then
|
|
61
|
-
* only if still enabled (so
|
|
60
|
+
* `enableArtifact` and the Exec tool on `enableExec`. Safe to call multiple
|
|
61
|
+
* times: gated tools are removed from the registry first, then
|
|
62
|
+
* initializeBuiltInTools() re-registers them only if still enabled (so
|
|
63
|
+
* toggling a flag off actually unregisters).
|
|
62
64
|
*/
|
|
63
65
|
reloadFeatureGatedTools(): void;
|
|
64
66
|
/**
|
|
65
67
|
* Check if a tool should be enabled based on tools configuration and permission rules
|
|
66
68
|
*/
|
|
67
69
|
private shouldEnableTool;
|
|
70
|
+
/**
|
|
71
|
+
* The auto-memory directory for this session, or undefined when the feature
|
|
72
|
+
* is off. Consumers (Read) treat "unset" as "not a memory file".
|
|
73
|
+
*/
|
|
74
|
+
private resolveAutoMemoryDir;
|
|
68
75
|
/**
|
|
69
76
|
* Execute a tool by name with the provided arguments and context
|
|
70
77
|
*
|
|
@@ -80,6 +87,25 @@ declare class ToolManager {
|
|
|
80
87
|
*/
|
|
81
88
|
execute(name: string, args: Record<string, unknown>, context: ToolContext): Promise<ToolResult>;
|
|
82
89
|
list(): ToolPlugin[];
|
|
90
|
+
/**
|
|
91
|
+
* Whether `Exec` is declared at all: registered and not denied. Exactly when the
|
|
92
|
+
* catalog channel is open — an undeclared `Exec` cannot be called, so a catalog
|
|
93
|
+
* for it would advertise a way in that does not exist.
|
|
94
|
+
*/
|
|
95
|
+
private isExecDeclared;
|
|
96
|
+
/** The tools the sandbox may reach — the pool the flat declarations give up. */
|
|
97
|
+
private execPool;
|
|
98
|
+
/**
|
|
99
|
+
* The MCP catalog as the model is meant to see it, or `undefined` when the
|
|
100
|
+
* catalog channel is closed.
|
|
101
|
+
*
|
|
102
|
+
* A function of the pool alone, and computed per call: with the catalog out of
|
|
103
|
+
* `tools[]` the declaration no longer moves when a server comes or goes, and the
|
|
104
|
+
* announcement channel diffs this against the history. Caching the rendering would
|
|
105
|
+
* mean caching pool state, which is exactly the process-side bookkeeping the
|
|
106
|
+
* channel is built to avoid.
|
|
107
|
+
*/
|
|
108
|
+
getExecCatalog(): RenderedCatalog | undefined;
|
|
83
109
|
getToolsConfig(options?: {
|
|
84
110
|
availableSubagents?: SubagentConfiguration[];
|
|
85
111
|
availableSkills?: SkillMetadata[];
|