agentcrew-ai 0.8.4__py3-none-any.whl → 0.8.6__py3-none-any.whl
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.
- AgentCrew/__init__.py +1 -1
- AgentCrew/modules/a2a/task_manager.py +154 -30
- AgentCrew/modules/agents/local_agent.py +9 -9
- AgentCrew/modules/browser_automation/element_extractor.py +1 -1
- AgentCrew/modules/browser_automation/service.py +17 -7
- AgentCrew/modules/chat/message/command_processor.py +4 -2
- AgentCrew/modules/chat/message/conversation.py +1 -0
- AgentCrew/modules/chat/message/handler.py +3 -6
- AgentCrew/modules/command_execution/constants.py +2 -2
- AgentCrew/modules/command_execution/service.py +37 -83
- AgentCrew/modules/command_execution/tool.py +5 -7
- AgentCrew/modules/command_execution/types.py +3 -4
- AgentCrew/modules/console/command_handlers.py +2 -2
- AgentCrew/modules/console/confirmation_handler.py +83 -38
- AgentCrew/modules/console/console_ui.py +27 -23
- AgentCrew/modules/console/diff_display.py +203 -0
- AgentCrew/modules/console/display_handlers.py +3 -0
- AgentCrew/modules/console/input_handler.py +3 -4
- AgentCrew/modules/console/tool_display.py +35 -4
- AgentCrew/modules/console/ui_effects.py +30 -14
- AgentCrew/modules/custom_llm/deepinfra_service.py +20 -19
- AgentCrew/modules/custom_llm/github_copilot_service.py +157 -2
- AgentCrew/modules/custom_llm/service.py +1 -9
- AgentCrew/modules/llm/constants.py +24 -3
- {agentcrew_ai-0.8.4.dist-info → agentcrew_ai-0.8.6.dist-info}/METADATA +2 -2
- {agentcrew_ai-0.8.4.dist-info → agentcrew_ai-0.8.6.dist-info}/RECORD +30 -30
- AgentCrew/modules/command_execution/metric.py +0 -55
- {agentcrew_ai-0.8.4.dist-info → agentcrew_ai-0.8.6.dist-info}/WHEEL +0 -0
- {agentcrew_ai-0.8.4.dist-info → agentcrew_ai-0.8.6.dist-info}/entry_points.txt +0 -0
- {agentcrew_ai-0.8.4.dist-info → agentcrew_ai-0.8.6.dist-info}/licenses/LICENSE +0 -0
- {agentcrew_ai-0.8.4.dist-info → agentcrew_ai-0.8.6.dist-info}/top_level.txt +0 -0
AgentCrew/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.8.
|
|
1
|
+
__version__ = "0.8.6"
|
|
@@ -27,6 +27,11 @@ from a2a.types import (
|
|
|
27
27
|
TaskState,
|
|
28
28
|
TaskStatusUpdateEvent,
|
|
29
29
|
TaskArtifactUpdateEvent,
|
|
30
|
+
Part,
|
|
31
|
+
TextPart,
|
|
32
|
+
DataPart,
|
|
33
|
+
Role,
|
|
34
|
+
Message,
|
|
30
35
|
)
|
|
31
36
|
|
|
32
37
|
from AgentCrew.modules.agents import LocalAgent
|
|
@@ -58,6 +63,7 @@ class AgentTaskManager(TaskManager):
|
|
|
58
63
|
"""Manages tasks for a specific agent"""
|
|
59
64
|
|
|
60
65
|
TERMINAL_STATES = {TaskState.completed, TaskState.canceled, TaskState.failed}
|
|
66
|
+
INPUT_REQUIRED_STATES = {TaskState.input_required}
|
|
61
67
|
|
|
62
68
|
def __init__(self, agent_name: str, agent_manager: AgentManager):
|
|
63
69
|
self.agent_name = agent_name
|
|
@@ -72,6 +78,9 @@ class AgentTaskManager(TaskManager):
|
|
|
72
78
|
] = defaultdict(list)
|
|
73
79
|
self.streaming_enabled_tasks: set[str] = set()
|
|
74
80
|
|
|
81
|
+
self.pending_ask_responses: Dict[str, asyncio.Event] = {}
|
|
82
|
+
self.ask_responses: Dict[str, str] = {}
|
|
83
|
+
|
|
75
84
|
self.agent = self.agent_manager.get_agent(self.agent_name)
|
|
76
85
|
if self.agent is None or not isinstance(self.agent, LocalAgent):
|
|
77
86
|
raise ValueError(f"Agent {agent_name} not found or is not a LocalAgent")
|
|
@@ -82,6 +91,19 @@ class AgentTaskManager(TaskManager):
|
|
|
82
91
|
"""Check if a state is terminal."""
|
|
83
92
|
return state in self.TERMINAL_STATES
|
|
84
93
|
|
|
94
|
+
def _extract_text_from_message(self, message: Dict[str, Any]) -> str:
|
|
95
|
+
"""Extract text content from a message."""
|
|
96
|
+
content = message.get("content", [])
|
|
97
|
+
if isinstance(content, str):
|
|
98
|
+
return content
|
|
99
|
+
text_parts = []
|
|
100
|
+
for part in content:
|
|
101
|
+
if isinstance(part, str):
|
|
102
|
+
text_parts.append(part)
|
|
103
|
+
elif isinstance(part, dict) and part.get("type") == "text":
|
|
104
|
+
text_parts.append(part.get("text", ""))
|
|
105
|
+
return " ".join(text_parts)
|
|
106
|
+
|
|
85
107
|
def _validate_task_not_terminal(
|
|
86
108
|
self, task: Task, operation: str
|
|
87
109
|
) -> Optional[TaskNotCancelableError]:
|
|
@@ -121,7 +143,6 @@ class AgentTaskManager(TaskManager):
|
|
|
121
143
|
)
|
|
122
144
|
)
|
|
123
145
|
|
|
124
|
-
# Generate task ID from message
|
|
125
146
|
task_id = (
|
|
126
147
|
request.params.message.task_id
|
|
127
148
|
or f"task_{request.params.message.message_id}"
|
|
@@ -135,8 +156,19 @@ class AgentTaskManager(TaskManager):
|
|
|
135
156
|
root=JSONRPCErrorResponse(id=request.id, error=error)
|
|
136
157
|
)
|
|
137
158
|
|
|
159
|
+
if existing_task.status.state == TaskState.input_required:
|
|
160
|
+
message = convert_a2a_message_to_agent(request.params.message)
|
|
161
|
+
user_response = self._extract_text_from_message(message)
|
|
162
|
+
|
|
163
|
+
if task_id in self.pending_ask_responses:
|
|
164
|
+
self.ask_responses[task_id] = user_response
|
|
165
|
+
self.pending_ask_responses[task_id].set()
|
|
166
|
+
|
|
167
|
+
return SendMessageResponse(
|
|
168
|
+
root=SendMessageSuccessResponse(id=request.id, result=existing_task)
|
|
169
|
+
)
|
|
170
|
+
|
|
138
171
|
if task_id not in self.tasks:
|
|
139
|
-
# Create task with initial state
|
|
140
172
|
task = Task(
|
|
141
173
|
id=task_id,
|
|
142
174
|
context_id=request.params.message.context_id or f"ctx_{task_id}",
|
|
@@ -147,8 +179,8 @@ class AgentTaskManager(TaskManager):
|
|
|
147
179
|
self.tasks[task.id] = task
|
|
148
180
|
|
|
149
181
|
task = self.tasks[task_id]
|
|
150
|
-
if
|
|
151
|
-
self.task_history[
|
|
182
|
+
if task.context_id not in self.task_history:
|
|
183
|
+
self.task_history[task.context_id] = []
|
|
152
184
|
|
|
153
185
|
# Convert A2A message to SwissKnife format
|
|
154
186
|
message = convert_a2a_message_to_agent(request.params.message)
|
|
@@ -185,9 +217,8 @@ class AgentTaskManager(TaskManager):
|
|
|
185
217
|
|
|
186
218
|
message["content"] = new_parts
|
|
187
219
|
|
|
188
|
-
self.task_history[
|
|
220
|
+
self.task_history[task.context_id].append(message)
|
|
189
221
|
|
|
190
|
-
# Process with agent (non-blocking)
|
|
191
222
|
asyncio.create_task(self._process_agent_task(self.agent, task))
|
|
192
223
|
|
|
193
224
|
# Return initial task state
|
|
@@ -243,6 +274,35 @@ class AgentTaskManager(TaskManager):
|
|
|
243
274
|
# Clean up
|
|
244
275
|
self.streaming_tasks.pop(task_id, None)
|
|
245
276
|
|
|
277
|
+
def _create_ask_tool_message(
|
|
278
|
+
self, question: str, guided_answers: list[str]
|
|
279
|
+
) -> Message:
|
|
280
|
+
"""
|
|
281
|
+
Create an A2A message for the ask tool's input-required state.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
question: The question to ask the user
|
|
285
|
+
guided_answers: List of suggested answers
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
A2A Message with the question and guided answers
|
|
289
|
+
"""
|
|
290
|
+
ask_data = {
|
|
291
|
+
"type": "ask",
|
|
292
|
+
"question": question,
|
|
293
|
+
"guided_answers": guided_answers,
|
|
294
|
+
"instruction": "Please respond with one of the guided answers or provide a custom response.",
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return Message(
|
|
298
|
+
message_id=f"ask_{hash(question)}",
|
|
299
|
+
role=Role.agent,
|
|
300
|
+
parts=[
|
|
301
|
+
Part(root=TextPart(text=f"❓ {question}")),
|
|
302
|
+
Part(root=DataPart(data=ask_data)),
|
|
303
|
+
],
|
|
304
|
+
)
|
|
305
|
+
|
|
246
306
|
def _record_and_emit_event(
|
|
247
307
|
self, task_id: str, event: Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent]
|
|
248
308
|
):
|
|
@@ -281,7 +341,7 @@ class AgentTaskManager(TaskManager):
|
|
|
281
341
|
|
|
282
342
|
try:
|
|
283
343
|
artifacts = []
|
|
284
|
-
if task.
|
|
344
|
+
if task.context_id not in self.task_history:
|
|
285
345
|
raise ValueError("Task history is not existed")
|
|
286
346
|
|
|
287
347
|
input_tokens = 0
|
|
@@ -308,7 +368,7 @@ class AgentTaskManager(TaskManager):
|
|
|
308
368
|
chunk_text,
|
|
309
369
|
thinking_chunk,
|
|
310
370
|
) in agent.process_messages(
|
|
311
|
-
self.task_history[task.
|
|
371
|
+
self.task_history[task.context_id], callback=process_result
|
|
312
372
|
):
|
|
313
373
|
# Update current response
|
|
314
374
|
if response_message:
|
|
@@ -388,9 +448,8 @@ class AgentTaskManager(TaskManager):
|
|
|
388
448
|
MessageType.Thinking, {"thinking": thinking_data}
|
|
389
449
|
)
|
|
390
450
|
if thinking_message:
|
|
391
|
-
self.task_history[task.
|
|
451
|
+
self.task_history[task.context_id].append(thinking_message)
|
|
392
452
|
|
|
393
|
-
# Format assistant message with the response and tool uses
|
|
394
453
|
assistant_message = agent.format_message(
|
|
395
454
|
MessageType.Assistant,
|
|
396
455
|
{
|
|
@@ -401,34 +460,99 @@ class AgentTaskManager(TaskManager):
|
|
|
401
460
|
},
|
|
402
461
|
)
|
|
403
462
|
if assistant_message:
|
|
404
|
-
self.task_history[task.
|
|
463
|
+
self.task_history[task.context_id].append(assistant_message)
|
|
405
464
|
|
|
406
|
-
# Process each tool use
|
|
407
465
|
for tool_use in tool_uses:
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
466
|
+
tool_name = tool_use["name"]
|
|
467
|
+
|
|
468
|
+
if tool_name == "ask":
|
|
469
|
+
question = tool_use["input"].get("question", "")
|
|
470
|
+
guided_answers = tool_use["input"].get("guided_answers", [])
|
|
471
|
+
|
|
472
|
+
task.status.state = TaskState.input_required
|
|
473
|
+
task.status.timestamp = datetime.now().isoformat()
|
|
474
|
+
task.status.message = self._create_ask_tool_message(
|
|
475
|
+
question, guided_answers
|
|
412
476
|
)
|
|
413
477
|
|
|
478
|
+
self._record_and_emit_event(
|
|
479
|
+
task.id,
|
|
480
|
+
TaskStatusUpdateEvent(
|
|
481
|
+
task_id=task.id,
|
|
482
|
+
context_id=task.context_id,
|
|
483
|
+
status=task.status,
|
|
484
|
+
final=False,
|
|
485
|
+
),
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
wait_event = asyncio.Event()
|
|
489
|
+
self.pending_ask_responses[task.id] = wait_event
|
|
490
|
+
|
|
491
|
+
try:
|
|
492
|
+
await asyncio.wait_for(wait_event.wait(), timeout=300)
|
|
493
|
+
user_answer = self.ask_responses.get(
|
|
494
|
+
task.id, "No response received"
|
|
495
|
+
)
|
|
496
|
+
except asyncio.TimeoutError:
|
|
497
|
+
user_answer = "User did not respond in time."
|
|
498
|
+
finally:
|
|
499
|
+
self.pending_ask_responses.pop(task.id, None)
|
|
500
|
+
self.ask_responses.pop(task.id, None)
|
|
501
|
+
|
|
502
|
+
tool_result = f"User's answer: {user_answer}"
|
|
503
|
+
|
|
504
|
+
task.status.state = TaskState.working
|
|
505
|
+
task.status.timestamp = datetime.now().isoformat()
|
|
506
|
+
task.status.message = None
|
|
507
|
+
|
|
414
508
|
tool_result_message = agent.format_message(
|
|
415
509
|
MessageType.ToolResult,
|
|
416
510
|
{"tool_use": tool_use, "tool_result": tool_result},
|
|
417
511
|
)
|
|
418
512
|
if tool_result_message:
|
|
419
|
-
self.task_history[task.
|
|
513
|
+
self.task_history[task.context_id].append(
|
|
514
|
+
tool_result_message
|
|
515
|
+
)
|
|
420
516
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
517
|
+
self._record_and_emit_event(
|
|
518
|
+
task.id,
|
|
519
|
+
TaskStatusUpdateEvent(
|
|
520
|
+
task_id=task.id,
|
|
521
|
+
context_id=task.context_id,
|
|
522
|
+
status=task.status,
|
|
523
|
+
final=False,
|
|
524
|
+
),
|
|
429
525
|
)
|
|
430
|
-
|
|
431
|
-
|
|
526
|
+
|
|
527
|
+
else:
|
|
528
|
+
try:
|
|
529
|
+
tool_result = await agent.execute_tool_call(
|
|
530
|
+
tool_name,
|
|
531
|
+
tool_use["input"],
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
tool_result_message = agent.format_message(
|
|
535
|
+
MessageType.ToolResult,
|
|
536
|
+
{"tool_use": tool_use, "tool_result": tool_result},
|
|
537
|
+
)
|
|
538
|
+
if tool_result_message:
|
|
539
|
+
self.task_history[task.context_id].append(
|
|
540
|
+
tool_result_message
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
except Exception as e:
|
|
544
|
+
error_message = agent.format_message(
|
|
545
|
+
MessageType.ToolResult,
|
|
546
|
+
{
|
|
547
|
+
"tool_use": tool_use,
|
|
548
|
+
"tool_result": str(e),
|
|
549
|
+
"is_error": True,
|
|
550
|
+
},
|
|
551
|
+
)
|
|
552
|
+
if error_message:
|
|
553
|
+
self.task_history[task.context_id].append(
|
|
554
|
+
error_message
|
|
555
|
+
)
|
|
432
556
|
|
|
433
557
|
return await _process_task()
|
|
434
558
|
return current_response
|
|
@@ -442,9 +566,9 @@ class AgentTaskManager(TaskManager):
|
|
|
442
566
|
},
|
|
443
567
|
)
|
|
444
568
|
if assistant_message:
|
|
445
|
-
self.task_history[task.
|
|
569
|
+
self.task_history[task.context_id].append(assistant_message)
|
|
446
570
|
user_message = (
|
|
447
|
-
self.task_history[task.
|
|
571
|
+
self.task_history[task.context_id][0]
|
|
448
572
|
.get("content", [{}])[0]
|
|
449
573
|
.get("text", "")
|
|
450
574
|
)
|
|
@@ -604,7 +728,7 @@ class AgentTaskManager(TaskManager):
|
|
|
604
728
|
Yields:
|
|
605
729
|
Streaming responses with task updates
|
|
606
730
|
"""
|
|
607
|
-
task_id = request.params.
|
|
731
|
+
task_id = request.params.id
|
|
608
732
|
|
|
609
733
|
if task_id not in self.tasks:
|
|
610
734
|
error = A2AError.task_not_found(task_id)
|
|
@@ -111,25 +111,25 @@ class LocalAgent(BaseAgent):
|
|
|
111
111
|
# self.tool_prompts.append(
|
|
112
112
|
# delegate_tool_prompt(self.services["agent_manager"])
|
|
113
113
|
# )
|
|
114
|
+
from AgentCrew.modules.agents.tools.ask import (
|
|
115
|
+
register as register_ask,
|
|
116
|
+
ask_tool_prompt,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
register_ask(self)
|
|
120
|
+
self.tool_prompts.append(ask_tool_prompt())
|
|
121
|
+
|
|
114
122
|
if not self.is_remoting_mode:
|
|
115
123
|
from AgentCrew.modules.agents.tools.transfer import (
|
|
116
124
|
register as register_transfer,
|
|
117
125
|
transfer_tool_prompt,
|
|
118
126
|
)
|
|
119
|
-
from AgentCrew.modules.agents.tools.ask import (
|
|
120
|
-
register as register_ask,
|
|
121
|
-
ask_tool_prompt,
|
|
122
|
-
)
|
|
123
127
|
|
|
124
128
|
register_transfer(self.services["agent_manager"], self)
|
|
125
129
|
self.tool_prompts.append(
|
|
126
130
|
transfer_tool_prompt(self.services["agent_manager"])
|
|
127
131
|
)
|
|
128
132
|
|
|
129
|
-
# Register the ask tool (always available)
|
|
130
|
-
register_ask(self)
|
|
131
|
-
self.tool_prompts.append(ask_tool_prompt())
|
|
132
|
-
|
|
133
133
|
for tool_name in self.tools:
|
|
134
134
|
if self.services and tool_name in self.services:
|
|
135
135
|
service = self.services[tool_name]
|
|
@@ -642,7 +642,7 @@ Check if `when` condition in <Global_Behavior> or <Project_Behavior> matches, up
|
|
|
642
642
|
adaptive_messages["content"].append(
|
|
643
643
|
{
|
|
644
644
|
"type": "text",
|
|
645
|
-
"text": f"Here are conversations that we have discussed:\n- {'\n- '.join(memory_headers)}",
|
|
645
|
+
"text": f"Here are conversations that we have discussed from oldest to latest:\n - {'\n - '.join(memory_headers)}",
|
|
646
646
|
}
|
|
647
647
|
)
|
|
648
648
|
if len(adaptive_messages["content"]) > 0:
|
|
@@ -68,7 +68,7 @@ def clean_markdown_images(markdown_content: str) -> str:
|
|
|
68
68
|
Cleaned markdown content
|
|
69
69
|
"""
|
|
70
70
|
# Pattern for markdown images: 
|
|
71
|
-
markdown_img_pattern = r"
|
|
71
|
+
markdown_img_pattern = r"!?\[([^\]]*)\]\(([^)]+)\)"
|
|
72
72
|
|
|
73
73
|
def replace_markdown_img(match):
|
|
74
74
|
alt_text = match.group(1)
|
|
@@ -7,7 +7,7 @@ scroll content, and extract page information using Chrome DevTools Protocol.
|
|
|
7
7
|
|
|
8
8
|
import time
|
|
9
9
|
from typing import Dict, Any, Optional, List
|
|
10
|
-
from html_to_markdown import
|
|
10
|
+
from html_to_markdown import convert, ConversionOptions, PreprocessingOptions
|
|
11
11
|
import urllib.parse
|
|
12
12
|
|
|
13
13
|
from html.parser import HTMLParser
|
|
@@ -344,13 +344,23 @@ class BrowserAutomationService:
|
|
|
344
344
|
filtered_html = self._filter_hidden_elements(raw_html)
|
|
345
345
|
|
|
346
346
|
# Convert HTML to markdown
|
|
347
|
-
raw_markdown_content = convert_to_markdown(
|
|
347
|
+
# raw_markdown_content = convert_to_markdown(
|
|
348
|
+
# filtered_html,
|
|
349
|
+
# source_encoding="utf-8",
|
|
350
|
+
# strip_newlines=True,
|
|
351
|
+
# extract_metadata=False,
|
|
352
|
+
# remove_forms=False,
|
|
353
|
+
# remove_navigation=False,
|
|
354
|
+
# )
|
|
355
|
+
raw_markdown_content = convert(
|
|
348
356
|
filtered_html,
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
357
|
+
ConversionOptions(
|
|
358
|
+
strip_newlines=True,
|
|
359
|
+
extract_metadata=False,
|
|
360
|
+
),
|
|
361
|
+
PreprocessingOptions(
|
|
362
|
+
remove_navigation=False, remove_forms=False, preset="minimal"
|
|
363
|
+
),
|
|
354
364
|
)
|
|
355
365
|
if not raw_markdown_content:
|
|
356
366
|
return {"success": False, "error": "Could not convert HTML to markdown"}
|
|
@@ -101,6 +101,9 @@ class CommandProcessor:
|
|
|
101
101
|
|
|
102
102
|
async def _handle_copy_command(self, user_input: str) -> CommandResult:
|
|
103
103
|
copy_idx = user_input[5:].strip() or 1
|
|
104
|
+
user_input_idxs = [
|
|
105
|
+
turn.message_index for turn in self.message_handler.conversation_turns
|
|
106
|
+
]
|
|
104
107
|
|
|
105
108
|
asssistant_messages_iterator = reversed(
|
|
106
109
|
[
|
|
@@ -108,8 +111,7 @@ class CommandProcessor:
|
|
|
108
111
|
for i, msg in enumerate(self.message_handler.streamline_messages)
|
|
109
112
|
if msg.get("role") == "assistant"
|
|
110
113
|
and (
|
|
111
|
-
|
|
112
|
-
== "user"
|
|
114
|
+
i + 1 in user_input_idxs
|
|
113
115
|
if i + 1 < len(self.message_handler.streamline_messages)
|
|
114
116
|
else True
|
|
115
117
|
)
|
|
@@ -279,7 +279,7 @@ class MessageHandler(Observable):
|
|
|
279
279
|
# Notify about response progress
|
|
280
280
|
if not self.agent.is_streaming():
|
|
281
281
|
# Delays it a bit when using without stream
|
|
282
|
-
time.sleep(0.
|
|
282
|
+
time.sleep(0.3)
|
|
283
283
|
self._notify("response_chunk", (chunk_text, assistant_response))
|
|
284
284
|
if voice_sentence is not None:
|
|
285
285
|
if (
|
|
@@ -321,6 +321,8 @@ class MessageHandler(Observable):
|
|
|
321
321
|
.lstrip("\n")
|
|
322
322
|
)
|
|
323
323
|
|
|
324
|
+
self.stream_generator = None
|
|
325
|
+
|
|
324
326
|
# End thinking when break the response stream
|
|
325
327
|
if not end_thinking and start_thinking:
|
|
326
328
|
self._notify("thinking_completed", thinking_content)
|
|
@@ -386,11 +388,6 @@ class MessageHandler(Observable):
|
|
|
386
388
|
|
|
387
389
|
return await self.get_assistant_response()
|
|
388
390
|
|
|
389
|
-
self.stream_generator = None
|
|
390
|
-
|
|
391
|
-
if thinking_content:
|
|
392
|
-
self._notify("agent_continue", self.agent.name)
|
|
393
|
-
|
|
394
391
|
# Add assistant response to messages
|
|
395
392
|
if assistant_response.strip():
|
|
396
393
|
self._messages_append(
|
|
@@ -15,8 +15,8 @@ MAX_CONCURRENT_COMMANDS = 3
|
|
|
15
15
|
# Maximum lifetime for a single command execution (seconds)
|
|
16
16
|
MAX_COMMAND_LIFETIME = 600
|
|
17
17
|
|
|
18
|
-
# Maximum output
|
|
19
|
-
|
|
18
|
+
# Maximum output lines to keep in rolling buffer per stream (stdout/stderr)
|
|
19
|
+
MAX_OUTPUT_LINES = 300
|
|
20
20
|
|
|
21
21
|
# Maximum number of commands allowed per minute (application-wide rate limit)
|
|
22
22
|
MAX_COMMANDS_PER_MINUTE = 10
|