@codebolt/agent 6.1.20 → 6.1.21

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.
Files changed (41) hide show
  1. package/dist/processor-pieces/messageModifiers/argumentProcessorModifier.js +11 -15
  2. package/dist/processor-pieces/messageModifiers/atFileProcessorModifier.d.ts +0 -1
  3. package/dist/processor-pieces/messageModifiers/atFileProcessorModifier.js +16 -33
  4. package/dist/processor-pieces/messageModifiers/capabilityContextModifier.js +3 -2
  5. package/dist/processor-pieces/messageModifiers/chatHistoryMessageModifier.d.ts +7 -0
  6. package/dist/processor-pieces/messageModifiers/chatHistoryMessageModifier.js +135 -27
  7. package/dist/processor-pieces/messageModifiers/chatRecordingModifier.js +3 -3
  8. package/dist/processor-pieces/messageModifiers/contextAssemblyModifier.js +18 -12
  9. package/dist/processor-pieces/messageModifiers/directoryContextModifier.d.ts +1 -0
  10. package/dist/processor-pieces/messageModifiers/directoryContextModifier.js +15 -15
  11. package/dist/processor-pieces/messageModifiers/environmentContextModifier.d.ts +1 -0
  12. package/dist/processor-pieces/messageModifiers/environmentContextModifier.js +48 -2
  13. package/dist/processor-pieces/messageModifiers/ideContextModifier.js +3 -2
  14. package/dist/processor-pieces/messageModifiers/memoryImportModifier.js +9 -15
  15. package/dist/processor-pieces/messageModifiers/toolInjectionModifier.js +17 -20
  16. package/dist/processor-pieces/postInferenceProcessors/loopDetectionModifier.js +8 -19
  17. package/dist/processor-pieces/postToolCallProcessors/conversationCompactorModifier.d.ts +1 -1
  18. package/dist/processor-pieces/postToolCallProcessors/conversationCompactorModifier.js +15 -15
  19. package/dist/processor-pieces/postToolCallProcessors/shellProcessorModifier.js +3 -3
  20. package/dist/processor-pieces/preInferenceProcessors/chatCompressionModifier.js +2 -2
  21. package/dist/processor-pieces/utils/messageModifierHelper.js +3 -3
  22. package/dist/types/libFunctionTypes.d.ts +5 -0
  23. package/dist/unified/agent/agent.d.ts +10 -0
  24. package/dist/unified/agent/agent.js +166 -15
  25. package/dist/unified/agent/tools.d.ts +14 -0
  26. package/dist/unified/agent/tools.js +82 -51
  27. package/dist/unified/base/agentStep.d.ts +1 -0
  28. package/dist/unified/base/agentStep.js +39 -11
  29. package/dist/unified/base/initialPromptGenerator.d.ts +2 -0
  30. package/dist/unified/base/initialPromptGenerator.js +42 -19
  31. package/dist/unified/base/promptContext.d.ts +3 -0
  32. package/dist/unified/base/promptContext.js +193 -15
  33. package/dist/unified/base/responseExecutor.d.ts +6 -0
  34. package/dist/unified/base/responseExecutor.js +222 -58
  35. package/dist/unified/services/CompressionCoordinator.js +9 -9
  36. package/dist/unified/services/compaction/autoCompact.js +5 -5
  37. package/dist/unified/services/compaction/contextCollapse.js +2 -2
  38. package/dist/unified/services/compaction/reactiveCompact.js +5 -5
  39. package/dist/unified/types/libTypes.d.ts +6 -0
  40. package/dist/unified/utils/agentToolLoader.js +29 -24
  41. package/package.json +5 -1
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ArgumentProcessorModifier = void 0;
4
4
  const base_1 = require("../base");
5
+ const promptContext_1 = require("../../unified/base/promptContext");
5
6
  class ArgumentProcessorModifier extends base_1.BaseMessageModifier {
6
7
  constructor(options = {}) {
7
8
  super();
@@ -19,8 +20,7 @@ class ArgumentProcessorModifier extends base_1.BaseMessageModifier {
19
20
  if (!(invocation === null || invocation === void 0 ? void 0 : invocation.args)) {
20
21
  return Promise.resolve(createdMessage);
21
22
  }
22
- // Find the user message to append arguments to
23
- const userMessage = createdMessage.message.messages.find(msg => msg.role === 'user');
23
+ const userMessage = (0, promptContext_1.getCurrentUserMessage)(createdMessage);
24
24
  if (!userMessage || typeof userMessage.content !== 'string') {
25
25
  return Promise.resolve(createdMessage);
26
26
  }
@@ -37,23 +37,19 @@ class ArgumentProcessorModifier extends base_1.BaseMessageModifier {
37
37
  }
38
38
  appendContent = `\n\nArguments: ${argsToAppend}`;
39
39
  }
40
- // Update the user message with appended arguments
41
- const updatedMessages = createdMessage.message.messages.map(msg => {
42
- if (msg.role === 'user' && typeof msg.content === 'string' && msg === userMessage) {
43
- return {
44
- ...msg,
45
- content: msg.content + appendContent
46
- };
47
- }
48
- return msg;
49
- });
40
+ const updatedMessage = (0, promptContext_1.updateCurrentUserMessage)(createdMessage, (message) => ({
41
+ ...message,
42
+ content: typeof message.content === 'string'
43
+ ? message.content + appendContent
44
+ : message.content,
45
+ }));
50
46
  return Promise.resolve({
47
+ ...updatedMessage,
51
48
  message: {
52
- ...createdMessage.message,
53
- messages: updatedMessages
49
+ ...updatedMessage.message,
54
50
  },
55
51
  metadata: {
56
- ...createdMessage.metadata,
52
+ ...updatedMessage.metadata,
57
53
  argumentsProcessed: true,
58
54
  argumentsAppended: appendContent.trim()
59
55
  }
@@ -19,7 +19,6 @@ export declare class AtFileProcessorModifier extends BaseMessageModifier {
19
19
  private readManyFiles;
20
20
  private readFileContent;
21
21
  private getFolderStructure;
22
- private findLastUserMessage;
23
22
  private getProjectPath;
24
23
  private isReadableTextFile;
25
24
  private isLikelyBinaryFile;
@@ -41,6 +41,7 @@ const base_1 = require("../base");
41
41
  const fs = __importStar(require("node:fs/promises"));
42
42
  const path = __importStar(require("node:path"));
43
43
  const codeboltjs_1 = __importDefault(require("@codebolt/codeboltjs"));
44
+ const promptContext_1 = require("../../unified/base/promptContext");
44
45
  class AtFileProcessorModifier extends base_1.BaseMessageModifier {
45
46
  constructor(options = {}) {
46
47
  super();
@@ -74,39 +75,30 @@ class AtFileProcessorModifier extends base_1.BaseMessageModifier {
74
75
  if (!result.success) {
75
76
  return createdMessage;
76
77
  }
77
- // Update the user message with processed content
78
- const messages = [...createdMessage.message.messages];
79
- const lastUserMessageIndex = this.findLastUserMessage(messages);
80
- if (lastUserMessageIndex !== -1 && result.processedContent) {
81
- const lastUserMessage = messages[lastUserMessageIndex];
82
- if (lastUserMessage) {
83
- // Convert string content to array format if needed
84
- let currentContent;
85
- if (Array.isArray(lastUserMessage.content)) {
86
- currentContent = lastUserMessage.content;
87
- }
88
- else {
89
- // Convert string content to array format
90
- currentContent = [{ type: 'text', text: lastUserMessage.content }];
91
- }
92
- // Ensure processedContent is in array format
78
+ const currentUserMessage = (0, promptContext_1.getCurrentUserMessage)(createdMessage);
79
+ const updatedMessage = currentUserMessage && result.processedContent
80
+ ? (0, promptContext_1.updateCurrentUserMessage)(createdMessage, (message) => {
81
+ const currentContent = Array.isArray(message.content)
82
+ ? message.content
83
+ : [{ type: 'text', text: message.content }];
93
84
  const newContent = Array.isArray(result.processedContent)
94
85
  ? result.processedContent
95
86
  : [{ type: 'text', text: result.processedContent }];
96
- // Merge existing content with processed content
97
- messages[lastUserMessageIndex] = {
98
- role: lastUserMessage.role,
87
+ return {
88
+ ...message,
89
+ type: 'message',
90
+ role: 'user',
99
91
  content: [...currentContent, ...newContent]
100
92
  };
101
- }
102
- }
93
+ })
94
+ : createdMessage;
103
95
  return {
96
+ ...updatedMessage,
104
97
  message: {
105
- ...createdMessage.message,
106
- messages
98
+ ...updatedMessage.message,
107
99
  },
108
100
  metadata: {
109
- ...createdMessage.metadata,
101
+ ...updatedMessage.metadata,
110
102
  atFileProcessed: true,
111
103
  processedFiles: mentionedFiles,
112
104
  processedFolders: mentionedFolders,
@@ -348,15 +340,6 @@ class AtFileProcessorModifier extends base_1.BaseMessageModifier {
348
340
  }
349
341
  return lines.join('\n');
350
342
  }
351
- findLastUserMessage(messages) {
352
- for (let i = messages.length - 1; i >= 0; i--) {
353
- const message = messages[i];
354
- if (message && message.role === 'user') {
355
- return i;
356
- }
357
- }
358
- return -1;
359
- }
360
343
  async getProjectPath() {
361
344
  const effectiveProjectPath = process.env['CODEBOLT_EFFECTIVE_PROJECT_PATH'] || process.env['CODEBOLT_PROJECT_PATH'];
362
345
  if (effectiveProjectPath) {
@@ -36,10 +36,11 @@ class CapabilityContextModifier extends base_1.BaseMessageModifier {
36
36
  role: 'user',
37
37
  content: this.formatSelectedCapabilitiesContext(selectedCapabilities, selectedCapabilityInfo, skillContexts),
38
38
  };
39
+ const updatedMessage = (0, promptContext_1.appendUserContextMessage)(createdMessage, contextMessage);
39
40
  return {
40
- ...(0, promptContext_1.appendUserContextMessage)(createdMessage, contextMessage),
41
+ ...updatedMessage,
41
42
  metadata: {
42
- ...createdMessage.metadata,
43
+ ...updatedMessage.metadata,
43
44
  selectedCapabilities,
44
45
  selectedCapabilitiesProcessed: true,
45
46
  selectedCapabilityNames: skillContexts.map((context) => context.name),
@@ -15,4 +15,11 @@ export declare class ChatHistoryMessageModifier extends BaseMessageModifier {
15
15
  modify(originalRequest: FlatUserMessage, createdMessage: ProcessedMessage): Promise<ProcessedMessage>;
16
16
  private getChatHistory;
17
17
  setMaxHistoryMessages(max: number): void;
18
+ private addMissingToolResponses;
19
+ private getStandaloneToolCall;
20
+ private getFunctionCallOutputId;
21
+ private getAssistantToolCallId;
22
+ private getAssistantToolCallName;
23
+ private getAssistantToolCallArguments;
24
+ private buildMissingToolResponseContent;
18
25
  }
@@ -25,11 +25,12 @@ class ChatHistoryMessageModifier extends base_1.BaseMessageModifier {
25
25
  if (!chatHistory || chatHistory.length === 0) {
26
26
  return createdMessage;
27
27
  }
28
+ const updatedMessage = (0, promptContext_1.prependTranscriptMessages)(createdMessage, chatHistory);
28
29
  // Directly use the chat history messages - they're already in MessageObject format
29
30
  return {
30
- ...(0, promptContext_1.prependTranscriptMessages)(createdMessage, chatHistory),
31
+ ...updatedMessage,
31
32
  metadata: {
32
- ...createdMessage.metadata,
33
+ ...updatedMessage.metadata,
33
34
  chatHistoryAdded: true,
34
35
  chatHistoryCount: chatHistory.length,
35
36
  threadId: originalRequest.threadId
@@ -47,40 +48,20 @@ class ChatHistoryMessageModifier extends base_1.BaseMessageModifier {
47
48
  try {
48
49
  const response = await codeboltjs_1.default.chat.getChatHistory(threadId);
49
50
  // Check if response has messages array
50
- if (!(response === null || response === void 0 ? void 0 : response.chats) || !((_a = response === null || response === void 0 ? void 0 : response.chats) === null || _a === void 0 ? void 0 : _a.messages) || !Array.isArray((_b = response === null || response === void 0 ? void 0 : response.chats) === null || _b === void 0 ? void 0 : _b.messages) || ((_c = response === null || response === void 0 ? void 0 : response.chats) === null || _c === void 0 ? void 0 : _c.messages.length) === 0) {
51
+ if (!(response === null || response === void 0 ? void 0 : response.chats) || !((_a = response === null || response === void 0 ? void 0 : response.chats) === null || _a === void 0 ? void 0 : _a.input) || !Array.isArray((_b = response === null || response === void 0 ? void 0 : response.chats) === null || _b === void 0 ? void 0 : _b.input) || ((_c = response === null || response === void 0 ? void 0 : response.chats) === null || _c === void 0 ? void 0 : _c.input.length) === 0) {
51
52
  return [];
52
53
  }
53
- let historyMessages = (_d = response === null || response === void 0 ? void 0 : response.chats) === null || _d === void 0 ? void 0 : _d.messages;
54
+ let historyMessages = (_d = response === null || response === void 0 ? void 0 : response.chats) === null || _d === void 0 ? void 0 : _d.input;
54
55
  // Thread filtering is now handled by the API directly via threadId parameter
55
56
  // Filter out system messages if not included
56
57
  if (!this.includeSystemMessages) {
57
58
  historyMessages = historyMessages.filter((msg) => msg.role !== 'system');
58
59
  }
59
- // Limit the number of messages
60
- // will do it later
61
- if (false) {
60
+ historyMessages = historyMessages.filter((message) => !(0, promptContext_1.isGeneratedUserContextMessage)(message));
61
+ if (this.maxHistoryMessages > 0 && historyMessages.length > this.maxHistoryMessages) {
62
62
  historyMessages = historyMessages.slice(-this.maxHistoryMessages);
63
63
  }
64
- // Check if the last message is an assistant message with tool_calls
65
- // If so, add tool response messages for each tool_call
66
- const lastMessage = historyMessages[historyMessages.length - 1];
67
- if (lastMessage &&
68
- lastMessage.role === 'assistant' &&
69
- lastMessage.tool_calls &&
70
- Array.isArray(lastMessage.tool_calls) &&
71
- lastMessage.tool_calls.length > 0) {
72
- // Add tool response messages for each tool_call
73
- const toolResponseMessages = [];
74
- for (const toolCall of lastMessage.tool_calls) {
75
- toolResponseMessages.push({
76
- role: 'tool',
77
- content: `Tool call "${toolCall.function.name}" was not executed. This appears to be from a previous incomplete conversation. The tool was called with arguments: ${toolCall.function.arguments}`,
78
- tool_call_id: toolCall.id
79
- });
80
- }
81
- // Return original messages + tool responses + context message
82
- return [...historyMessages, ...toolResponseMessages];
83
- }
64
+ historyMessages = this.addMissingToolResponses(historyMessages);
84
65
  // Add a system message to indicate this is previous conversation
85
66
  const contextMessage = {
86
67
  role: 'user',
@@ -98,5 +79,132 @@ class ChatHistoryMessageModifier extends base_1.BaseMessageModifier {
98
79
  setMaxHistoryMessages(max) {
99
80
  this.maxHistoryMessages = max;
100
81
  }
82
+ addMissingToolResponses(historyMessages) {
83
+ const existingToolResponseIds = new Set();
84
+ for (const message of historyMessages) {
85
+ const functionCallOutputId = this.getFunctionCallOutputId(message);
86
+ if (functionCallOutputId) {
87
+ existingToolResponseIds.add(functionCallOutputId);
88
+ }
89
+ if (typeof message.tool_call_id === 'string') {
90
+ existingToolResponseIds.add(message.tool_call_id);
91
+ }
92
+ }
93
+ const repairedMessages = [];
94
+ for (const message of historyMessages) {
95
+ repairedMessages.push(message);
96
+ const standaloneToolCall = this.getStandaloneToolCall(message);
97
+ if (standaloneToolCall && !existingToolResponseIds.has(standaloneToolCall.id)) {
98
+ repairedMessages.push({
99
+ type: 'function_call_output',
100
+ call_id: standaloneToolCall.id,
101
+ output: this.buildMissingToolResponseContent(standaloneToolCall.name, standaloneToolCall.arguments),
102
+ status: 'completed',
103
+ });
104
+ existingToolResponseIds.add(standaloneToolCall.id);
105
+ }
106
+ const assistantToolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
107
+ for (const toolCall of assistantToolCalls) {
108
+ const toolCallId = this.getAssistantToolCallId(toolCall);
109
+ if (!toolCallId || existingToolResponseIds.has(toolCallId)) {
110
+ continue;
111
+ }
112
+ const toolName = this.getAssistantToolCallName(toolCall);
113
+ const toolArguments = this.getAssistantToolCallArguments(toolCall);
114
+ repairedMessages.push({
115
+ role: 'tool',
116
+ content: this.buildMissingToolResponseContent(toolName, toolArguments),
117
+ tool_call_id: toolCallId,
118
+ });
119
+ existingToolResponseIds.add(toolCallId);
120
+ }
121
+ }
122
+ return repairedMessages;
123
+ }
124
+ getStandaloneToolCall(message) {
125
+ var _a, _b;
126
+ const typedMessage = message;
127
+ if (typedMessage.type !== 'function_call') {
128
+ return null;
129
+ }
130
+ const id = typeof typedMessage.call_id === 'string'
131
+ ? typedMessage.call_id
132
+ : typeof typedMessage.id === 'string'
133
+ ? typedMessage.id
134
+ : '';
135
+ if (!id) {
136
+ return null;
137
+ }
138
+ const name = typeof typedMessage.name === 'string'
139
+ ? typedMessage.name
140
+ : typeof ((_a = typedMessage.function) === null || _a === void 0 ? void 0 : _a.name) === 'string'
141
+ ? typedMessage.function.name
142
+ : 'unknown';
143
+ const toolArguments = typeof typedMessage.arguments === 'string'
144
+ ? typedMessage.arguments
145
+ : typeof ((_b = typedMessage.function) === null || _b === void 0 ? void 0 : _b.arguments) === 'string'
146
+ ? typedMessage.function.arguments
147
+ : '';
148
+ return {
149
+ id,
150
+ name,
151
+ arguments: toolArguments,
152
+ };
153
+ }
154
+ getFunctionCallOutputId(message) {
155
+ const typedMessage = message;
156
+ if (typedMessage.type === 'function_call_output' &&
157
+ typeof typedMessage.call_id === 'string') {
158
+ return typedMessage.call_id;
159
+ }
160
+ return null;
161
+ }
162
+ getAssistantToolCallId(toolCall) {
163
+ if (!toolCall || typeof toolCall !== 'object') {
164
+ return null;
165
+ }
166
+ const typedToolCall = toolCall;
167
+ if (typeof typedToolCall.id === 'string') {
168
+ return typedToolCall.id;
169
+ }
170
+ if (typeof typedToolCall.call_id === 'string') {
171
+ return typedToolCall.call_id;
172
+ }
173
+ return null;
174
+ }
175
+ getAssistantToolCallName(toolCall) {
176
+ var _a;
177
+ if (!toolCall || typeof toolCall !== 'object') {
178
+ return 'unknown';
179
+ }
180
+ const typedToolCall = toolCall;
181
+ if (typeof typedToolCall.name === 'string') {
182
+ return typedToolCall.name;
183
+ }
184
+ if (typeof ((_a = typedToolCall.function) === null || _a === void 0 ? void 0 : _a.name) === 'string') {
185
+ return typedToolCall.function.name;
186
+ }
187
+ return 'unknown';
188
+ }
189
+ getAssistantToolCallArguments(toolCall) {
190
+ var _a;
191
+ if (!toolCall || typeof toolCall !== 'object') {
192
+ return '';
193
+ }
194
+ const typedToolCall = toolCall;
195
+ if (typeof typedToolCall.arguments === 'string') {
196
+ return typedToolCall.arguments;
197
+ }
198
+ if (typeof ((_a = typedToolCall.function) === null || _a === void 0 ? void 0 : _a.arguments) === 'string') {
199
+ return typedToolCall.function.arguments;
200
+ }
201
+ return '';
202
+ }
203
+ buildMissingToolResponseContent(toolName, toolArguments) {
204
+ if (toolName.includes('attempt_completion')) {
205
+ return 'The user is satisfied with the result.';
206
+ }
207
+ return `Tool call "${toolName}" was not executed. This appears to be from a previous incomplete conversation. The tool was called with arguments: ${toolArguments}`;
208
+ }
101
209
  }
102
210
  exports.ChatHistoryMessageModifier = ChatHistoryMessageModifier;
@@ -107,12 +107,12 @@ class ChatRecordingModifier extends base_1.BaseMessageModifier {
107
107
  }
108
108
  const timestamp = new Date().toISOString();
109
109
  // Record each message
110
- for (const message of createdMessage.message.messages) {
110
+ for (const message of createdMessage.message.input) {
111
111
  const record = {
112
112
  timestamp,
113
113
  messageId: (_a = createdMessage.metadata) === null || _a === void 0 ? void 0 : _a['messageId'],
114
114
  threadId: (_b = createdMessage.metadata) === null || _b === void 0 ? void 0 : _b['threadId'],
115
- role: message.role,
115
+ role: message.role || 'assistant',
116
116
  content: typeof message.content === 'string' ? message.content : JSON.stringify(message.content)
117
117
  };
118
118
  if (this.options.includeMetadata) {
@@ -129,7 +129,7 @@ class ChatRecordingModifier extends base_1.BaseMessageModifier {
129
129
  }
130
130
  }
131
131
  catch (error) {
132
- console.error('Error recording chat messages:', error);
132
+ console.error('Error recording chat input:', error);
133
133
  }
134
134
  }
135
135
  async writeRecord(record) {
@@ -45,6 +45,7 @@ class ContextAssemblyModifier extends base_1.BaseMessageModifier {
45
45
  return createdMessage;
46
46
  }
47
47
  const contextMessage = {
48
+ type: 'message',
48
49
  role: this.options.messageRole,
49
50
  content: contextContent,
50
51
  };
@@ -54,7 +55,7 @@ class ContextAssemblyModifier extends base_1.BaseMessageModifier {
54
55
  return {
55
56
  ...updatedMessage,
56
57
  metadata: {
57
- ...createdMessage.metadata,
58
+ ...updatedMessage.metadata,
58
59
  contextAssemblyAdded: true,
59
60
  contextAssemblyStats: {
60
61
  totalTokens: context.total_tokens,
@@ -76,7 +77,7 @@ class ContextAssemblyModifier extends base_1.BaseMessageModifier {
76
77
  scope_variables: { ...this.options.scopeVariables },
77
78
  };
78
79
  if (this.options.includeUserInput) {
79
- const userMessage = createdMessage.message.messages.find(msg => msg.role === 'user');
80
+ const userMessage = (0, promptContext_1.getCurrentUserMessage)(createdMessage);
80
81
  if (userMessage && typeof userMessage.content === 'string') {
81
82
  request.input = userMessage.content;
82
83
  }
@@ -137,7 +138,7 @@ class RuleBasedContextModifier extends base_1.BaseMessageModifier {
137
138
  var _a;
138
139
  try {
139
140
  // First evaluate rules to determine which memories to include
140
- const userMessage = createdMessage.message.messages.find(msg => msg.role === 'user');
141
+ const userMessage = (0, promptContext_1.getCurrentUserMessage)(createdMessage);
141
142
  const input = userMessage && typeof userMessage.content === 'string' ? userMessage.content : undefined;
142
143
  const request = {
143
144
  scope_variables: { ...this.options.scopeVariables },
@@ -181,14 +182,16 @@ class RuleBasedContextModifier extends base_1.BaseMessageModifier {
181
182
  role: this.options.messageRole,
182
183
  content: contextContent,
183
184
  };
184
- const messages = [...createdMessage.message.messages, contextMessage];
185
+ const updatedMessage = this.options.messageRole === 'user'
186
+ ? (0, promptContext_1.appendUserContextMessage)(createdMessage, contextMessage)
187
+ : (0, promptContext_1.appendSystemContextMessage)(createdMessage, contextMessage);
185
188
  return {
189
+ ...updatedMessage,
186
190
  message: {
187
- ...createdMessage.message,
188
- messages,
191
+ ...updatedMessage.message,
189
192
  },
190
193
  metadata: {
191
- ...createdMessage.metadata,
194
+ ...updatedMessage.metadata,
192
195
  ruleBasedContextAdded: true,
193
196
  ruleEvaluation: {
194
197
  matchedRules: ruleResult.data.matched_rules,
@@ -241,7 +244,7 @@ class MemoryTypeContextModifier extends base_1.BaseMessageModifier {
241
244
  additionalVariables = this.resolveVariables(requiredVarsResponse.data, createdMessage);
242
245
  }
243
246
  }
244
- const userMessage = createdMessage.message.messages.find(msg => msg.role === 'user');
247
+ const userMessage = (0, promptContext_1.getCurrentUserMessage)(createdMessage);
245
248
  const input = userMessage && typeof userMessage.content === 'string' ? userMessage.content : undefined;
246
249
  const request = {
247
250
  scope_variables: { ...this.options.scopeVariables },
@@ -268,17 +271,20 @@ class MemoryTypeContextModifier extends base_1.BaseMessageModifier {
268
271
  return createdMessage;
269
272
  }
270
273
  const contextMessage = {
274
+ type: 'message',
271
275
  role: this.options.messageRole,
272
276
  content: contextContent,
273
277
  };
274
- const messages = [...createdMessage.message.messages, contextMessage];
278
+ const updatedMessage = this.options.messageRole === 'user'
279
+ ? (0, promptContext_1.appendUserContextMessage)(createdMessage, contextMessage)
280
+ : (0, promptContext_1.appendSystemContextMessage)(createdMessage, contextMessage);
275
281
  return {
282
+ ...updatedMessage,
276
283
  message: {
277
- ...createdMessage.message,
278
- messages,
284
+ ...updatedMessage.message,
279
285
  },
280
286
  metadata: {
281
- ...createdMessage.metadata,
287
+ ...updatedMessage.metadata,
282
288
  memoryTypeContextAdded: true,
283
289
  memoryNames: this.options.memoryNames,
284
290
  contextAssemblyStats: {
@@ -24,6 +24,7 @@ export declare class DirectoryContextModifier extends BaseMessageModifier {
24
24
  * @returns A promise resolving to the formatted folder structure string.
25
25
  */
26
26
  private getFolderStructure;
27
+ private isTruncated;
27
28
  private readFullStructure;
28
29
  /**
29
30
  * Reads the directory structure using BFS, respecting maxItems.
@@ -106,10 +106,11 @@ ${folderStructure}`;
106
106
  role: 'user', // Note: gemini-cli adds this as user message, not system
107
107
  content: directoryContext
108
108
  };
109
+ const updatedMessage = (0, promptContext_1.appendUserContextMessage)(createdMessage, contextMessage);
109
110
  return Promise.resolve({
110
- ...(0, promptContext_1.appendUserContextMessage)(createdMessage, contextMessage),
111
+ ...updatedMessage,
111
112
  metadata: {
112
- ...createdMessage.metadata,
113
+ ...updatedMessage.metadata,
113
114
  directoryContextAdded: true,
114
115
  workspaceDirectories
115
116
  }
@@ -226,20 +227,8 @@ ${folderStructure}`;
226
227
  const structureLines = [];
227
228
  // Pass true for isRoot for the initial call
228
229
  this.formatStructure(structureRoot, '', true, true, structureLines);
229
- // 3. Build the final output string
230
- function isTruncated(node) {
231
- if (node.hasMoreFiles || node.hasMoreSubfolders || node.isIgnored) {
232
- return true;
233
- }
234
- for (const sub of node.subFolders) {
235
- if (isTruncated(sub)) {
236
- return true;
237
- }
238
- }
239
- return false;
240
- }
241
230
  let summary = `Showing up to ${mergedOptions.maxItems} items (files + folders).`;
242
- if (isTruncated(structureRoot)) {
231
+ if (this.isTruncated(structureRoot)) {
243
232
  summary += ` Folders or files indicated with ${TRUNCATION_INDICATOR} contain more items not shown, were ignored, or the display limit (${mergedOptions.maxItems} items) was reached.`;
244
233
  }
245
234
  return `${summary}\n\n${resolvedPath}${path.sep}\n${structureLines.join('\n')}`;
@@ -249,6 +238,17 @@ ${folderStructure}`;
249
238
  return `Error processing directory "${resolvedPath}": ${getErrorMessage(error)}`;
250
239
  }
251
240
  }
241
+ isTruncated(node) {
242
+ if (node.hasMoreFiles || node.hasMoreSubfolders || node.isIgnored) {
243
+ return true;
244
+ }
245
+ for (const subFolder of node.subFolders) {
246
+ if (this.isTruncated(subFolder)) {
247
+ return true;
248
+ }
249
+ }
250
+ return false;
251
+ }
252
252
  async readFullStructure(rootPath, options) {
253
253
  const rootName = path.basename(rootPath);
254
254
  const rootNode = {
@@ -14,6 +14,7 @@ export declare class EnvironmentContextModifier extends BaseMessageModifier {
14
14
  constructor(options?: EnvironmentContextOptions);
15
15
  modify(originalRequest: FlatUserMessage, createdMessage: ProcessedMessage): Promise<ProcessedMessage>;
16
16
  private formatMentionedEnvironments;
17
+ private formatPendingAsyncTasks;
17
18
  private readProjectAgentMd;
18
19
  private readAgentInstructionFile;
19
20
  private generateFullContext;
@@ -102,6 +102,7 @@ Effective project directory for this task: ${currentDir}
102
102
  ${baseProjectPath ? `CodeBolt base project directory: ${baseProjectPath}
103
103
  CodeBolt app-level configuration, providers, plugins, and .codebolt data are loaded from the base project directory. Code changes for the task should still target the effective project directory unless the user explicitly says otherwise.` : ''}
104
104
  ${directoryListing}
105
+ Async task policy: long-running commands and child threads are tracked as async tasks with execution_mode foreground_scoped, auto_scoped, background_scoped, or background_detached. Scoped tasks are owned by the current agent run and are stopped if the user force-stops the agent. Before completing, use async_task_list to inspect unresolved scoped work and async_task_control to wait, stop, or explicitly detach each running task. background_detached tasks survive user force-stop and natural completion, so detach only intentional background services such as dev servers.
105
106
  `.trim();
106
107
  // Prepare context parts (same as gemini-cli)
107
108
  const contextParts = [environmentContext];
@@ -119,6 +120,10 @@ ${directoryListing}
119
120
  if (mentionedEnvironmentContext) {
120
121
  contextParts.push(mentionedEnvironmentContext);
121
122
  }
123
+ const asyncTaskContext = await this.formatPendingAsyncTasks();
124
+ if (asyncTaskContext) {
125
+ contextParts.push(asyncTaskContext);
126
+ }
122
127
  // Add full file context if enabled (just like gemini-cli does)
123
128
  if (this.options.enableFullContext) {
124
129
  try {
@@ -137,10 +142,11 @@ ${directoryListing}
137
142
  role: 'user',
138
143
  content: finalContent
139
144
  };
145
+ const updatedMessage = (0, promptContext_1.appendUserContextMessage)(createdMessage, contextMessage);
140
146
  return Promise.resolve({
141
- ...(0, promptContext_1.appendUserContextMessage)(createdMessage, contextMessage),
147
+ ...updatedMessage,
142
148
  metadata: {
143
- ...createdMessage.metadata,
149
+ ...updatedMessage.metadata,
144
150
  environmentContextAdded: true,
145
151
  projectAgentMdContextAdded: Boolean(agentMdContext),
146
152
  fullContextAdded: this.options.enableFullContext,
@@ -187,6 +193,7 @@ ${directoryListing}
187
193
  'The user explicitly mentioned these environments with #. Treat them as routing context, not as an automatic active-environment selection.',
188
194
  'If the user asks to run, start, delegate, create, or continue work in one of these environments, first inspect this mentioned environment list and choose the matching environment.',
189
195
  'Use the thread tool `thread_create_background` to create a background thread in the selected remote environment. Pass the selected environment object in `environment`, set `isRemoteTask: true`, and put the requested work in `userMessage` or `task`.',
196
+ 'If the next step depends on that child thread finishing, use `async_task_control` with taskId `thread:<threadId>` and action `wait` before continuing.',
190
197
  'If `thread_create_background` is not available in the current tool list, first use the tool search capability, such as `tool_search`, to find the thread/background-thread creation tool and then use the matching tool.',
191
198
  'If multiple mentioned environments could match and the user did not specify which one, ask a brief clarification before creating the background thread.',
192
199
  'Do not call environment management tools or change the active environment just because an environment was mentioned.',
@@ -194,6 +201,45 @@ ${directoryListing}
194
201
  '</mentioned-environments>',
195
202
  ].join('\n');
196
203
  }
204
+ async formatPendingAsyncTasks() {
205
+ try {
206
+ const response = await codeboltjs_1.default.asyncTask.listTasks({
207
+ scope: 'thread',
208
+ });
209
+ const tasks = Array.isArray(response === null || response === void 0 ? void 0 : response.tasks) ? response.tasks : [];
210
+ const unresolvedTasks = tasks.filter((task) => task &&
211
+ task.executionMode !== 'background_detached' &&
212
+ !['completed', 'failed', 'stopped'].includes(task.status));
213
+ const detachedTasks = tasks.filter((task) => task &&
214
+ task.executionMode === 'background_detached' &&
215
+ !['completed', 'failed', 'stopped'].includes(task.status));
216
+ if (unresolvedTasks.length === 0 && detachedTasks.length === 0) {
217
+ return null;
218
+ }
219
+ const sections = [];
220
+ if (unresolvedTasks.length > 0) {
221
+ sections.push([
222
+ '<pending_async_tasks>',
223
+ 'These scoped async tasks are still unresolved for this thread. Before completing, choose wait, stop, or detach for each task using async_task_control. Detach only intentional background services that should survive agent stop.',
224
+ JSON.stringify(unresolvedTasks, null, 2),
225
+ '</pending_async_tasks>',
226
+ ].join('\n'));
227
+ }
228
+ if (detachedTasks.length > 0) {
229
+ sections.push([
230
+ '<detached_async_tasks>',
231
+ 'These background_detached async tasks are still running in the background for this thread. They do not block completion and survive user force-stop. Stop them with async_task_control if they are no longer needed.',
232
+ JSON.stringify(detachedTasks, null, 2),
233
+ '</detached_async_tasks>',
234
+ ].join('\n'));
235
+ }
236
+ return sections.join('\n\n');
237
+ }
238
+ catch (error) {
239
+ console.error('Error reading pending async tasks:', error);
240
+ return null;
241
+ }
242
+ }
197
243
  async readProjectAgentMd(basePath, title = 'Project Agent Instructions') {
198
244
  const instructionFiles = [
199
245
  path.join(basePath, '.codebolt', 'agent.md'),
@@ -34,10 +34,11 @@ class IdeContextModifier extends base_1.BaseMessageModifier {
34
34
  this.lastSentIdeContext = newIdeContext;
35
35
  }
36
36
  this.forceFullContext = false;
37
+ const updatedMessage = (0, promptContext_1.appendSystemContextMessage)(createdMessage, ideContextMessage);
37
38
  return Promise.resolve({
38
- ...(0, promptContext_1.appendSystemContextMessage)(createdMessage, ideContextMessage),
39
+ ...updatedMessage,
39
40
  metadata: {
40
- ...createdMessage.metadata,
41
+ ...updatedMessage.metadata,
41
42
  ideContextAdded: true,
42
43
  ideContextType: this.forceFullContext ? 'full' : 'incremental'
43
44
  }