wave-agent-sdk 0.15.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builtin/skills/loop/SKILL.md +29 -3
- package/dist/agent.d.ts +11 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +44 -11
- package/dist/constants/tools.d.ts +3 -0
- package/dist/constants/tools.d.ts.map +1 -1
- package/dist/constants/tools.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/managers/aiManager.d.ts +13 -1
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +69 -17
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +9 -0
- package/dist/managers/mcpManager.d.ts +4 -1
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +25 -5
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +7 -6
- package/dist/managers/permissionManager.d.ts +0 -2
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +0 -30
- package/dist/managers/slashCommandManager.d.ts +1 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +20 -4
- package/dist/managers/subagentManager.d.ts +6 -1
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +17 -18
- package/dist/managers/toolManager.d.ts +6 -0
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +41 -1
- package/dist/prompts/index.d.ts +1 -2
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +14 -6
- package/dist/services/initializationService.d.ts +0 -2
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +3 -35
- package/dist/services/jsonlHandler.d.ts +4 -4
- package/dist/services/jsonlHandler.d.ts.map +1 -1
- package/dist/services/jsonlHandler.js +4 -13
- package/dist/services/memory.d.ts +6 -0
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +27 -14
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +3 -12
- package/dist/tools/agentTool.d.ts.map +1 -1
- package/dist/tools/agentTool.js +16 -4
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +2 -5
- package/dist/tools/cronCreateTool.d.ts.map +1 -1
- package/dist/tools/cronCreateTool.js +71 -6
- package/dist/tools/cronDeleteTool.d.ts.map +1 -1
- package/dist/tools/cronDeleteTool.js +5 -1
- package/dist/tools/cronListTool.d.ts.map +1 -1
- package/dist/tools/cronListTool.js +5 -1
- package/dist/tools/enterWorktreeTool.d.ts +8 -0
- package/dist/tools/enterWorktreeTool.d.ts.map +1 -0
- package/dist/tools/enterWorktreeTool.js +144 -0
- package/dist/tools/exitWorktreeTool.d.ts +8 -0
- package/dist/tools/exitWorktreeTool.d.ts.map +1 -0
- package/dist/tools/exitWorktreeTool.js +184 -0
- package/dist/tools/skillTool.d.ts.map +1 -1
- package/dist/tools/skillTool.js +16 -4
- package/dist/tools/taskManagementTools.d.ts.map +1 -1
- package/dist/tools/taskManagementTools.js +4 -0
- package/dist/tools/toolSearchTool.d.ts +15 -0
- package/dist/tools/toolSearchTool.d.ts.map +1 -0
- package/dist/tools/toolSearchTool.js +185 -0
- package/dist/tools/types.d.ts +19 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/webFetchTool.d.ts.map +1 -1
- package/dist/tools/webFetchTool.js +1 -0
- package/dist/types/agent.d.ts +6 -1
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +3 -1
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +1 -0
- package/dist/types/messaging.d.ts +1 -0
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/session.d.ts +0 -4
- package/dist/types/session.d.ts.map +1 -1
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +4 -6
- package/dist/utils/cronToHuman.d.ts +6 -0
- package/dist/utils/cronToHuman.d.ts.map +1 -0
- package/dist/utils/cronToHuman.js +79 -0
- package/dist/utils/isDeferredTool.d.ts +19 -0
- package/dist/utils/isDeferredTool.d.ts.map +1 -0
- package/dist/utils/isDeferredTool.js +31 -0
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +1 -0
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +5 -0
- package/dist/utils/parseCronExpression.d.ts +6 -0
- package/dist/utils/parseCronExpression.d.ts.map +1 -0
- package/dist/utils/parseCronExpression.js +74 -0
- package/dist/utils/worktreeSession.d.ts +26 -0
- package/dist/utils/worktreeSession.d.ts.map +1 -0
- package/dist/utils/worktreeSession.js +14 -0
- package/dist/utils/worktreeUtils.d.ts +42 -0
- package/dist/utils/worktreeUtils.d.ts.map +1 -0
- package/dist/utils/worktreeUtils.js +236 -0
- package/package.json +1 -1
- package/src/agent.ts +61 -12
- package/src/constants/tools.ts +3 -0
- package/src/index.ts +1 -0
- package/src/managers/aiManager.ts +73 -18
- package/src/managers/hookManager.ts +10 -0
- package/src/managers/mcpManager.ts +32 -6
- package/src/managers/messageManager.ts +7 -8
- package/src/managers/permissionManager.ts +0 -42
- package/src/managers/slashCommandManager.ts +30 -5
- package/src/managers/subagentManager.ts +28 -23
- package/src/managers/toolManager.ts +47 -1
- package/src/prompts/index.ts +17 -6
- package/src/services/initializationService.ts +2 -41
- package/src/services/jsonlHandler.ts +12 -24
- package/src/services/memory.ts +30 -17
- package/src/services/session.ts +3 -14
- package/src/tools/agentTool.ts +24 -5
- package/src/tools/bashTool.ts +2 -5
- package/src/tools/cronCreateTool.ts +81 -8
- package/src/tools/cronDeleteTool.ts +7 -2
- package/src/tools/cronListTool.ts +7 -2
- package/src/tools/enterWorktreeTool.ts +183 -0
- package/src/tools/exitWorktreeTool.ts +242 -0
- package/src/tools/skillTool.ts +24 -4
- package/src/tools/taskManagementTools.ts +4 -0
- package/src/tools/toolSearchTool.ts +228 -0
- package/src/tools/types.ts +19 -0
- package/src/tools/webFetchTool.ts +1 -0
- package/src/types/agent.ts +6 -0
- package/src/types/hooks.ts +4 -0
- package/src/types/messaging.ts +1 -0
- package/src/types/session.ts +0 -8
- package/src/utils/containerSetup.ts +7 -8
- package/src/utils/cronToHuman.ts +99 -0
- package/src/utils/isDeferredTool.ts +36 -0
- package/src/utils/mcpUtils.ts +1 -0
- package/src/utils/messageOperations.ts +5 -0
- package/src/utils/parseCronExpression.ts +78 -0
- package/src/utils/worktreeSession.ts +36 -0
- package/src/utils/worktreeUtils.ts +288 -0
|
@@ -24,7 +24,7 @@ interface McpConnection {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export interface McpManagerCallbacks {
|
|
27
|
-
|
|
27
|
+
onMcpServersChange?: (servers: McpServerStatus[]) => void;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
import { logger } from "../utils/globalLogger.js";
|
|
@@ -32,6 +32,8 @@ import { logger } from "../utils/globalLogger.js";
|
|
|
32
32
|
export interface McpManagerOptions {
|
|
33
33
|
callbacks?: McpManagerCallbacks;
|
|
34
34
|
logger?: Logger;
|
|
35
|
+
/** Pre-configured MCP servers passed from constructor options */
|
|
36
|
+
mcpServers?: Record<string, McpServerConfig>;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/**
|
|
@@ -96,12 +98,14 @@ export class McpManager {
|
|
|
96
98
|
private configPath: string = "";
|
|
97
99
|
private workdir: string = "";
|
|
98
100
|
private callbacks: McpManagerCallbacks;
|
|
101
|
+
private mcpServers: Record<string, McpServerConfig> | undefined;
|
|
99
102
|
|
|
100
103
|
constructor(
|
|
101
104
|
private container: Container,
|
|
102
105
|
options: McpManagerOptions = {},
|
|
103
106
|
) {
|
|
104
107
|
this.callbacks = options.callbacks || {};
|
|
108
|
+
this.mcpServers = options.mcpServers;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
/**
|
|
@@ -114,11 +118,20 @@ export class McpManager {
|
|
|
114
118
|
this.configPath = join(workdir, ".mcp.json");
|
|
115
119
|
this.workdir = workdir;
|
|
116
120
|
|
|
121
|
+
// Register constructor-provided servers before loading .mcp.json
|
|
122
|
+
if (this.mcpServers) {
|
|
123
|
+
for (const [name, config] of Object.entries(this.mcpServers)) {
|
|
124
|
+
this.addServer(name, config);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
117
128
|
if (autoConnect) {
|
|
118
129
|
logger?.debug("Initializing MCP servers...");
|
|
119
130
|
|
|
120
|
-
//
|
|
121
|
-
|
|
131
|
+
// Load workspace MCP configuration (always read, merge with any plugin servers already added)
|
|
132
|
+
await this.loadConfig();
|
|
133
|
+
|
|
134
|
+
const config = this.config;
|
|
122
135
|
|
|
123
136
|
if (config && config.mcpServers) {
|
|
124
137
|
// Connect to all configured servers in background to avoid blocking agent initialization
|
|
@@ -145,7 +158,7 @@ export class McpManager {
|
|
|
145
158
|
|
|
146
159
|
logger?.debug("MCP servers initialization started in background");
|
|
147
160
|
// Trigger state change callback after starting initialization
|
|
148
|
-
this.callbacks.
|
|
161
|
+
this.callbacks.onMcpServersChange?.(this.getAllServers());
|
|
149
162
|
}
|
|
150
163
|
}
|
|
151
164
|
|
|
@@ -164,7 +177,20 @@ export class McpManager {
|
|
|
164
177
|
|
|
165
178
|
try {
|
|
166
179
|
const configContent = await fs.readFile(this.configPath, "utf-8");
|
|
167
|
-
|
|
180
|
+
const workspaceConfig = resolveMcpConfig(JSON.parse(configContent));
|
|
181
|
+
|
|
182
|
+
// Merge workspace config with any existing config (e.g., from plugins or constructor)
|
|
183
|
+
// Constructor-provided servers take precedence, then workspace config, then existing config
|
|
184
|
+
const merged: McpConfig = { mcpServers: {} };
|
|
185
|
+
if (this.config) {
|
|
186
|
+
Object.assign(merged.mcpServers, this.config.mcpServers);
|
|
187
|
+
}
|
|
188
|
+
Object.assign(merged.mcpServers, workspaceConfig.mcpServers);
|
|
189
|
+
// Constructor-provided servers override both for same names
|
|
190
|
+
if (this.mcpServers) {
|
|
191
|
+
Object.assign(merged.mcpServers, this.mcpServers);
|
|
192
|
+
}
|
|
193
|
+
this.config = merged;
|
|
168
194
|
|
|
169
195
|
// Initialize server statuses (preserve existing status for already known servers)
|
|
170
196
|
if (this.config) {
|
|
@@ -226,7 +252,7 @@ export class McpManager {
|
|
|
226
252
|
if (server) {
|
|
227
253
|
this.servers.set(name, { ...server, ...updates });
|
|
228
254
|
// Trigger state change callback
|
|
229
|
-
this.callbacks.
|
|
255
|
+
this.callbacks.onMcpServersChange?.(this.getAllServers());
|
|
230
256
|
}
|
|
231
257
|
}
|
|
232
258
|
|
|
@@ -518,6 +518,7 @@ export class MessageManager {
|
|
|
518
518
|
sessionId: this.sessionId,
|
|
519
519
|
},
|
|
520
520
|
],
|
|
521
|
+
timestamp: new Date().toISOString(),
|
|
521
522
|
...(usage && { usage }),
|
|
522
523
|
};
|
|
523
524
|
|
|
@@ -959,15 +960,13 @@ export class MessageManager {
|
|
|
959
960
|
try {
|
|
960
961
|
const { writeFile } = await import("fs/promises");
|
|
961
962
|
|
|
962
|
-
const sessionMessages: import("../types/session.js").SessionMessage[] =
|
|
963
|
-
messages.map((message) => ({
|
|
964
|
-
...message,
|
|
965
|
-
timestamp: new Date().toISOString(),
|
|
966
|
-
}));
|
|
967
|
-
|
|
968
963
|
const content =
|
|
969
|
-
|
|
970
|
-
|
|
964
|
+
messages
|
|
965
|
+
.map((m) => {
|
|
966
|
+
const { timestamp, ...rest } = m;
|
|
967
|
+
return JSON.stringify({ timestamp, ...rest });
|
|
968
|
+
})
|
|
969
|
+
.join("\n") + (messages.length > 0 ? "\n" : "");
|
|
971
970
|
|
|
972
971
|
await writeFile(this.transcriptPath, content, "utf8");
|
|
973
972
|
} catch (error) {
|
|
@@ -129,8 +129,6 @@ export class PermissionManager {
|
|
|
129
129
|
private additionalDirectories: string[] = [];
|
|
130
130
|
private systemAdditionalDirectories: string[] = [];
|
|
131
131
|
private planFilePath?: string;
|
|
132
|
-
private worktreeName?: string;
|
|
133
|
-
private mainRepoRoot?: string;
|
|
134
132
|
private workdir?: string;
|
|
135
133
|
private onConfiguredPermissionModeChange?: (mode: PermissionMode) => void;
|
|
136
134
|
private _logger?: Logger;
|
|
@@ -151,8 +149,6 @@ export class PermissionManager {
|
|
|
151
149
|
this.addSystemAdditionalDirectory(dir);
|
|
152
150
|
}
|
|
153
151
|
|
|
154
|
-
this.worktreeName = this.container.get<string>("WorktreeName");
|
|
155
|
-
this.mainRepoRoot = this.container.get<string>("MainRepoRoot");
|
|
156
152
|
this.workdir = this.container.get<string>("Workdir");
|
|
157
153
|
}
|
|
158
154
|
|
|
@@ -438,44 +434,6 @@ export class PermissionManager {
|
|
|
438
434
|
}
|
|
439
435
|
}
|
|
440
436
|
|
|
441
|
-
// 0. Check worktree safety for Write and Edit tools
|
|
442
|
-
const currentWorkdir = this.getWorkdir();
|
|
443
|
-
if (
|
|
444
|
-
this.worktreeName &&
|
|
445
|
-
this.mainRepoRoot &&
|
|
446
|
-
currentWorkdir &&
|
|
447
|
-
(context.toolName === WRITE_TOOL_NAME ||
|
|
448
|
-
context.toolName === EDIT_TOOL_NAME)
|
|
449
|
-
) {
|
|
450
|
-
const targetPath = context.toolInput?.file_path as string | undefined;
|
|
451
|
-
if (targetPath) {
|
|
452
|
-
const absoluteTargetPath = path.resolve(currentWorkdir, targetPath);
|
|
453
|
-
const isInsideMainRepo = isPathInside(
|
|
454
|
-
absoluteTargetPath,
|
|
455
|
-
this.mainRepoRoot,
|
|
456
|
-
);
|
|
457
|
-
const isInsideWorktree = isPathInside(
|
|
458
|
-
absoluteTargetPath,
|
|
459
|
-
currentWorkdir,
|
|
460
|
-
);
|
|
461
|
-
|
|
462
|
-
// If it's inside the main repo but NOT inside the current worktree
|
|
463
|
-
if (isInsideMainRepo && !isInsideWorktree) {
|
|
464
|
-
logger?.warn("Worktree safety violation", {
|
|
465
|
-
toolName: context.toolName,
|
|
466
|
-
targetPath,
|
|
467
|
-
worktreeName: this.worktreeName,
|
|
468
|
-
mainRepoRoot: this.mainRepoRoot,
|
|
469
|
-
workdir: currentWorkdir,
|
|
470
|
-
});
|
|
471
|
-
return {
|
|
472
|
-
behavior: "deny",
|
|
473
|
-
message: `Access denied: You are currently in a worktree session ("${this.worktreeName}"). Modifying files in the main repository (outside the worktree) is not allowed. Please only modify files within the worktree directory: ${currentWorkdir}`,
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
437
|
// 0. Check denied rules first - Deny always takes precedence
|
|
480
438
|
for (const rule of this.deniedRules) {
|
|
481
439
|
if (this.matchesRule(context, rule)) {
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import type { SkillManager } from "./skillManager.js";
|
|
24
24
|
import type { SkillMetadata } from "../types/skills.js";
|
|
25
25
|
import type { SubagentManager } from "./subagentManager.js";
|
|
26
|
+
import type { MemoryService } from "../services/memory.js";
|
|
26
27
|
|
|
27
28
|
import { logger } from "../utils/globalLogger.js";
|
|
28
29
|
|
|
@@ -81,6 +82,10 @@ export class SlashCommandManager {
|
|
|
81
82
|
return this.container.get<SubagentManager>("SubagentManager")!;
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
private get memoryService(): MemoryService {
|
|
86
|
+
return this.container.get<MemoryService>("MemoryService")!;
|
|
87
|
+
}
|
|
88
|
+
|
|
84
89
|
private initializeBuiltinCommands(): void {
|
|
85
90
|
// Register built-in clear command
|
|
86
91
|
this.registerCommand({
|
|
@@ -90,6 +95,7 @@ export class SlashCommandManager {
|
|
|
90
95
|
handler: async () => {
|
|
91
96
|
this.aiManager.abortAIMessage();
|
|
92
97
|
this.messageManager.clearMessages();
|
|
98
|
+
this.memoryService.clearCache();
|
|
93
99
|
await this.taskManager.syncWithSession();
|
|
94
100
|
},
|
|
95
101
|
});
|
|
@@ -224,20 +230,39 @@ export class SlashCommandManager {
|
|
|
224
230
|
const messages = subagent.messages;
|
|
225
231
|
const tokens =
|
|
226
232
|
subagent.messageManager.getLatestTotalTokens();
|
|
227
|
-
const
|
|
233
|
+
const usedTools = subagent.usedTools;
|
|
228
234
|
|
|
229
235
|
const toolCount = countToolBlocks(messages);
|
|
230
236
|
const summary = formatToolTokenSummary(toolCount, tokens);
|
|
231
237
|
|
|
238
|
+
const getDisplayParam = (t: {
|
|
239
|
+
name: string;
|
|
240
|
+
parameters: string;
|
|
241
|
+
compactParams?: string;
|
|
242
|
+
stage?: string;
|
|
243
|
+
}) => {
|
|
244
|
+
if (
|
|
245
|
+
(t.stage === "end" || t.stage === "running") &&
|
|
246
|
+
t.compactParams
|
|
247
|
+
) {
|
|
248
|
+
return t.compactParams;
|
|
249
|
+
}
|
|
250
|
+
const flat = t.parameters.replace(/\n/g, "\\n");
|
|
251
|
+
return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
|
|
252
|
+
};
|
|
253
|
+
|
|
232
254
|
let shortResult = "";
|
|
233
255
|
if (toolCount > 2) {
|
|
234
256
|
shortResult += "... ";
|
|
235
257
|
}
|
|
236
|
-
if (lastTools.length > 0) {
|
|
237
|
-
shortResult += `${lastTools.join(", ")} `;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
258
|
shortResult += summary;
|
|
259
|
+
if (usedTools.length > 0) {
|
|
260
|
+
shortResult +=
|
|
261
|
+
"\n" +
|
|
262
|
+
usedTools
|
|
263
|
+
.map((t) => `${t.name} ${getDisplayParam(t)}`)
|
|
264
|
+
.join("\n");
|
|
265
|
+
}
|
|
241
266
|
|
|
242
267
|
this.messageManager.updateToolBlock({
|
|
243
268
|
id: toolBlockId,
|
|
@@ -68,7 +68,12 @@ export interface SubagentInstance {
|
|
|
68
68
|
toolManager: ToolManager;
|
|
69
69
|
status: "initializing" | "active" | "completed" | "error" | "aborted";
|
|
70
70
|
messages: Message[];
|
|
71
|
-
|
|
71
|
+
usedTools: {
|
|
72
|
+
name: string;
|
|
73
|
+
parameters: string;
|
|
74
|
+
compactParams?: string;
|
|
75
|
+
stage?: string;
|
|
76
|
+
}[]; // Track tools with display info
|
|
72
77
|
subagentType: string; // Store the subagent type for hook context
|
|
73
78
|
description: string; // Store the AI-generated description
|
|
74
79
|
allowedTools?: string[]; // Optional permission rules (e.g. git:*)
|
|
@@ -388,7 +393,7 @@ export class SubagentManager {
|
|
|
388
393
|
toolManager,
|
|
389
394
|
status: "initializing",
|
|
390
395
|
messages: [],
|
|
391
|
-
|
|
396
|
+
usedTools: [], // Initialize usedTools
|
|
392
397
|
subagentType: parameters.subagent_type, // Store the subagent type
|
|
393
398
|
description: parameters.description, // Store the AI-generated description
|
|
394
399
|
allowedTools: parameters.allowedTools, // Store optional permission rules
|
|
@@ -790,27 +795,16 @@ export class SubagentManager {
|
|
|
790
795
|
},
|
|
791
796
|
|
|
792
797
|
onToolBlockUpdated: (params: AgentToolBlockUpdateParams) => {
|
|
793
|
-
|
|
794
|
-
if (
|
|
795
|
-
|
|
796
|
-
if (instance) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
instance.onUpdate?.();
|
|
804
|
-
|
|
805
|
-
// Log tool execution to file
|
|
806
|
-
if (instance.logStream) {
|
|
807
|
-
const displayParams =
|
|
808
|
-
params.compactParams ||
|
|
809
|
-
(params.parameters || "{}").substring(0, 100);
|
|
810
|
-
instance.logStream.write(
|
|
811
|
-
`[${new Date().toISOString()}] ${params.name}${displayParams ? ` ${displayParams}` : ""}\n`,
|
|
812
|
-
);
|
|
813
|
-
}
|
|
798
|
+
const instance = this.instances.get(subagentId);
|
|
799
|
+
if (instance) {
|
|
800
|
+
// Log tool execution to file only when finalized
|
|
801
|
+
if (instance.logStream && params.stage === "end") {
|
|
802
|
+
const displayParams =
|
|
803
|
+
params.compactParams ||
|
|
804
|
+
(params.parameters || "").substring(0, 100);
|
|
805
|
+
instance.logStream.write(
|
|
806
|
+
`[${new Date().toISOString()}] ${params.name}${displayParams ? ` ${displayParams}` : ""}\n`,
|
|
807
|
+
);
|
|
814
808
|
}
|
|
815
809
|
}
|
|
816
810
|
|
|
@@ -825,6 +819,17 @@ export class SubagentManager {
|
|
|
825
819
|
const instance = this.instances.get(subagentId);
|
|
826
820
|
if (instance) {
|
|
827
821
|
instance.messages = messages;
|
|
822
|
+
// Compute usedTools from messages (last 2 tool blocks)
|
|
823
|
+
const toolBlocks = messages.flatMap(
|
|
824
|
+
(m) => m.blocks?.filter((b) => b.type === "tool") ?? [],
|
|
825
|
+
);
|
|
826
|
+
const last2 = toolBlocks.slice(-2);
|
|
827
|
+
instance.usedTools = last2.map((tb) => ({
|
|
828
|
+
name: tb.name ?? "",
|
|
829
|
+
parameters: tb.parameters ?? "",
|
|
830
|
+
compactParams: tb.compactParams,
|
|
831
|
+
stage: tb.stage,
|
|
832
|
+
}));
|
|
828
833
|
// Trigger the onUpdate callback if provided
|
|
829
834
|
instance.onUpdate?.();
|
|
830
835
|
// Forward subagent message changes to parent via callbacks
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
taskUpdateTool,
|
|
24
24
|
taskListTool,
|
|
25
25
|
} from "../tools/taskManagementTools.js";
|
|
26
|
+
import { enterWorktreeTool } from "../tools/enterWorktreeTool.js";
|
|
27
|
+
import { exitWorktreeTool } from "../tools/exitWorktreeTool.js";
|
|
26
28
|
import { McpManager } from "./mcpManager.js";
|
|
27
29
|
import { PermissionManager } from "./permissionManager.js";
|
|
28
30
|
import { ChatCompletionFunctionTool } from "openai/resources.js";
|
|
@@ -43,6 +45,8 @@ import { logger } from "../utils/globalLogger.js";
|
|
|
43
45
|
|
|
44
46
|
import type { SubagentConfiguration } from "../utils/subagentParser.js";
|
|
45
47
|
import type { SkillMetadata } from "../types/skills.js";
|
|
48
|
+
import { toolSearchTool } from "../tools/toolSearchTool.js";
|
|
49
|
+
import { isDeferredTool } from "../utils/isDeferredTool.js";
|
|
46
50
|
|
|
47
51
|
export interface ToolManagerOptions {
|
|
48
52
|
container: Container;
|
|
@@ -123,6 +127,9 @@ class ToolManager {
|
|
|
123
127
|
cronDeleteTool,
|
|
124
128
|
cronListTool,
|
|
125
129
|
webFetchTool,
|
|
130
|
+
enterWorktreeTool,
|
|
131
|
+
exitWorktreeTool,
|
|
132
|
+
toolSearchTool,
|
|
126
133
|
];
|
|
127
134
|
|
|
128
135
|
for (const tool of builtInTools) {
|
|
@@ -191,6 +198,7 @@ class ToolManager {
|
|
|
191
198
|
permissionMode: effectivePermissionMode,
|
|
192
199
|
canUseToolCallback,
|
|
193
200
|
permissionManager,
|
|
201
|
+
toolManager: this, // Allow ToolSearchTool to access the tool manager
|
|
194
202
|
taskManager:
|
|
195
203
|
this.container.get<import("../services/taskManager.js").TaskManager>(
|
|
196
204
|
"TaskManager",
|
|
@@ -224,6 +232,11 @@ class ToolManager {
|
|
|
224
232
|
this.container.get<import("./messageManager.js").MessageManager>(
|
|
225
233
|
"MessageManager",
|
|
226
234
|
)!,
|
|
235
|
+
hookManager: this.container.has("HookManager")
|
|
236
|
+
? this.container.get<import("./hookManager.js").HookManager>(
|
|
237
|
+
"HookManager",
|
|
238
|
+
)
|
|
239
|
+
: undefined,
|
|
227
240
|
sessionId: context.sessionId,
|
|
228
241
|
toolCallId: context.toolCallId,
|
|
229
242
|
};
|
|
@@ -287,10 +300,13 @@ class ToolManager {
|
|
|
287
300
|
availableSkills?: SkillMetadata[];
|
|
288
301
|
workdir?: string;
|
|
289
302
|
isSubagent?: boolean;
|
|
303
|
+
/** Set of discovered deferred tool names to include in the API call */
|
|
304
|
+
discoveredTools?: Set<string>;
|
|
290
305
|
}): ChatCompletionFunctionTool[] {
|
|
291
306
|
const permissionManager =
|
|
292
307
|
this.container.get<PermissionManager>("PermissionManager");
|
|
293
308
|
const effectivePermissionMode = this.getPermissionMode();
|
|
309
|
+
const discoveredTools = options?.discoveredTools;
|
|
294
310
|
const builtInToolsConfig = Array.from(this.toolsRegistry.values())
|
|
295
311
|
.filter((tool) => {
|
|
296
312
|
// If tool is explicitly denied by name in permission rules, filter it out
|
|
@@ -315,6 +331,10 @@ class ToolManager {
|
|
|
315
331
|
effectivePermissionMode !== "bypassPermissions"
|
|
316
332
|
);
|
|
317
333
|
}
|
|
334
|
+
// Exclude deferred tools that haven't been discovered yet
|
|
335
|
+
if (isDeferredTool(tool) && !discoveredTools?.has(tool.name)) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
318
338
|
return true;
|
|
319
339
|
})
|
|
320
340
|
.map((tool) => {
|
|
@@ -333,7 +353,16 @@ class ToolManager {
|
|
|
333
353
|
});
|
|
334
354
|
const mcpToolsConfig = this.mcpManager
|
|
335
355
|
.getMcpToolsConfig()
|
|
336
|
-
.filter((tool) =>
|
|
356
|
+
.filter((tool) => {
|
|
357
|
+
if (permissionManager?.isToolDenied(tool.function.name)) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
// Exclude MCP tools that haven't been discovered yet
|
|
361
|
+
if (discoveredTools && !discoveredTools.has(tool.function.name)) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
return true;
|
|
365
|
+
});
|
|
337
366
|
return [...builtInToolsConfig, ...mcpToolsConfig];
|
|
338
367
|
}
|
|
339
368
|
|
|
@@ -375,6 +404,23 @@ class ToolManager {
|
|
|
375
404
|
return this.container.get<PermissionManager>("PermissionManager");
|
|
376
405
|
}
|
|
377
406
|
|
|
407
|
+
/**
|
|
408
|
+
* Get the names of all deferred tools (those that require ToolSearch to discover).
|
|
409
|
+
*/
|
|
410
|
+
public getDeferredToolNames(): string[] {
|
|
411
|
+
const permissionManager =
|
|
412
|
+
this.container.get<PermissionManager>("PermissionManager");
|
|
413
|
+
const builtInDeferred = Array.from(this.toolsRegistry.values())
|
|
414
|
+
.filter((tool) => isDeferredTool(tool))
|
|
415
|
+
.filter((tool) => !permissionManager?.isToolDenied(tool.name))
|
|
416
|
+
.map((tool) => tool.name);
|
|
417
|
+
const mcpDeferred = this.mcpManager
|
|
418
|
+
.getMcpToolsConfig()
|
|
419
|
+
.filter((tool) => !permissionManager?.isToolDenied(tool.function.name))
|
|
420
|
+
.map((tool) => tool.function.name);
|
|
421
|
+
return [...builtInDeferred, ...mcpDeferred];
|
|
422
|
+
}
|
|
423
|
+
|
|
378
424
|
/**
|
|
379
425
|
* Get the task manager
|
|
380
426
|
*/
|
package/src/prompts/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import { ToolPlugin } from "../tools/types.js";
|
|
3
3
|
import { isGitRepository } from "../utils/gitUtils.js";
|
|
4
|
+
import { getCurrentWorktreeSession } from "../utils/worktreeSession.js";
|
|
4
5
|
import { buildAutoMemoryPrompt } from "./autoMemory.js";
|
|
5
6
|
import { PermissionMode } from "../types/permissions.js";
|
|
6
7
|
import {
|
|
@@ -17,9 +18,9 @@ import {
|
|
|
17
18
|
READ_TOOL_NAME,
|
|
18
19
|
GLOB_TOOL_NAME,
|
|
19
20
|
GREP_TOOL_NAME,
|
|
21
|
+
TOOL_SEARCH_TOOL_NAME,
|
|
20
22
|
} from "../constants/tools.js";
|
|
21
|
-
|
|
22
|
-
export const MAX_PARALLEL_TOOL_CALLS = 3;
|
|
23
|
+
import { isDeferredTool } from "../utils/isDeferredTool.js";
|
|
23
24
|
|
|
24
25
|
export const BASE_SYSTEM_PROMPT = `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.`;
|
|
25
26
|
|
|
@@ -60,7 +61,6 @@ export const TOOL_POLICY = `# Using your tools
|
|
|
60
61
|
- To search the content of files, use ${GREP_TOOL_NAME} instead of grep or rg
|
|
61
62
|
- Reserve using the ${BASH_TOOL_NAME} exclusively for system commands and terminal operations that require shell execution. If you are unsure and there is a relevant dedicated tool, default to using the dedicated tool and only fallback on using the ${BASH_TOOL_NAME} tool for these if it is absolutely necessary.
|
|
62
63
|
- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency.
|
|
63
|
-
- **Limit**: You MUST NOT call more than ${MAX_PARALLEL_TOOL_CALLS} tools in parallel in a single response.
|
|
64
64
|
- However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.
|
|
65
65
|
- If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks.`;
|
|
66
66
|
|
|
@@ -260,6 +260,13 @@ export function buildSystemPrompt(
|
|
|
260
260
|
prompt += `\n\n${TOOL_POLICY}`;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
// List available deferred tool names so the model knows they exist
|
|
264
|
+
// Matching Claude Code: deferred tools appear by name, not loaded until fetched.
|
|
265
|
+
const deferredToolNames = tools.filter(isDeferredTool).map((t) => t.name);
|
|
266
|
+
if (deferredToolNames.length > 0) {
|
|
267
|
+
prompt += `\n\n<available-deferred-tools>${deferredToolNames.join(" ")}\nThese tools are NOT loaded yet — call ${TOOL_SEARCH_TOOL_NAME} first to discover their schemas before invoking them.</available-deferred-tools>`;
|
|
268
|
+
}
|
|
269
|
+
|
|
263
270
|
prompt += `\n\n${OUTPUT_EFFICIENCY_PROMPT}`;
|
|
264
271
|
prompt += `\n\n${TONE_AND_STYLE_PROMPT}`;
|
|
265
272
|
|
|
@@ -287,11 +294,13 @@ export function buildSystemPrompt(
|
|
|
287
294
|
? "bash"
|
|
288
295
|
: shell;
|
|
289
296
|
|
|
297
|
+
const worktreeSession = getCurrentWorktreeSession();
|
|
298
|
+
|
|
290
299
|
prompt += `
|
|
291
300
|
|
|
292
301
|
Here is useful information about the environment you are running in:
|
|
293
302
|
<env>
|
|
294
|
-
Working directory: ${options.workdir}
|
|
303
|
+
Working directory: ${options.workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
|
|
295
304
|
Is directory a git repo: ${isGitRepo}
|
|
296
305
|
Platform: ${platform}
|
|
297
306
|
Shell: ${shellName}
|
|
@@ -330,8 +339,10 @@ export function enhanceSystemPromptWithEnvDetails(
|
|
|
330
339
|
? "bash"
|
|
331
340
|
: shell;
|
|
332
341
|
|
|
342
|
+
const worktreeSession = getCurrentWorktreeSession();
|
|
343
|
+
|
|
333
344
|
const notes = `Notes:
|
|
334
|
-
- Agent threads always have their cwd reset between bash calls, as a result please only use absolute file paths.
|
|
345
|
+
- Agent threads always have their cwd reset between bash calls, as a result please only use absolute file paths.${worktreeSession ? `\n- You are in a git worktree at ${worktreeSession.worktreePath} (branch: ${worktreeSession.worktreeBranch}). Absolute paths from prior context may refer to the original repo at ${worktreeSession.originalCwd}; translate them to your worktree. Do NOT edit files outside this worktree.` : ""}
|
|
335
346
|
- In your final response, share file paths (always absolute, never relative) that are relevant to the task. Include code snippets only when the exact text is load-bearing (e.g., a bug you found, a function signature the caller asked for) — do not recap code you merely read.
|
|
336
347
|
- For clear communication with the user the assistant MUST avoid using emojis.
|
|
337
348
|
- Do not use a colon before tool calls. Text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.`;
|
|
@@ -342,7 +353,7 @@ ${notes}
|
|
|
342
353
|
|
|
343
354
|
Here is useful information about the environment you are running in:
|
|
344
355
|
<env>
|
|
345
|
-
Working directory: ${workdir}
|
|
356
|
+
Working directory: ${workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
|
|
346
357
|
Is directory a git repo: ${isGitRepo}
|
|
347
358
|
Platform: ${platform}
|
|
348
359
|
Shell: ${shellName}
|
|
@@ -22,7 +22,6 @@ import type { MemoryRuleManager } from "../managers/MemoryRuleManager.js";
|
|
|
22
22
|
import type { LiveConfigManager } from "../managers/liveConfigManager.js";
|
|
23
23
|
import type { TaskManager } from "./taskManager.js";
|
|
24
24
|
import type { PermissionManager } from "../managers/permissionManager.js";
|
|
25
|
-
import type { MemoryService } from "./memory.js";
|
|
26
25
|
|
|
27
26
|
export interface InitializationContext {
|
|
28
27
|
skillManager: SkillManager;
|
|
@@ -42,8 +41,6 @@ export interface InitializationContext {
|
|
|
42
41
|
memoryRuleManager: MemoryRuleManager;
|
|
43
42
|
liveConfigManager: LiveConfigManager;
|
|
44
43
|
taskManager: TaskManager;
|
|
45
|
-
setProjectMemory: (content: string) => void;
|
|
46
|
-
setUserMemory: (content: string) => void;
|
|
47
44
|
resolveAndValidateConfig: () => void;
|
|
48
45
|
}
|
|
49
46
|
|
|
@@ -74,8 +71,6 @@ export class InitializationService {
|
|
|
74
71
|
memoryRuleManager,
|
|
75
72
|
liveConfigManager,
|
|
76
73
|
taskManager,
|
|
77
|
-
setProjectMemory,
|
|
78
|
-
setUserMemory,
|
|
79
74
|
resolveAndValidateConfig,
|
|
80
75
|
} = context;
|
|
81
76
|
|
|
@@ -293,42 +288,8 @@ export class InitializationService {
|
|
|
293
288
|
// Don't throw error to prevent app startup failure - continue without live reload
|
|
294
289
|
}
|
|
295
290
|
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
const phaseStart = performance.now();
|
|
299
|
-
const memoryService = container.get<MemoryService>("MemoryService");
|
|
300
|
-
if (!memoryService) {
|
|
301
|
-
throw new Error("MemoryService not found in container");
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Load project memory from AGENTS.md
|
|
305
|
-
try {
|
|
306
|
-
const projectMemoryContent =
|
|
307
|
-
await memoryService.readMemoryFile(workdir);
|
|
308
|
-
setProjectMemory(projectMemoryContent);
|
|
309
|
-
} catch (error) {
|
|
310
|
-
logger?.warn("Failed to load project memory file:", error);
|
|
311
|
-
setProjectMemory("");
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Load user memory
|
|
315
|
-
try {
|
|
316
|
-
const userMemoryContent = await memoryService.getUserMemoryContent();
|
|
317
|
-
setUserMemory(userMemoryContent);
|
|
318
|
-
} catch (error) {
|
|
319
|
-
logger?.warn("Failed to load user memory file:", error);
|
|
320
|
-
setUserMemory("");
|
|
321
|
-
}
|
|
322
|
-
logger?.debug(
|
|
323
|
-
`Initialization Phase [Memory Files Loading] took ${(performance.now() - phaseStart).toFixed(2)}ms`,
|
|
324
|
-
);
|
|
325
|
-
} catch (error) {
|
|
326
|
-
// Ensure memory is always initialized even if loading fails
|
|
327
|
-
setProjectMemory("");
|
|
328
|
-
setUserMemory("");
|
|
329
|
-
logger?.error("Failed to load memory files:", error);
|
|
330
|
-
// Don't throw error to prevent app startup failure
|
|
331
|
-
}
|
|
291
|
+
// Memory is lazy-cached on first getCombinedMemoryContent call
|
|
292
|
+
// No explicit loading needed during initialization
|
|
332
293
|
|
|
333
294
|
// Handle session restoration or set provided messages
|
|
334
295
|
const sessionPhaseStart = performance.now();
|