wave-agent-sdk 1.1.5 → 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 +128 -28
- package/dist/agent.js +201 -49
- package/dist/builtin/index.js +2 -0
- package/dist/builtin/plugins.js +11 -20
- package/dist/builtin/skills/settings.js +7 -20
- 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 +54 -10
- package/dist/core/plugin.js +137 -23
- 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 +7 -1
- package/dist/index.js +8 -1
- package/dist/managers/MemoryRuleManager.d.ts +6 -0
- package/dist/managers/MemoryRuleManager.js +12 -0
- package/dist/managers/aiManager.d.ts +35 -25
- package/dist/managers/aiManager.js +204 -202
- package/dist/managers/backgroundTaskManager.js +14 -0
- package/dist/managers/bashModeManager.d.ts +33 -0
- package/dist/managers/bashModeManager.js +110 -0
- package/dist/managers/hookManager.d.ts +18 -0
- package/dist/managers/hookManager.js +37 -3
- package/dist/managers/liveConfigManager.d.ts +33 -0
- package/dist/managers/liveConfigManager.js +106 -11
- package/dist/managers/lspManager.d.ts +9 -0
- package/dist/managers/lspManager.js +47 -18
- package/dist/managers/mcpManager.d.ts +68 -10
- package/dist/managers/mcpManager.js +265 -15
- package/dist/managers/messageManager.d.ts +60 -18
- package/dist/managers/messageManager.js +170 -81
- package/dist/managers/permissionManager.d.ts +69 -0
- package/dist/managers/permissionManager.js +221 -78
- package/dist/managers/planManager.d.ts +9 -0
- package/dist/managers/planManager.js +19 -1
- 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 +50 -0
- package/dist/managers/skillManager.js +166 -12
- package/dist/managers/slashCommandManager.d.ts +10 -0
- package/dist/managers/slashCommandManager.js +44 -31
- package/dist/managers/subagentManager.d.ts +15 -0
- package/dist/managers/subagentManager.js +81 -9
- package/dist/managers/toolManager.d.ts +29 -3
- package/dist/managers/toolManager.js +87 -13
- package/dist/managers/workflowManager.js +6 -0
- 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/index.d.ts +0 -1
- package/dist/prompts/index.js +0 -4
- 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 +323 -102
- 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 +92 -9
- package/dist/services/configurationService.js +246 -64
- 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 +21 -17
- package/dist/services/interactionService.d.ts +9 -1
- package/dist/services/interactionService.js +28 -8
- package/dist/services/jsonlHandler.d.ts +98 -0
- package/dist/services/jsonlHandler.js +250 -12
- package/dist/services/memory.d.ts +17 -1
- package/dist/services/memory.js +44 -7
- package/dist/services/officialMarketplaceMirror.d.ts +85 -0
- package/dist/services/officialMarketplaceMirror.js +290 -0
- package/dist/services/pluginLoader.d.ts +12 -4
- package/dist/services/pluginLoader.js +38 -7
- package/dist/services/remoteSettingsService.js +20 -6
- package/dist/services/session.d.ts +74 -0
- package/dist/services/session.js +174 -16
- package/dist/services/sessionEntries.d.ts +2 -0
- package/dist/services/sessionEntries.js +20 -0
- package/dist/services/worktreeHooks.js +6 -1
- package/dist/stdio/index.d.ts +12 -0
- package/dist/stdio/index.js +12 -0
- package/dist/stdio/notificationRouter.d.ts +38 -0
- package/dist/stdio/notificationRouter.js +97 -0
- package/dist/stdio/rpcClient.d.ts +18 -0
- package/dist/stdio/rpcClient.js +10 -0
- package/dist/stdio/stdioAgent.d.ts +229 -0
- package/dist/stdio/stdioAgent.js +360 -0
- package/dist/tools/artifactTool.js +406 -273
- package/dist/tools/bashTool.js +10 -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/exitPlanMode.js +10 -2
- 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 +24 -1
- package/dist/types/commands.d.ts +7 -0
- package/dist/types/configuration.d.ts +45 -2
- package/dist/types/hooks.d.ts +1 -0
- package/dist/types/hooks.js +19 -0
- package/dist/types/marketplace.d.ts +40 -2
- package/dist/types/mcp.d.ts +42 -0
- package/dist/types/messaging.d.ts +1 -8
- 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 +26 -0
- package/dist/utils/bashParser.d.ts +17 -0
- package/dist/utils/bashParser.js +72 -0
- package/dist/utils/bashStructure/bashLexer.d.ts +96 -0
- package/dist/utils/bashStructure/bashLexer.js +676 -0
- package/dist/utils/bashStructure/bashParser.d.ts +144 -0
- package/dist/utils/bashStructure/bashParser.js +606 -0
- package/dist/utils/bashStructure/bashSemantics.d.ts +70 -0
- package/dist/utils/bashStructure/bashSemantics.js +477 -0
- package/dist/utils/bashStructure/index.d.ts +26 -0
- package/dist/utils/bashStructure/index.js +27 -0
- package/dist/utils/bashStructure/types.d.ts +62 -0
- package/dist/utils/bashStructure/types.js +47 -0
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +10 -0
- package/dist/utils/containerSetup.js +48 -6
- 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 +44 -0
- package/dist/utils/fileUtils.js +118 -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 -20
- package/dist/utils/messageOperations.js +40 -91
- 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 +48 -45
- package/dist/utils/tokenCalculation.js +0 -8
- package/dist/utils/userSettings.d.ts +90 -0
- package/dist/utils/userSettings.js +291 -0
- package/dist/utils/worktreeUtils.d.ts +2 -1
- package/dist/utils/worktreeUtils.js +64 -34
- package/package.json +12 -4
- package/dist/managers/bangManager.d.ts +0 -26
- package/dist/managers/bangManager.js +0 -78
|
@@ -9,39 +9,14 @@ 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
|
}
|
|
15
18
|
initialize() {
|
|
16
19
|
this.loadCustomCommands();
|
|
17
|
-
// Builtin /subtask: manual fork subagent (aligned with Claude Code's
|
|
18
|
-
// /subtask — inherits the parent's full context, runs in the background,
|
|
19
|
-
// result returns to the main conversation via a task notification).
|
|
20
|
-
this.registerCommand({
|
|
21
|
-
id: "subtask",
|
|
22
|
-
name: "subtask",
|
|
23
|
-
description: "Start a fork subagent with the current conversation context in the background",
|
|
24
|
-
handler: async (args, signal) => {
|
|
25
|
-
const taskDescription = args?.trim();
|
|
26
|
-
if (!taskDescription) {
|
|
27
|
-
this.messageManager.addErrorBlock("Usage: /subtask <task description>");
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
// The fork snapshots its context synchronously on entry, so the
|
|
32
|
-
// transcript echo below never duplicates the prompt inside the fork.
|
|
33
|
-
await this.aiManager.runForkSubagent(taskDescription, { description: taskDescription }, signal);
|
|
34
|
-
this.messageManager.addUserMessage({
|
|
35
|
-
content: `/subtask ${taskDescription}`,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
this.aiManager.setIsLoading(false);
|
|
40
|
-
logger?.error("Failed to execute /subtask:", error);
|
|
41
|
-
this.messageManager.addErrorBlock(`Failed to execute /subtask: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
20
|
// Listen for skill refreshes and update skill commands
|
|
46
21
|
const skillManager = this.container.get("SkillManager");
|
|
47
22
|
if (skillManager) {
|
|
@@ -103,6 +78,13 @@ export class SlashCommandManager {
|
|
|
103
78
|
this.unregisterCommand(commandId);
|
|
104
79
|
}
|
|
105
80
|
this.skillCommandIds.clear();
|
|
81
|
+
const skillSourceFor = (skill) => {
|
|
82
|
+
if (skill.pluginName)
|
|
83
|
+
return "plugin";
|
|
84
|
+
if (skill.type === "personal")
|
|
85
|
+
return "user";
|
|
86
|
+
return skill.type; // "project" | "builtin"
|
|
87
|
+
};
|
|
106
88
|
for (const skill of skills) {
|
|
107
89
|
if (skill.userInvocable === false) {
|
|
108
90
|
continue;
|
|
@@ -113,6 +95,8 @@ export class SlashCommandManager {
|
|
|
113
95
|
id: commandId,
|
|
114
96
|
name: skill.name,
|
|
115
97
|
description: `Skill: ${skill.description}`,
|
|
98
|
+
// UI source tag: plugin skills carry pluginName; personal → 用户
|
|
99
|
+
skillSource: skillSourceFor(skill),
|
|
116
100
|
handler: async (args, signal) => {
|
|
117
101
|
try {
|
|
118
102
|
// 1. Prepare skill content immediately
|
|
@@ -252,6 +236,7 @@ export class SlashCommandManager {
|
|
|
252
236
|
const namespacedId = `${pluginName}:${command.id}`;
|
|
253
237
|
const namespacedName = `${pluginName}:${command.name}`;
|
|
254
238
|
this.customCommands.set(namespacedId, command);
|
|
239
|
+
this.pluginCommandIds.add(namespacedId);
|
|
255
240
|
// Generate description: prioritize custom description, otherwise use default description
|
|
256
241
|
const description = command.description ||
|
|
257
242
|
`Plugin command: ${namespacedName}${hasParameterPlaceholders(command.content) ? " (supports parameters)" : ""}`;
|
|
@@ -271,15 +256,43 @@ export class SlashCommandManager {
|
|
|
271
256
|
}
|
|
272
257
|
logger?.debug(`Registered ${commands.length} commands from plugin '${pluginName}'`);
|
|
273
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
|
+
}
|
|
274
282
|
/**
|
|
275
283
|
* Reload custom commands (useful for development)
|
|
276
284
|
*/
|
|
277
285
|
reloadCustomCommands() {
|
|
278
|
-
//
|
|
279
|
-
|
|
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
|
+
}
|
|
280
293
|
this.unregisterCommand(commandId);
|
|
294
|
+
this.customCommands.delete(commandId);
|
|
281
295
|
}
|
|
282
|
-
this.customCommands.clear();
|
|
283
296
|
// Reload
|
|
284
297
|
this.loadCustomCommands();
|
|
285
298
|
}
|
|
@@ -100,6 +100,13 @@ export declare class SubagentManager {
|
|
|
100
100
|
* for conditional subagent registration (e.g. WAVE_VISION_MODEL).
|
|
101
101
|
*/
|
|
102
102
|
private getMergedEnv;
|
|
103
|
+
/**
|
|
104
|
+
* Delete a user-level or project-level subagent by removing its markdown
|
|
105
|
+
* file. Builtin and plugin agents are read-only and cannot be deleted.
|
|
106
|
+
* @param name - The subagent name as shown in configurations
|
|
107
|
+
* @returns true if the subagent was deleted
|
|
108
|
+
*/
|
|
109
|
+
deleteSubagent(name: string): Promise<boolean>;
|
|
103
110
|
/**
|
|
104
111
|
* Get cached configurations synchronously (must call loadConfigurations first)
|
|
105
112
|
*/
|
|
@@ -113,6 +120,14 @@ export declare class SubagentManager {
|
|
|
113
120
|
* Names each agent as `pluginName:agentName` to avoid collisions.
|
|
114
121
|
*/
|
|
115
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;
|
|
116
131
|
/**
|
|
117
132
|
* Create a new subagent instance with isolated managers
|
|
118
133
|
*/
|
|
@@ -107,6 +107,31 @@ export class SubagentManager {
|
|
|
107
107
|
return (this.configurationService?.getMergedEnv?.() ??
|
|
108
108
|
process.env);
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Delete a user-level or project-level subagent by removing its markdown
|
|
112
|
+
* file. Builtin and plugin agents are read-only and cannot be deleted.
|
|
113
|
+
* @param name - The subagent name as shown in configurations
|
|
114
|
+
* @returns true if the subagent was deleted
|
|
115
|
+
*/
|
|
116
|
+
async deleteSubagent(name) {
|
|
117
|
+
const configs = this.cachedConfigurations ?? [];
|
|
118
|
+
const config = configs.find((c) => c.name === name);
|
|
119
|
+
if (!config) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
// Builtin and plugin agents are read-only
|
|
123
|
+
if (config.scope !== "user" && config.scope !== "project") {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
const { unlink } = await import("fs/promises");
|
|
127
|
+
await unlink(config.filePath);
|
|
128
|
+
// Drop the deleted agent from the cached configurations immediately.
|
|
129
|
+
// A later refreshConfigurations() re-scans the filesystem and reflects
|
|
130
|
+
// the deletion persistently.
|
|
131
|
+
this.cachedConfigurations = configs.filter((c) => c.name !== name);
|
|
132
|
+
logger?.debug(`Deleted subagent '${name}' at ${config.filePath}`);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
110
135
|
/**
|
|
111
136
|
* Get cached configurations synchronously (must call loadConfigurations first)
|
|
112
137
|
*/
|
|
@@ -158,6 +183,22 @@ export class SubagentManager {
|
|
|
158
183
|
return a.name.localeCompare(b.name);
|
|
159
184
|
});
|
|
160
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
|
+
}
|
|
161
202
|
/**
|
|
162
203
|
* Create a new subagent instance with isolated managers
|
|
163
204
|
*/
|
|
@@ -207,6 +248,10 @@ export class SubagentManager {
|
|
|
207
248
|
const subagentPermissionManager = new PermissionManager(subagentContainer, {
|
|
208
249
|
configuredPermissionMode: parameters.permissionModeOverride ??
|
|
209
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?.(),
|
|
210
255
|
allowedRules: parentPermissionManager?.getAllowedRules(),
|
|
211
256
|
deniedRules: parentPermissionManager?.getDeniedRules(),
|
|
212
257
|
instanceAllowedRules: parentPermissionManager?.getInstanceAllowedRules?.(),
|
|
@@ -339,8 +384,13 @@ export class SubagentManager {
|
|
|
339
384
|
const result = await this.internalExecute(instance, prompt, abortSignal);
|
|
340
385
|
const task = backgroundTaskManager?.getTask(taskId);
|
|
341
386
|
if (task) {
|
|
342
|
-
|
|
343
|
-
task
|
|
387
|
+
// internalExecute already recorded the terminal state; only fill
|
|
388
|
+
// in a "completed" status when the task was not concurrently
|
|
389
|
+
// killed (mirrors the catch branch below).
|
|
390
|
+
if (task.status !== "killed") {
|
|
391
|
+
task.status = "completed";
|
|
392
|
+
task.stdout = result;
|
|
393
|
+
}
|
|
344
394
|
task.endTime = Date.now();
|
|
345
395
|
task.runtime = task.endTime - startTime;
|
|
346
396
|
}
|
|
@@ -348,9 +398,13 @@ export class SubagentManager {
|
|
|
348
398
|
catch (error) {
|
|
349
399
|
const task = backgroundTaskManager?.getTask(taskId);
|
|
350
400
|
if (task) {
|
|
351
|
-
|
|
352
|
-
task.
|
|
353
|
-
|
|
401
|
+
// Preserve a "killed" terminal state (internalExecute already
|
|
402
|
+
// notified it) instead of flipping the in-map task to failed.
|
|
403
|
+
if (task.status !== "killed") {
|
|
404
|
+
task.status = "failed";
|
|
405
|
+
task.stderr =
|
|
406
|
+
error instanceof Error ? error.message : String(error);
|
|
407
|
+
}
|
|
354
408
|
task.endTime = Date.now();
|
|
355
409
|
task.runtime = task.endTime - startTime;
|
|
356
410
|
}
|
|
@@ -480,8 +534,13 @@ export class SubagentManager {
|
|
|
480
534
|
const task = backgroundTaskManager.getTask(instance.backgroundTaskId);
|
|
481
535
|
if (task) {
|
|
482
536
|
const wasAlreadyKilled = task.status === "killed";
|
|
483
|
-
|
|
484
|
-
|
|
537
|
+
// Preserve a "killed" terminal state (stopTask already notified it)
|
|
538
|
+
// instead of overwriting it with "completed" — mirrors shell exit
|
|
539
|
+
// semantics in BackgroundTaskManager.onExit.
|
|
540
|
+
if (!wasAlreadyKilled) {
|
|
541
|
+
task.status = "completed";
|
|
542
|
+
task.stdout = response || "Agent completed with no text response";
|
|
543
|
+
}
|
|
485
544
|
task.endTime = Date.now();
|
|
486
545
|
if (task.startTime) {
|
|
487
546
|
task.runtime = task.endTime - task.startTime;
|
|
@@ -496,6 +555,11 @@ export class SubagentManager {
|
|
|
496
555
|
messageQueue.enqueueNotification(`<task-notification>\n<task-id>${instance.backgroundTaskId}</task-id>\n<task-type>agent</task-type>\n<status>completed</status>\n<summary>${summary}</summary>\n</task-notification>`);
|
|
497
556
|
}
|
|
498
557
|
}
|
|
558
|
+
// Push a terminal snapshot: SubagentManager mutates the task object in
|
|
559
|
+
// place, so without an explicit notify the UI/hosts keep the last
|
|
560
|
+
// snapshot — the task created with status "running" — and the IDE
|
|
561
|
+
// background-task gate (new chat / history restore) stays locked.
|
|
562
|
+
backgroundTaskManager.notifyTasksChange();
|
|
499
563
|
}
|
|
500
564
|
}
|
|
501
565
|
return response || "Agent completed with no text response";
|
|
@@ -512,12 +576,20 @@ export class SubagentManager {
|
|
|
512
576
|
const task = backgroundTaskManager.getTask(instance.backgroundTaskId);
|
|
513
577
|
if (task) {
|
|
514
578
|
const wasAlreadyKilled = task.status === "killed";
|
|
515
|
-
|
|
516
|
-
|
|
579
|
+
// Preserve a user/agent-initiated "killed" terminal state instead of
|
|
580
|
+
// overwriting it with "failed" (mirrors shell exit semantics) — the
|
|
581
|
+
// abort that stopped the task surfaces here as a thrown error.
|
|
582
|
+
if (!wasAlreadyKilled) {
|
|
583
|
+
task.status = "failed";
|
|
584
|
+
task.stderr =
|
|
585
|
+
error instanceof Error ? error.message : String(error);
|
|
586
|
+
}
|
|
517
587
|
task.endTime = Date.now();
|
|
518
588
|
if (task.startTime) {
|
|
519
589
|
task.runtime = task.endTime - task.startTime;
|
|
520
590
|
}
|
|
591
|
+
// Push a terminal snapshot (see the completion branch above).
|
|
592
|
+
backgroundTaskManager.notifyTasksChange();
|
|
521
593
|
// Skip notification if task was already stopped (e.g. by main agent shutdown)
|
|
522
594
|
if (!wasAlreadyKilled) {
|
|
523
595
|
const messageQueue = this.container.has("MessageQueue")
|
|
@@ -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[];
|
|
@@ -10,7 +10,12 @@ import { cronDeleteTool } from "../tools/cronDeleteTool.js";
|
|
|
10
10
|
import { cronListTool } from "../tools/cronListTool.js";
|
|
11
11
|
import { webFetchTool } from "../tools/webFetchTool.js";
|
|
12
12
|
import { artifactTool } from "../tools/artifactTool.js";
|
|
13
|
+
import { execTool } from "../tools/execTool.js";
|
|
13
14
|
import { isArtifactEnabled } from "../services/artifactAvailability.js";
|
|
15
|
+
import { isExecEnabled } from "../services/execAvailability.js";
|
|
16
|
+
import { buildExecPool, renderCatalog } from "../exec/catalog.js";
|
|
17
|
+
import { EXEC_DEFAULT_CATALOG_TOKENS } from "../exec/constants.js";
|
|
18
|
+
import { EXEC_TOOL_NAME } from "../constants/tools.js";
|
|
14
19
|
// New tools
|
|
15
20
|
import { globTool } from "../tools/globTool.js";
|
|
16
21
|
import { grepTool } from "../tools/grepTool.js";
|
|
@@ -102,6 +107,13 @@ class ToolManager {
|
|
|
102
107
|
if (isArtifactEnabled(this.container.get("Workdir"))) {
|
|
103
108
|
builtInTools.push(artifactTool);
|
|
104
109
|
}
|
|
110
|
+
// Exec is on by default; enableExec: false restores flat MCP declarations.
|
|
111
|
+
// Registration is decoupled from declaration: getToolsConfig() only declares
|
|
112
|
+
// it (and only then collapses the MCP pool) once MCP servers have connected
|
|
113
|
+
// and yielded a non-empty pool, which cannot be known until then.
|
|
114
|
+
if (isExecEnabled(this.container.get("Workdir"))) {
|
|
115
|
+
builtInTools.push(execTool);
|
|
116
|
+
}
|
|
105
117
|
for (const tool of builtInTools) {
|
|
106
118
|
if (this.shouldEnableTool(tool.name)) {
|
|
107
119
|
this.toolsRegistry.set(tool.name, tool);
|
|
@@ -117,12 +129,14 @@ class ToolManager {
|
|
|
117
129
|
/**
|
|
118
130
|
* Re-evaluate feature-gated built-in tools after a live configuration
|
|
119
131
|
* reload. Currently gates the Artifact tool on settings.json
|
|
120
|
-
* `enableArtifact`. Safe to call multiple
|
|
121
|
-
* from the registry first, then
|
|
122
|
-
* only if still enabled (so
|
|
132
|
+
* `enableArtifact` and the Exec tool on `enableExec`. Safe to call multiple
|
|
133
|
+
* times: gated tools are removed from the registry first, then
|
|
134
|
+
* initializeBuiltInTools() re-registers them only if still enabled (so
|
|
135
|
+
* toggling a flag off actually unregisters).
|
|
123
136
|
*/
|
|
124
137
|
reloadFeatureGatedTools() {
|
|
125
138
|
this.toolsRegistry.delete(artifactTool.name);
|
|
139
|
+
this.toolsRegistry.delete(execTool.name);
|
|
126
140
|
this.initializeBuiltInTools();
|
|
127
141
|
}
|
|
128
142
|
/**
|
|
@@ -139,6 +153,22 @@ class ToolManager {
|
|
|
139
153
|
}
|
|
140
154
|
return this.tools.some((toolName) => toolName.toLowerCase() === name.toLowerCase());
|
|
141
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* The auto-memory directory for this session, or undefined when the feature
|
|
158
|
+
* is off. Consumers (Read) treat "unset" as "not a memory file".
|
|
159
|
+
*/
|
|
160
|
+
resolveAutoMemoryDir(workdir) {
|
|
161
|
+
if (!this.container.has("MemoryService"))
|
|
162
|
+
return undefined;
|
|
163
|
+
const configuration = this.container.has("ConfigurationService")
|
|
164
|
+
? this.container.get("ConfigurationService")
|
|
165
|
+
: undefined;
|
|
166
|
+
if (!configuration?.resolveAutoMemoryEnabled?.())
|
|
167
|
+
return undefined;
|
|
168
|
+
return this.container
|
|
169
|
+
.get("MemoryService")
|
|
170
|
+
?.getAutoMemoryDirectory?.(workdir);
|
|
171
|
+
}
|
|
142
172
|
/**
|
|
143
173
|
* Execute a tool by name with the provided arguments and context
|
|
144
174
|
*
|
|
@@ -202,6 +232,7 @@ class ToolManager {
|
|
|
202
232
|
.get("ConfigurationService")
|
|
203
233
|
?.getMergedEnv?.()
|
|
204
234
|
: undefined,
|
|
235
|
+
autoMemoryDir: this.resolveAutoMemoryDir(context.workdir),
|
|
205
236
|
sessionId: context.sessionId,
|
|
206
237
|
toolCallId: context.toolCallId,
|
|
207
238
|
};
|
|
@@ -287,10 +318,53 @@ class ToolManager {
|
|
|
287
318
|
.filter((tool) => !permissionManager?.isToolDenied(tool.name));
|
|
288
319
|
return [...builtInTools, ...mcpTools];
|
|
289
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Whether `Exec` is declared at all: registered and not denied. Exactly when the
|
|
323
|
+
* catalog channel is open — an undeclared `Exec` cannot be called, so a catalog
|
|
324
|
+
* for it would advertise a way in that does not exist.
|
|
325
|
+
*/
|
|
326
|
+
isExecDeclared() {
|
|
327
|
+
return (this.toolsRegistry.has(EXEC_TOOL_NAME) &&
|
|
328
|
+
!this.getPermissionManager()?.isToolDenied(EXEC_TOOL_NAME));
|
|
329
|
+
}
|
|
330
|
+
/** The tools the sandbox may reach — the pool the flat declarations give up. */
|
|
331
|
+
execPool() {
|
|
332
|
+
return buildExecPool(this.mcpManager, this.getPermissionManager());
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* The MCP catalog as the model is meant to see it, or `undefined` when the
|
|
336
|
+
* catalog channel is closed.
|
|
337
|
+
*
|
|
338
|
+
* A function of the pool alone, and computed per call: with the catalog out of
|
|
339
|
+
* `tools[]` the declaration no longer moves when a server comes or goes, and the
|
|
340
|
+
* announcement channel diffs this against the history. Caching the rendering would
|
|
341
|
+
* mean caching pool state, which is exactly the process-side bookkeeping the
|
|
342
|
+
* channel is built to avoid.
|
|
343
|
+
*/
|
|
344
|
+
getExecCatalog() {
|
|
345
|
+
if (!this.isExecDeclared())
|
|
346
|
+
return undefined;
|
|
347
|
+
return renderCatalog(this.execPool(), EXEC_DEFAULT_CATALOG_TOKENS);
|
|
348
|
+
}
|
|
290
349
|
getToolsConfig(options) {
|
|
291
|
-
const permissionManager = this.
|
|
350
|
+
const permissionManager = this.getPermissionManager();
|
|
351
|
+
// Exec either replaces the flat MCP declarations or is absent: the two must
|
|
352
|
+
// never coexist, or the model would see the same tool twice while the
|
|
353
|
+
// catalog claimed to be the only way in. Both halves are derived from the
|
|
354
|
+
// same pool, and the pool is exactly what the agent could already call
|
|
355
|
+
// directly, so collapsing it cannot widen access.
|
|
356
|
+
const execPool = this.execPool();
|
|
357
|
+
// No minimum pool size: any catalogable tool collapses the pool, matching
|
|
358
|
+
// opencode. The switch (`enableExec`), not a count, decides whether Exec is
|
|
359
|
+
// used at all.
|
|
360
|
+
const collapseMcp = this.isExecDeclared() && execPool.length > 0;
|
|
292
361
|
const builtInToolsConfig = Array.from(this.toolsRegistry.values())
|
|
293
362
|
.filter((tool) => {
|
|
363
|
+
// With an empty pool there is nothing to collapse, so Exec stays
|
|
364
|
+
// registered but undeclared rather than declaring an empty catalog.
|
|
365
|
+
if (tool.name === EXEC_TOOL_NAME && !collapseMcp) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
294
368
|
// If tool is explicitly denied by name in permission rules, filter it out
|
|
295
369
|
if (permissionManager?.isToolDenied(tool.name)) {
|
|
296
370
|
return false;
|
|
@@ -307,18 +381,18 @@ class ToolManager {
|
|
|
307
381
|
};
|
|
308
382
|
// Override description with prompt if available
|
|
309
383
|
if (tool.prompt) {
|
|
310
|
-
config.function.description = tool.prompt(options);
|
|
384
|
+
config.function.description = tool.prompt({ ...options });
|
|
311
385
|
}
|
|
312
386
|
return config;
|
|
313
387
|
});
|
|
314
|
-
const mcpToolsConfig =
|
|
315
|
-
|
|
316
|
-
.filter((tool) => {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
388
|
+
const mcpToolsConfig = collapseMcp
|
|
389
|
+
? []
|
|
390
|
+
: this.mcpManager.getMcpToolsConfig().filter((tool) => {
|
|
391
|
+
if (permissionManager?.isToolDenied(tool.function.name)) {
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
});
|
|
322
396
|
return [...builtInToolsConfig, ...mcpToolsConfig];
|
|
323
397
|
}
|
|
324
398
|
/**
|
|
@@ -207,6 +207,10 @@ export class WorkflowManager {
|
|
|
207
207
|
task.status = "completed";
|
|
208
208
|
task.endTime = Date.now();
|
|
209
209
|
}
|
|
210
|
+
// Push the terminal snapshot: without it the last snapshot holds the
|
|
211
|
+
// "running" registration and UI consumers (background-task gate) never
|
|
212
|
+
// see the workflow finish.
|
|
213
|
+
this.backgroundTaskManager.notifyTasksChange();
|
|
210
214
|
// Enqueue completion notification
|
|
211
215
|
const journalPath = journal.filePath;
|
|
212
216
|
this.messageQueue.enqueueNotification(taskNotificationToXml({
|
|
@@ -244,6 +248,8 @@ export class WorkflowManager {
|
|
|
244
248
|
task.stderr = run.error || "";
|
|
245
249
|
task.endTime = Date.now();
|
|
246
250
|
}
|
|
251
|
+
// Push the terminal snapshot (see the completion branch above).
|
|
252
|
+
this.backgroundTaskManager.notifyTasksChange();
|
|
247
253
|
this.messageQueue.enqueueNotification(taskNotificationToXml({
|
|
248
254
|
type: "task_notification",
|
|
249
255
|
taskId: runId,
|
|
@@ -1 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the main agent's auto-memory prompt. The section order mirrors Claude
|
|
3
|
+
* Code's `buildMemoryLines()` (individual-only variant). The index content of
|
|
4
|
+
* `MEMORY.md` is appended separately by the system prompt builder, so this
|
|
5
|
+
* function only produces the behavioral instructions.
|
|
6
|
+
*
|
|
7
|
+
* Every section except the directory line is shared with the extraction fork
|
|
8
|
+
* prompt (`autoMemoryExtraction.ts`) — see `memoryTypes.ts` for why.
|
|
9
|
+
*/
|
|
1
10
|
export declare function buildAutoMemoryPrompt(memoryDir: string): string;
|
|
@@ -1,33 +1,32 @@
|
|
|
1
|
+
import { DIR_EXISTS_GUIDANCE, MEMORY_AND_OTHER_PERSISTENCE_SECTION, TYPES_SECTION, TRUSTING_RECALL_SECTION, WHAT_NOT_TO_SAVE_SECTION, WHEN_TO_ACCESS_SECTION, buildHowToSaveSection, } from "./memoryTypes.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build the main agent's auto-memory prompt. The section order mirrors Claude
|
|
4
|
+
* Code's `buildMemoryLines()` (individual-only variant). The index content of
|
|
5
|
+
* `MEMORY.md` is appended separately by the system prompt builder, so this
|
|
6
|
+
* function only produces the behavioral instructions.
|
|
7
|
+
*
|
|
8
|
+
* Every section except the directory line is shared with the extraction fork
|
|
9
|
+
* prompt (`autoMemoryExtraction.ts`) — see `memoryTypes.ts` for why.
|
|
10
|
+
*/
|
|
1
11
|
export function buildAutoMemoryPrompt(memoryDir) {
|
|
2
|
-
return
|
|
3
|
-
# auto memory
|
|
4
|
-
|
|
5
|
-
You have a persistent
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## What NOT to save:
|
|
24
|
-
- Session-specific context (current task details, in-progress work, temporary state)
|
|
25
|
-
- Information that might be incomplete — verify against project docs before writing
|
|
26
|
-
- Anything that duplicates or contradicts existing AGENTS.md instructions
|
|
27
|
-
- Speculative or unverified conclusions from reading a single file
|
|
28
|
-
|
|
29
|
-
## Explicit user requests:
|
|
30
|
-
- When the user asks you to remember something across sessions (e.g., "always use bun", "never auto-commit"), save it — no need to wait for multiple interactions
|
|
31
|
-
- When the user asks to forget or stop remembering something, find and remove the relevant entries from your memory files
|
|
32
|
-
`;
|
|
12
|
+
return [
|
|
13
|
+
"# auto memory",
|
|
14
|
+
"",
|
|
15
|
+
`You have a persistent, file-based memory system at \`${memoryDir}\`. ${DIR_EXISTS_GUIDANCE}`,
|
|
16
|
+
"",
|
|
17
|
+
"You should build up this memory system over time so that future conversations can have a complete picture of who the user is, how they'd like to collaborate with you, what behaviors to avoid or repeat, and the context behind the work the user gives you.",
|
|
18
|
+
"",
|
|
19
|
+
"If the user explicitly asks you to remember something, save it immediately as whichever type fits best. If they ask you to forget something, find and remove the relevant entry.",
|
|
20
|
+
"",
|
|
21
|
+
...TYPES_SECTION,
|
|
22
|
+
...WHAT_NOT_TO_SAVE_SECTION,
|
|
23
|
+
"",
|
|
24
|
+
...buildHowToSaveSection(),
|
|
25
|
+
"",
|
|
26
|
+
...WHEN_TO_ACCESS_SECTION,
|
|
27
|
+
"",
|
|
28
|
+
...TRUSTING_RECALL_SECTION,
|
|
29
|
+
"",
|
|
30
|
+
...MEMORY_AND_OTHER_PERSISTENCE_SECTION,
|
|
31
|
+
].join("\n");
|
|
33
32
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build the auto-memory extraction prompt for the background agent.
|
|
3
|
+
*
|
|
4
|
+
* The taxonomy, the "what not to save" list, and the save procedure are shared
|
|
5
|
+
* with the main agent's memory prompt (`memoryTypes.ts`): the fork reuses the
|
|
6
|
+
* main system prompt, so both texts are in the same request.
|
|
3
7
|
*/
|
|
4
8
|
export declare function buildAutoMemoryExtractionPrompt(newMessageCount: number, existingMemoriesManifest: string): string;
|