wave-agent-sdk 0.11.5 → 0.11.7
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/init/SKILL.md +2 -0
- package/builtin/skills/settings/SKILLS.md +3 -2
- package/builtin/skills/settings/SUBAGENTS.md +1 -3
- package/dist/agent.d.ts +6 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +18 -1
- package/dist/constants/tools.d.ts +1 -1
- package/dist/constants/tools.d.ts.map +1 -1
- package/dist/constants/tools.js +1 -1
- package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
- package/dist/managers/MemoryRuleManager.js +1 -9
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +22 -3
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +32 -13
- package/dist/managers/messageManager.d.ts +13 -5
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +62 -34
- package/dist/managers/permissionManager.js +4 -4
- package/dist/managers/pluginManager.d.ts.map +1 -1
- package/dist/managers/pluginManager.js +4 -2
- package/dist/managers/slashCommandManager.d.ts +2 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +98 -4
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +8 -2
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +5 -0
- package/dist/services/GitService.d.ts +1 -0
- package/dist/services/GitService.d.ts.map +1 -1
- package/dist/services/GitService.js +16 -0
- package/dist/services/MarketplaceService.d.ts +7 -0
- package/dist/services/MarketplaceService.d.ts.map +1 -1
- package/dist/services/MarketplaceService.js +321 -252
- package/dist/services/aiService.d.ts +34 -0
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +124 -1
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +18 -0
- package/dist/tools/agentTool.js +3 -3
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +4 -4
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +2 -0
- package/dist/tools/globTool.d.ts.map +1 -1
- package/dist/tools/globTool.js +15 -3
- package/dist/tools/grepTool.d.ts.map +1 -1
- package/dist/tools/grepTool.js +38 -12
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +61 -0
- package/dist/tools/skillTool.js +2 -2
- package/dist/tools/types.d.ts +16 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/webFetchTool.d.ts +3 -0
- package/dist/tools/webFetchTool.d.ts.map +1 -0
- package/dist/tools/webFetchTool.js +171 -0
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +2 -0
- package/dist/types/commands.d.ts +1 -1
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/messaging.d.ts +1 -0
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/utils/bashParser.d.ts +20 -2
- package/dist/utils/bashParser.d.ts.map +1 -1
- package/dist/utils/bashParser.js +281 -146
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +7 -0
- package/dist/utils/fileUtils.d.ts +8 -0
- package/dist/utils/fileUtils.d.ts.map +1 -1
- package/dist/utils/fileUtils.js +52 -0
- package/dist/utils/messageOperations.d.ts +12 -3
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +77 -9
- package/package.json +4 -2
- package/src/agent.ts +19 -1
- package/src/constants/tools.ts +1 -1
- package/src/managers/MemoryRuleManager.ts +1 -10
- package/src/managers/aiManager.ts +23 -3
- package/src/managers/mcpManager.ts +37 -16
- package/src/managers/messageManager.ts +76 -38
- package/src/managers/permissionManager.ts +4 -4
- package/src/managers/pluginManager.ts +4 -2
- package/src/managers/slashCommandManager.ts +130 -4
- package/src/managers/toolManager.ts +11 -2
- package/src/prompts/index.ts +6 -0
- package/src/services/GitService.ts +20 -0
- package/src/services/MarketplaceService.ts +397 -324
- package/src/services/aiService.ts +197 -1
- package/src/services/initializationService.ts +38 -0
- package/src/tools/agentTool.ts +3 -3
- package/src/tools/bashTool.ts +3 -4
- package/src/tools/editTool.ts +3 -0
- package/src/tools/globTool.ts +16 -3
- package/src/tools/grepTool.ts +41 -13
- package/src/tools/readTool.ts +69 -0
- package/src/tools/skillTool.ts +2 -2
- package/src/tools/types.ts +13 -0
- package/src/tools/webFetchTool.ts +194 -0
- package/src/tools/writeTool.ts +3 -0
- package/src/types/commands.ts +1 -1
- package/src/types/messaging.ts +1 -0
- package/src/utils/bashParser.ts +316 -161
- package/src/utils/convertMessagesForAPI.ts +8 -0
- package/src/utils/fileUtils.ts +69 -0
- package/src/utils/messageOperations.ts +84 -9
- package/dist/tools/taskOutputTool.d.ts +0 -3
- package/dist/tools/taskOutputTool.d.ts.map +0 -1
- package/dist/tools/taskOutputTool.js +0 -198
- package/src/tools/taskOutputTool.ts +0 -222
|
@@ -44,7 +44,7 @@ export const convertImageToBase64 = (imagePath) => {
|
|
|
44
44
|
};
|
|
45
45
|
export const generateMessageId = () => `msg-${randomUUID()}`;
|
|
46
46
|
// Add user message
|
|
47
|
-
export const addUserMessageToMessages = ({ messages, content, images, customCommandContent, source, id, }) => {
|
|
47
|
+
export const addUserMessageToMessages = ({ messages, content, images, customCommandContent, source, id, isMeta, }) => {
|
|
48
48
|
const blocks = [];
|
|
49
49
|
// Create text block with optional customCommandContent and source
|
|
50
50
|
const textBlock = {
|
|
@@ -66,6 +66,7 @@ export const addUserMessageToMessages = ({ messages, content, images, customComm
|
|
|
66
66
|
id: id || generateMessageId(),
|
|
67
67
|
role: "user",
|
|
68
68
|
blocks,
|
|
69
|
+
...(isMeta !== undefined && { isMeta }),
|
|
69
70
|
};
|
|
70
71
|
return [...messages, userMessage];
|
|
71
72
|
};
|
|
@@ -88,7 +89,11 @@ export const updateUserMessageInMessages = (messages, id, params) => {
|
|
|
88
89
|
}
|
|
89
90
|
return block;
|
|
90
91
|
});
|
|
91
|
-
return {
|
|
92
|
+
return {
|
|
93
|
+
...msg,
|
|
94
|
+
blocks: newBlocks,
|
|
95
|
+
...(params.isMeta !== undefined && { isMeta: params.isMeta }),
|
|
96
|
+
};
|
|
92
97
|
}
|
|
93
98
|
return msg;
|
|
94
99
|
});
|
|
@@ -122,12 +127,74 @@ export const addAssistantMessageToMessages = (messages, content, toolCalls, usag
|
|
|
122
127
|
};
|
|
123
128
|
return [...messages, initialAssistantMessage];
|
|
124
129
|
};
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Add a tool block to a specific message by ID.
|
|
132
|
+
*/
|
|
133
|
+
export const addToolBlockToMessageInMessages = (messages, messageId, params) => {
|
|
134
|
+
const toolBlockId = randomUUID();
|
|
135
|
+
const newMessages = messages.map((msg) => {
|
|
136
|
+
if (msg.id === messageId) {
|
|
137
|
+
return {
|
|
138
|
+
...msg,
|
|
139
|
+
blocks: [
|
|
140
|
+
...msg.blocks,
|
|
141
|
+
{
|
|
142
|
+
type: "tool",
|
|
143
|
+
id: toolBlockId,
|
|
144
|
+
name: params.name || "unknown",
|
|
145
|
+
parameters: params.parameters || "",
|
|
146
|
+
result: params.result || "",
|
|
147
|
+
stage: "start",
|
|
148
|
+
...params,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return msg;
|
|
154
|
+
});
|
|
155
|
+
return { messages: newMessages, toolBlockId };
|
|
156
|
+
};
|
|
157
|
+
// Update Tool Block of the last assistant or user message
|
|
158
|
+
export const updateToolBlockInMessage = ({ messages, id, messageId, parameters, result, success, error, stage, name, shortResult, startLineNumber, images, compactParams, parametersChunk, isManuallyBackgrounded, }) => {
|
|
127
159
|
const newMessages = [...messages];
|
|
128
|
-
//
|
|
160
|
+
// If messageId is provided, target that specific message
|
|
161
|
+
if (messageId) {
|
|
162
|
+
const messageIndex = newMessages.findIndex((msg) => msg.id === messageId);
|
|
163
|
+
if (messageIndex !== -1) {
|
|
164
|
+
const toolBlockIndex = newMessages[messageIndex].blocks.findIndex((block) => block.type === "tool" && block.id === id);
|
|
165
|
+
if (toolBlockIndex !== -1) {
|
|
166
|
+
const toolBlock = newMessages[messageIndex].blocks[toolBlockIndex];
|
|
167
|
+
if (toolBlock.type === "tool") {
|
|
168
|
+
if (parameters !== undefined)
|
|
169
|
+
toolBlock.parameters = parameters;
|
|
170
|
+
if (result !== undefined)
|
|
171
|
+
toolBlock.result = result;
|
|
172
|
+
if (shortResult !== undefined)
|
|
173
|
+
toolBlock.shortResult = shortResult;
|
|
174
|
+
if (startLineNumber !== undefined)
|
|
175
|
+
toolBlock.startLineNumber = startLineNumber;
|
|
176
|
+
if (images !== undefined)
|
|
177
|
+
toolBlock.images = images;
|
|
178
|
+
if (success !== undefined)
|
|
179
|
+
toolBlock.success = success;
|
|
180
|
+
if (error !== undefined)
|
|
181
|
+
toolBlock.error = error;
|
|
182
|
+
if (stage !== undefined)
|
|
183
|
+
toolBlock.stage = stage;
|
|
184
|
+
if (compactParams !== undefined)
|
|
185
|
+
toolBlock.compactParams = compactParams;
|
|
186
|
+
if (parametersChunk !== undefined)
|
|
187
|
+
toolBlock.parametersChunk = parametersChunk;
|
|
188
|
+
if (isManuallyBackgrounded !== undefined)
|
|
189
|
+
toolBlock.isManuallyBackgrounded = isManuallyBackgrounded;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return newMessages;
|
|
194
|
+
}
|
|
195
|
+
// Find the last assistant or user message
|
|
129
196
|
for (let i = newMessages.length - 1; i >= 0; i--) {
|
|
130
|
-
if (newMessages[i].role === "assistant") {
|
|
197
|
+
if (newMessages[i].role === "assistant" || newMessages[i].role === "user") {
|
|
131
198
|
const toolBlockIndex = newMessages[i].blocks.findIndex((block) => block.type === "tool" && block.id === id);
|
|
132
199
|
if (toolBlockIndex !== -1) {
|
|
133
200
|
const toolBlock = newMessages[i].blocks[toolBlockIndex];
|
|
@@ -155,9 +222,10 @@ export const updateToolBlockInMessage = ({ messages, id, parameters, result, suc
|
|
|
155
222
|
if (isManuallyBackgrounded !== undefined)
|
|
156
223
|
toolBlock.isManuallyBackgrounded = isManuallyBackgrounded;
|
|
157
224
|
}
|
|
225
|
+
break; // Found and updated, stop searching
|
|
158
226
|
}
|
|
159
|
-
else {
|
|
160
|
-
// If existing block not found, create new one
|
|
227
|
+
else if (newMessages[i].role === "assistant") {
|
|
228
|
+
// If existing block not found in assistant message, create new one
|
|
161
229
|
// This handles cases where we're streaming tool parameters before execution
|
|
162
230
|
newMessages[i].blocks.push({
|
|
163
231
|
type: "tool",
|
|
@@ -175,8 +243,8 @@ export const updateToolBlockInMessage = ({ messages, id, parameters, result, suc
|
|
|
175
243
|
parametersChunk: parametersChunk,
|
|
176
244
|
isManuallyBackgrounded: isManuallyBackgrounded,
|
|
177
245
|
});
|
|
246
|
+
break; // Created and added, stop searching
|
|
178
247
|
}
|
|
179
|
-
break;
|
|
180
248
|
}
|
|
181
249
|
}
|
|
182
250
|
return newMessages;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-agent-sdk",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.7",
|
|
4
4
|
"description": "SDK for building AI-powered development tools and agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -34,9 +34,11 @@
|
|
|
34
34
|
"fuzzysort": "^3.1.0",
|
|
35
35
|
"glob": "^13.0.0",
|
|
36
36
|
"minimatch": "^10.0.3",
|
|
37
|
-
"openai": "^5.12.2"
|
|
37
|
+
"openai": "^5.12.2",
|
|
38
|
+
"turndown": "^7.2.2"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
41
|
+
"@types/turndown": "^5.0.6",
|
|
40
42
|
"@vitest/coverage-v8": "^4.0.18",
|
|
41
43
|
"rimraf": "^6.1.2",
|
|
42
44
|
"tsc-alias": "^1.8.16",
|
package/src/agent.ts
CHANGED
|
@@ -35,6 +35,8 @@ import { LiveConfigManager } from "./managers/liveConfigManager.js";
|
|
|
35
35
|
import { configValidator } from "./utils/configValidator.js";
|
|
36
36
|
import { SkillManager } from "./managers/skillManager.js";
|
|
37
37
|
import { TaskManager } from "./services/taskManager.js";
|
|
38
|
+
import { btw } from "./services/aiService.js";
|
|
39
|
+
import { convertMessagesForAPI } from "./utils/convertMessagesForAPI.js";
|
|
38
40
|
import { InitializationService } from "./services/initializationService.js";
|
|
39
41
|
import { InteractionService } from "./services/interactionService.js";
|
|
40
42
|
import { ConfigurationService } from "./services/configurationService.js";
|
|
@@ -196,7 +198,7 @@ export class Agent {
|
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
public get latestTotalTokens(): number {
|
|
199
|
-
return this.messageManager.
|
|
201
|
+
return this.messageManager.getLatestTotalTokens();
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
/** Get working directory */
|
|
@@ -495,6 +497,22 @@ export class Agent {
|
|
|
495
497
|
this.messageManager.triggerShowRewind();
|
|
496
498
|
}
|
|
497
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Ask a side question without tool use
|
|
502
|
+
* @param question - The side question to ask
|
|
503
|
+
* @returns Promise that resolves to the AI's answer
|
|
504
|
+
*/
|
|
505
|
+
public async askBtw(question: string): Promise<string> {
|
|
506
|
+
const messages = convertMessagesForAPI(this.messageManager.getMessages());
|
|
507
|
+
const result = await btw({
|
|
508
|
+
gatewayConfig: this.getGatewayConfig(),
|
|
509
|
+
modelConfig: this.getModelConfig(),
|
|
510
|
+
messages,
|
|
511
|
+
question,
|
|
512
|
+
});
|
|
513
|
+
return result.content;
|
|
514
|
+
}
|
|
515
|
+
|
|
498
516
|
/**
|
|
499
517
|
* Send a message to the AI agent with optional images
|
|
500
518
|
*
|
package/src/constants/tools.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
|
|
2
2
|
export const BASH_TOOL_NAME = "Bash";
|
|
3
|
-
export const TASK_OUTPUT_TOOL_NAME = "TaskOutput";
|
|
4
3
|
export const TASK_STOP_TOOL_NAME = "TaskStop";
|
|
5
4
|
export const EDIT_TOOL_NAME = "Edit";
|
|
6
5
|
export const EXIT_PLAN_MODE_TOOL_NAME = "ExitPlanMode";
|
|
@@ -18,3 +17,4 @@ export const WRITE_TOOL_NAME = "Write";
|
|
|
18
17
|
export const CRON_CREATE_TOOL_NAME = "CronCreate";
|
|
19
18
|
export const CRON_DELETE_TOOL_NAME = "CronDelete";
|
|
20
19
|
export const CRON_LIST_TOOL_NAME = "CronList";
|
|
20
|
+
export const WEB_FETCH_TOOL_NAME = "WebFetch";
|
|
@@ -55,17 +55,8 @@ export class MemoryRuleManager {
|
|
|
55
55
|
|
|
56
56
|
this.state.rules = newRules;
|
|
57
57
|
const ruleCount = Object.keys(newRules).length;
|
|
58
|
+
// Removed verbose logging of all discovered rules
|
|
58
59
|
logger.debug(`Discovered ${ruleCount} modular memory rules`);
|
|
59
|
-
|
|
60
|
-
if (ruleCount > 0) {
|
|
61
|
-
logger.debug("Loaded memory rules:");
|
|
62
|
-
for (const [id, rule] of Object.entries(newRules)) {
|
|
63
|
-
const pathInfo = rule.metadata.paths
|
|
64
|
-
? `paths: ${JSON.stringify(rule.metadata.paths)}`
|
|
65
|
-
: "global (no path restriction)";
|
|
66
|
-
logger.debug(` - ${id} (${rule.source}): ${pathInfo}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
60
|
}
|
|
70
61
|
|
|
71
62
|
private async scanDirectory(
|
|
@@ -339,6 +339,25 @@ export class AIManager {
|
|
|
339
339
|
return;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
// Scan for file mentions in the last user message
|
|
343
|
+
if (recursionDepth === 0) {
|
|
344
|
+
const messages = this.messageManager.getMessages();
|
|
345
|
+
const lastMessage = messages[messages.length - 1];
|
|
346
|
+
if (lastMessage && lastMessage.role === "user") {
|
|
347
|
+
for (const block of lastMessage.blocks) {
|
|
348
|
+
if (block.type === "text") {
|
|
349
|
+
const content = block.content;
|
|
350
|
+
const fileMentionRegex = /(?:^|\s)@([\w.\-/]+)/g;
|
|
351
|
+
let match;
|
|
352
|
+
while ((match = fileMentionRegex.exec(content)) !== null) {
|
|
353
|
+
const filePath = match[1];
|
|
354
|
+
this.messageManager.touchFile(filePath);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
342
361
|
// Save session in each recursion to ensure message persistence
|
|
343
362
|
await this.messageManager.saveSession();
|
|
344
363
|
|
|
@@ -798,11 +817,12 @@ export class AIManager {
|
|
|
798
817
|
"Some tools were manually backgrounded, stopping recursion.",
|
|
799
818
|
);
|
|
800
819
|
} else if (!isCurrentlyAborted) {
|
|
801
|
-
// If response was truncated
|
|
802
|
-
if (result.finish_reason === "length"
|
|
820
|
+
// If response was truncated, add a hidden continuation message
|
|
821
|
+
if (result.finish_reason === "length") {
|
|
803
822
|
this.messageManager.addUserMessage({
|
|
804
823
|
content:
|
|
805
|
-
"
|
|
824
|
+
"Output token limit hit. Resume directly — no apology, no recap of what you were doing. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces.",
|
|
825
|
+
isMeta: true,
|
|
806
826
|
});
|
|
807
827
|
}
|
|
808
828
|
|
|
@@ -63,12 +63,11 @@ export class McpManager {
|
|
|
63
63
|
const config = await this.ensureConfigLoaded();
|
|
64
64
|
|
|
65
65
|
if (config && config.mcpServers) {
|
|
66
|
-
// Connect to all configured servers
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const success = await this.connectServer(serverName);
|
|
66
|
+
// Connect to all configured servers in background to avoid blocking agent initialization
|
|
67
|
+
Object.keys(config.mcpServers).forEach((serverName) => {
|
|
68
|
+
logger?.debug(`Connecting to MCP server: ${serverName}`);
|
|
69
|
+
this.connectServer(serverName)
|
|
70
|
+
.then((success) => {
|
|
72
71
|
if (success) {
|
|
73
72
|
logger?.debug(
|
|
74
73
|
`Successfully connected to MCP server: ${serverName}`,
|
|
@@ -76,18 +75,18 @@ export class McpManager {
|
|
|
76
75
|
} else {
|
|
77
76
|
logger?.warn(`Failed to connect to MCP server: ${serverName}`);
|
|
78
77
|
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
})
|
|
79
|
+
.catch((error) => {
|
|
80
|
+
logger?.error(
|
|
81
|
+
`Background connection to MCP server ${serverName} failed:`,
|
|
82
|
+
error,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
logger?.debug("MCP servers initialization
|
|
90
|
-
// Trigger state change callback after initialization
|
|
88
|
+
logger?.debug("MCP servers initialization started in background");
|
|
89
|
+
// Trigger state change callback after starting initialization
|
|
91
90
|
this.callbacks.onServersChange?.(this.getAllServers());
|
|
92
91
|
}
|
|
93
92
|
}
|
|
@@ -233,8 +232,30 @@ export class McpManager {
|
|
|
233
232
|
...(server.config.env || {}),
|
|
234
233
|
},
|
|
235
234
|
cwd: this.workdir, // Use the agent's workdir as the process working directory
|
|
235
|
+
stderr: "pipe", // Pipe stderr to capture it
|
|
236
236
|
});
|
|
237
237
|
|
|
238
|
+
// Handle stderr output
|
|
239
|
+
const stderr = transport.stderr;
|
|
240
|
+
if (stderr) {
|
|
241
|
+
let buffer = "";
|
|
242
|
+
stderr.on("data", (chunk: Buffer) => {
|
|
243
|
+
buffer += chunk.toString();
|
|
244
|
+
const lines = buffer.split("\n");
|
|
245
|
+
buffer = lines.pop() || "";
|
|
246
|
+
for (const line of lines) {
|
|
247
|
+
if (line.trim()) {
|
|
248
|
+
logger?.error(`[MCP Server ${name}] ${line}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
stderr.on("end", () => {
|
|
253
|
+
if (buffer.trim()) {
|
|
254
|
+
logger?.error(`[MCP Server ${name}] ${buffer}`);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
238
259
|
// Create client
|
|
239
260
|
const client = new Client(
|
|
240
261
|
{
|
|
@@ -8,12 +8,13 @@ import {
|
|
|
8
8
|
updateBangInMessage,
|
|
9
9
|
completeBangInMessage,
|
|
10
10
|
removeLastUserMessage,
|
|
11
|
+
addToolBlockToMessageInMessages,
|
|
11
12
|
UserMessageParams,
|
|
12
13
|
type AgentToolBlockUpdateParams,
|
|
13
14
|
generateMessageId,
|
|
14
15
|
} from "../utils/messageOperations.js";
|
|
15
16
|
import type { Message, Usage } from "../types/index.js";
|
|
16
|
-
import { join } from "path";
|
|
17
|
+
import { join, isAbsolute, relative } from "path";
|
|
17
18
|
import {
|
|
18
19
|
appendMessages,
|
|
19
20
|
createSession,
|
|
@@ -137,7 +138,7 @@ export class MessageManager {
|
|
|
137
138
|
return [...this._usages];
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
public
|
|
141
|
+
public getLatestTotalTokens(): number {
|
|
141
142
|
return this.latestTotalTokens;
|
|
142
143
|
}
|
|
143
144
|
|
|
@@ -172,12 +173,7 @@ export class MessageManager {
|
|
|
172
173
|
const filesInContext = this.getFilesInContext();
|
|
173
174
|
const activeRules = this.memoryRuleManager.getActiveRules(filesInContext);
|
|
174
175
|
if (activeRules.length > 0) {
|
|
175
|
-
|
|
176
|
-
`Active modular rules (${activeRules.length}): ${activeRules.map((r) => r.id).join(", ")}`,
|
|
177
|
-
);
|
|
178
|
-
if (combined) {
|
|
179
|
-
combined += "\n\n";
|
|
180
|
-
}
|
|
176
|
+
combined += "\n\n";
|
|
181
177
|
combined += activeRules.map((r) => r.content).join("\n\n");
|
|
182
178
|
}
|
|
183
179
|
}
|
|
@@ -221,9 +217,53 @@ export class MessageManager {
|
|
|
221
217
|
}
|
|
222
218
|
}
|
|
223
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Adds a file path to the set of files in context, normalizing it if necessary.
|
|
222
|
+
*/
|
|
223
|
+
public touchFile(filePath: string): void {
|
|
224
|
+
const normalizedPath = isAbsolute(filePath)
|
|
225
|
+
? relative(this.workdir, filePath)
|
|
226
|
+
: filePath;
|
|
227
|
+
this.filesInContext.add(normalizedPath);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Extracts and adds file paths from a message's tool blocks.
|
|
232
|
+
*/
|
|
233
|
+
private addPathsFromMessage(message: Message): void {
|
|
234
|
+
for (const block of message.blocks) {
|
|
235
|
+
if (block.type === "tool" && block.parameters) {
|
|
236
|
+
try {
|
|
237
|
+
const params = JSON.parse(block.parameters) as Record<
|
|
238
|
+
string,
|
|
239
|
+
unknown
|
|
240
|
+
>;
|
|
241
|
+
const paths = this.extractPathsFromParams(params);
|
|
242
|
+
for (const p of paths) {
|
|
243
|
+
this.touchFile(p);
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
// Ignore parse errors
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
224
252
|
public setMessages(messages: Message[]): void {
|
|
253
|
+
const oldLength = this.messages.length;
|
|
225
254
|
this.messages = [...messages];
|
|
226
|
-
|
|
255
|
+
|
|
256
|
+
// Incrementally add paths from new messages
|
|
257
|
+
const newMessages = messages.slice(oldLength);
|
|
258
|
+
for (const message of newMessages) {
|
|
259
|
+
this.addPathsFromMessage(message);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Also check if the last message was updated (common for tool blocks)
|
|
263
|
+
if (messages.length > 0 && messages.length === oldLength) {
|
|
264
|
+
this.addPathsFromMessage(messages[messages.length - 1]);
|
|
265
|
+
}
|
|
266
|
+
|
|
227
267
|
this.callbacks.onMessagesChange?.([...messages]);
|
|
228
268
|
}
|
|
229
269
|
|
|
@@ -296,7 +336,6 @@ export class MessageManager {
|
|
|
296
336
|
this.rootSessionId = sessionData.rootSessionId || sessionData.id;
|
|
297
337
|
this.parentSessionId = sessionData.parentSessionId;
|
|
298
338
|
this.setMessages([...sessionData.messages]);
|
|
299
|
-
this.updateFilesInContext(sessionData.messages);
|
|
300
339
|
this.setlatestTotalTokens(sessionData.metadata.latestTotalTokens);
|
|
301
340
|
|
|
302
341
|
// Set saved message count to the number of loaded messages since they're already saved
|
|
@@ -401,6 +440,20 @@ export class MessageManager {
|
|
|
401
440
|
// Note: Subagent-specific callbacks are now handled by SubagentManager
|
|
402
441
|
}
|
|
403
442
|
|
|
443
|
+
/**
|
|
444
|
+
* Add a tool block to a specific message by ID.
|
|
445
|
+
*/
|
|
446
|
+
public addToolBlockToMessage(
|
|
447
|
+
messageId: string,
|
|
448
|
+
params: Omit<AgentToolBlockUpdateParams, "id">,
|
|
449
|
+
): string {
|
|
450
|
+
const { messages: newMessages, toolBlockId } =
|
|
451
|
+
addToolBlockToMessageInMessages(this.messages, messageId, params);
|
|
452
|
+
this.setMessages(newMessages);
|
|
453
|
+
this.callbacks.onToolBlockUpdated?.({ ...params, id: toolBlockId });
|
|
454
|
+
return toolBlockId;
|
|
455
|
+
}
|
|
456
|
+
|
|
404
457
|
public addErrorBlock(error: string): void {
|
|
405
458
|
const newMessages = addErrorBlockToMessage({
|
|
406
459
|
messages: this.messages,
|
|
@@ -462,6 +515,19 @@ export class MessageManager {
|
|
|
462
515
|
// Set new message list
|
|
463
516
|
this.setMessages(newMessages);
|
|
464
517
|
|
|
518
|
+
// Reset and re-populate filesInContext
|
|
519
|
+
this.filesInContext.clear();
|
|
520
|
+
for (const message of this.messages) {
|
|
521
|
+
this.addPathsFromMessage(message);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Scan compressedContent for file mentions
|
|
525
|
+
const fileMentionRegex = /(?:^|\s)@([\w.\-/]+)/g;
|
|
526
|
+
let match;
|
|
527
|
+
while ((match = fileMentionRegex.exec(compressedContent)) !== null) {
|
|
528
|
+
this.touchFile(match[1]);
|
|
529
|
+
}
|
|
530
|
+
|
|
465
531
|
// Trigger compression callback
|
|
466
532
|
this.callbacks.onCompressBlockAdded?.(compressedContent);
|
|
467
533
|
}
|
|
@@ -794,34 +860,6 @@ export class MessageManager {
|
|
|
794
860
|
}
|
|
795
861
|
}
|
|
796
862
|
|
|
797
|
-
/**
|
|
798
|
-
* Updates the set of files mentioned in the conversation.
|
|
799
|
-
*/
|
|
800
|
-
private updateFilesInContext(messages: Message[]): void {
|
|
801
|
-
this.filesInContext.clear();
|
|
802
|
-
for (const message of messages) {
|
|
803
|
-
for (const block of message.blocks) {
|
|
804
|
-
if (block.type === "tool") {
|
|
805
|
-
// Extract file paths from common tool parameters
|
|
806
|
-
if (block.parameters) {
|
|
807
|
-
try {
|
|
808
|
-
const params = JSON.parse(block.parameters) as Record<
|
|
809
|
-
string,
|
|
810
|
-
unknown
|
|
811
|
-
>;
|
|
812
|
-
const paths = this.extractPathsFromParams(params);
|
|
813
|
-
for (const p of paths) {
|
|
814
|
-
this.filesInContext.add(p);
|
|
815
|
-
}
|
|
816
|
-
} catch {
|
|
817
|
-
// Ignore parse errors
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
863
|
private extractPathsFromParams(params: Record<string, unknown>): string[] {
|
|
826
864
|
const paths: string[] = [];
|
|
827
865
|
if (typeof params !== "object" || params === null) return paths;
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
stripEnvVars,
|
|
22
22
|
stripRedirections,
|
|
23
23
|
hasWriteRedirections,
|
|
24
|
-
|
|
24
|
+
isBashHeredocWrite,
|
|
25
25
|
getSmartPrefix,
|
|
26
26
|
DANGEROUS_COMMANDS,
|
|
27
27
|
} from "../utils/bashParser.js";
|
|
@@ -370,10 +370,10 @@ export class PermissionManager {
|
|
|
370
370
|
async checkPermission(
|
|
371
371
|
context: ToolPermissionContext,
|
|
372
372
|
): Promise<PermissionDecision> {
|
|
373
|
-
// 0. Intercept Bash
|
|
373
|
+
// 0. Intercept Bash EOF writing operations
|
|
374
374
|
if (context.toolName === BASH_TOOL_NAME && context.toolInput?.command) {
|
|
375
375
|
const command = String(context.toolInput.command);
|
|
376
|
-
if (
|
|
376
|
+
if (isBashHeredocWrite(command)) {
|
|
377
377
|
// Check if this specific command is explicitly allowed by a rule that includes redirection
|
|
378
378
|
const isExplicitlyAllowed = [
|
|
379
379
|
...this.instanceAllowedRules,
|
|
@@ -395,7 +395,7 @@ export class PermissionManager {
|
|
|
395
395
|
return {
|
|
396
396
|
behavior: "deny",
|
|
397
397
|
message:
|
|
398
|
-
"Bash-based file writing operations (e.g.,
|
|
398
|
+
"Bash-based file writing operations using heredocs (e.g., 'cat <<EOF > file') are not allowed. Please use the dedicated 'Write' or 'Edit' tools instead for file modifications.",
|
|
399
399
|
};
|
|
400
400
|
}
|
|
401
401
|
}
|
|
@@ -74,9 +74,11 @@ export class PluginManager {
|
|
|
74
74
|
|
|
75
75
|
const marketplaceService = new MarketplaceService();
|
|
76
76
|
|
|
77
|
-
// Trigger auto-update for marketplaces
|
|
77
|
+
// Trigger auto-update for marketplaces in the background
|
|
78
78
|
if (!process.env.VITEST) {
|
|
79
|
-
|
|
79
|
+
marketplaceService.autoUpdateAll().catch((error) => {
|
|
80
|
+
logger?.error("Background marketplace auto-update failed:", error);
|
|
81
|
+
});
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
let installedRegistry = await marketplaceService.getInstalledPlugins();
|