Added timeout and limit to script entries

This commit is contained in:
Niels Geens
2024-11-13 17:48:39 +01:00
parent 65478e0b17
commit 834ce12e8f
21 changed files with 262 additions and 141 deletions

View File

@@ -33,6 +33,8 @@
Single script that runs once and completes.
Output is stored in context until explicitly deleted.
Used for one-time operations like file manipulation.
Single scripts are limited to 1024 characters and 1 second timeout by default.
These limits can be changed with attributes.
Example:
<single>
ls /
@@ -43,6 +45,8 @@
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip"/>
</xs:sequence>
<xs:attribute name="timeout" type="xs:float" use="optional"/>
<xs:attribute name="limit" type="xs:integer" use="optional"/>
</xs:complexType>
</xs:element>
@@ -51,6 +55,8 @@
After a command is issued, all repeat scripts in context are run again.
Useful for monitoring changing files or viewing results immediately after changing a file.
Repeat scripts should execute quickly to avoid blocking the agent.
Repeat scripts are limited to 1024 characters and 1 second timeout by default.
These limits can be changed with attributes.
Example:
<repeat>
ls /
@@ -61,6 +67,8 @@
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip"/>
</xs:sequence>
<xs:attribute name="timeout" type="xs:float" use="optional"/>
<xs:attribute name="limit" type="xs:integer" use="optional"/>
</xs:complexType>
</xs:element>
@@ -83,6 +91,7 @@
<!--
Read all available text on stdin and store it in context.
Do this only if the context indicates there is data in the stdin buffer.
Example:
<read_stdin/>
-->
@@ -93,6 +102,7 @@
<!--
Write to stdout.
This is your main way of contacting the user.
Make sure you have properly reasoned about what to say and if it is necessary before issuing a write_stdout command.
Example:
<write_stdout>
Hello world!

View File

@@ -17,14 +17,19 @@ class BackgroundEntry(Entry):
_exit_code: Exit code when process completes
"""
def __init__(self, script: str, id: str, timestamp: datetime):
def __init__(
self,
id: str,
timestamp: datetime,
script: str,
):
"""
Initialize a new background entry.
Args:
script: The script/command to execute
id: Unique identifier for this entry
timestamp: Creation timestamp for this entry
script: The script/command to execute
"""
super().__init__(id, timestamp)
self._script = script

View File

@@ -8,15 +8,21 @@ class ParseErrorEntry(Entry):
Entry type for parse and validation errors.
"""
def __init__(self, content: str, error: str, id: str, timestamp: datetime):
def __init__(
self,
id: str,
timestamp: datetime,
content: str,
error: str,
):
"""
Initialize a new parse error entry.
Args:
content: Original content that failed to parse
error: Error message describing the failure
id: Unique identifier for this entry
timestamp: Creation timestamp for this entry
content: Original content that failed to parse
error: Error message describing the failure
"""
super().__init__(id, timestamp)
self._content = content

View File

@@ -13,14 +13,19 @@ class ReadEntry(Entry):
_read: Whether content has been read
"""
def __init__(self, io_buffer, id: str, timestamp: datetime):
def __init__(
self,
id: str,
timestamp: datetime,
io_buffer,
):
"""
Initialize a new read entry.
Args:
io_buffer: Buffer to use for IO operations
id: Unique identifier for this entry
timestamp: Creation timestamp for this entry
io_buffer: Buffer to use for IO operations
"""
super().__init__(id, timestamp)
self._content = ""

View File

@@ -11,14 +11,19 @@ class ReasoningEntry(Entry):
_content: The reasoning text
"""
def __init__(self, content: str, id: str, timestamp: datetime):
def __init__(
self,
id: str,
timestamp: datetime,
content: str,
):
"""
Initialize a new reasoning entry.
Args:
content: The reasoning text
id: Unique identifier for this entry
timestamp: Creation timestamp for this entry
content: The reasoning text
"""
super().__init__(id, timestamp)
self._content = content

View File

@@ -8,22 +8,46 @@ from .entry import Entry
class RepeatEntry(Entry):
"""
Entry type for scripts that are executed on every update.
Attributes:
script: The script/command to execute
timeout: Maximum time to wait for script execution
limit: Maximum number of characters to capture from stdout/stderr
stdout: Captured standard output from script execution
stderr: Captured standard error from script execution
exit_code: Process exit code after completion
_executed: Whether the script has been executed
"""
default_timeout = 1
default_limit = 1024
def __init__(self, script: str, id: str, timestamp: datetime):
def __init__(
self,
id: str,
timestamp: datetime,
script: str,
timeout: Optional[float],
limit: Optional[int],
):
"""
Initialize a new repeat entry.
Args:
script: The script/command to execute
id: Unique identifier for this entry
timestamp: Creation timestamp for this entry
script: The script/command to execute
timeout: Maximum time to wait for script execution
limit: Maximum number of characters to capture from stdout/stderr
"""
super().__init__(id, timestamp)
self._script = script
self._timeout = timeout or self.default_timeout
self._limit = limit or self.default_limit
self._stdout = ""
self._stderr = ""
self._exit_code: Optional[int] = None
self._timed_out = False
@property
def script(self) -> str:
@@ -50,15 +74,20 @@ class RepeatEntry(Entry):
Execute the script and update the output.
Captures stdout, stderr and exit code from each execution.
"""
process = subprocess.run(
self._script,
shell=True,
capture_output=True,
text=True
)
self._stdout = process.stdout
self._stderr = process.stderr
self._exit_code = process.returncode
try:
process = subprocess.run(
self._script,
timeout=self._timeout,
shell=True,
capture_output=True,
text=True
)
self._stdout = process.stdout
self._stderr = process.stderr
self._exit_code = process.returncode
except subprocess.TimeoutExpired as e:
self._timed_out = True
def generate_context(self) -> ET.Element:
"""
@@ -69,12 +98,20 @@ class RepeatEntry(Entry):
"""
element = ET.Element("repeat", {
"id": self.id,
"exit_code": str(self._exit_code) if self._exit_code is not None else "-1"
})
element.text = self.script
stdout_elem = ET.SubElement(element, "stdout")
stdout_elem.text = self._stdout
stderr_elem = ET.SubElement(element, "stderr")
stderr_elem.text = self._stderr
if self._exit_code is not None:
element.set("exit_code", str(self._exit_code))
if self._timed_out:
element.set("timed_out", "true")
else:
if len(self._stdout) > self._limit:
element.set("stdout_truncated", "true")
stdout_elem = ET.SubElement(element, "stdout")
stdout_elem.text = self._stdout[:self._limit]
if len(self._stderr) > self._limit:
element.set("stderr_truncated", "true")
stderr_elem = ET.SubElement(element, "stderr")
stderr_elem.text = self._stderr[:self._limit]
return element

View File

@@ -54,15 +54,15 @@ class ResponseParser:
try:
root = ET.fromstring(xml.strip())
except ET.ParseError as e:
return ParseErrorEntry(xml, f"Invalid XML: {str(e)}", entry_id, timestamp)
return ParseErrorEntry(entry_id, timestamp, xml, f"Invalid XML: {str(e)}")
try:
if root.tag == 'delete':
target_id = root.get('id')
if not target_id:
return ParseErrorEntry(xml, "Delete command missing required 'id' attribute", entry_id, timestamp)
return ParseErrorEntry(entry_id, timestamp, xml, "Delete command missing required 'id' attribute")
if len(root.attrib) > 1:
return ParseErrorEntry(xml, "Delete command should only have 'id' attribute", entry_id, timestamp)
return ParseErrorEntry(entry_id, timestamp, xml, "Delete command should only have 'id' attribute")
return DeleteCommand(target_id)
elif root.tag == 'stop':
@@ -70,34 +70,46 @@ class ResponseParser:
elif root.tag == 'background':
if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '':
return ParseErrorEntry(xml, "Background entry requires (only) script content", entry_id, timestamp)
return BackgroundEntry(root.text, entry_id, timestamp)
return ParseErrorEntry(entry_id, timestamp, xml, "Background entry requires (only) script content")
return BackgroundEntry(entry_id, timestamp, root.text)
elif root.tag == 'repeat':
if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '':
return ParseErrorEntry(xml, "Repeat entry requires (only) script content", entry_id, timestamp)
return RepeatEntry(root.text, entry_id, timestamp)
if len(root) != 0 or root.text is None or root.text.strip() == '':
return ParseErrorEntry(entry_id, timestamp, xml, "Repeat entry requires (only) script content")
if len(root.attrib) > 2 or any(k not in ('timeout', 'limit') for k in root.attrib):
return ParseErrorEntry(entry_id, timestamp, xml, "Repeat entry only accepts 'timeout' and 'limit' attributes")
timeout = root.get('timeout')
limit = root.get('limit')
timeout = float(timeout) if timeout is not None else None
limit = int(limit) if limit is not None else None
return RepeatEntry(entry_id, timestamp, root.text, timeout, limit)
elif root.tag == 'single':
if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '':
return ParseErrorEntry(xml, "Single entry requires (only) script content", entry_id, timestamp)
return SingleEntry(root.text, entry_id, timestamp)
if len(root) != 0 or root.text is None or root.text.strip() == '':
return ParseErrorEntry(entry_id, timestamp, xml, "Single entry requires (only) script content")
if len(root.attrib) > 2 or any(k not in ('timeout', 'limit') for k in root.attrib):
return ParseErrorEntry(entry_id, timestamp, xml, "Single entry only accepts 'timeout' and 'limit' attributes")
timeout = root.get('timeout')
limit = root.get('limit')
timeout = float(timeout) if timeout is not None else None
limit = int(limit) if limit is not None else None
return SingleEntry(entry_id, timestamp, root.text, timeout, limit)
elif root.tag == 'reasoning':
if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '':
return ParseErrorEntry(xml, "Reasoning entry requires (only) text content", entry_id, timestamp)
return ReasoningEntry(root.text, entry_id, timestamp)
return ParseErrorEntry(entry_id, timestamp, xml, "Reasoning entry requires (only) text content")
return ReasoningEntry(entry_id, timestamp, root.text)
elif root.tag == 'read_stdin':
return ReadEntry(self.io_buffer, entry_id, timestamp)
return ReadEntry(entry_id, timestamp, self.io_buffer)
elif root.tag == 'write_stdout':
if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '':
return ParseErrorEntry(xml, "Write stdout entry requires (only) text content", entry_id, timestamp)
return WriteEntry(root.text, self.io_buffer, entry_id, timestamp)
return ParseErrorEntry(entry_id, timestamp, xml, "Write stdout entry requires (only) text content")
return WriteEntry(entry_id, timestamp, root.text, self.io_buffer)
else:
return ParseErrorEntry(xml, f"Unknown root element: {root.tag}", entry_id, timestamp)
return ParseErrorEntry(entry_id, timestamp, xml, f"Unknown root element: {root.tag}")
except Exception as e:
return ParseErrorEntry(xml, f"Error parsing response: {str(e)}", entry_id, timestamp)
return ParseErrorEntry(entry_id, timestamp, xml, f"Error parsing response: {str(e)}")

View File

@@ -11,27 +11,44 @@ class SingleEntry(Entry):
Attributes:
script: The script/command to execute
timeout: Maximum time to wait for script execution
limit: Maximum number of characters to capture from stdout/stderr
stdout: Captured standard output from script execution
stderr: Captured standard error from script execution
exit_code: Process exit code after completion
_executed: Whether the script has been executed
"""
default_timeout = 1
default_limit = 1024
def __init__(self, script: str, id: str, timestamp: datetime):
def __init__(
self,
id: str,
timestamp: datetime,
script: str,
timeout: Optional[float],
limit: Optional[int],
):
"""
Initialize a new single shot entry.
Args:
script: The script/command to execute
id: Unique identifier for this entry
timestamp: Creation timestamp for this entry
script: The script/command to execute
timeout: Maximum time to wait for script execution
limit: Maximum number of characters to capture from stdout/stderr
"""
super().__init__(id, timestamp)
self._script = script
self._timeout = timeout or self.default_timeout
self._limit = limit or self.default_limit
self._stdout = ""
self._stderr = ""
self._exit_code: Optional[int] = None
self._executed = False
self._timed_out = False
@property
def script(self) -> str:
@@ -60,16 +77,20 @@ class SingleEntry(Entry):
"""
if self._executed:
return
process = subprocess.run(
self.script,
shell=True,
capture_output=True,
text=True
)
self._stdout = process.stdout
self._stderr = process.stderr
self._exit_code = process.returncode
self._executed = True
try:
process = subprocess.run(
self._script,
timeout=self._timeout,
shell=True,
capture_output=True,
text=True
)
self._stdout = process.stdout
self._stderr = process.stderr
self._exit_code = process.returncode
except subprocess.TimeoutExpired as e:
self._timed_out = True
def generate_context(self) -> ET.Element:
"""
@@ -82,10 +103,16 @@ class SingleEntry(Entry):
"id": self.id,
})
element.text = self.script
if self._executed:
stdout_elem = ET.SubElement(element, "stdout")
stdout_elem.text = self._stdout
stderr_elem = ET.SubElement(element, "stderr")
stderr_elem.text = self._stderr
if self._timed_out:
element.set("timed_out", "true")
elif self._executed:
element.set("exit_code", str(self._exit_code))
if len(self._stdout) > self._limit:
element.set("stdout_truncated", "true")
stdout_elem = ET.SubElement(element, "stdout")
stdout_elem.text = self._stdout[:self._limit]
if len(self._stderr) > self._limit:
element.set("stderr_truncated", "true")
stderr_elem = ET.SubElement(element, "stderr")
stderr_elem.text = self._stderr[:self._limit]
return element

View File

@@ -2,6 +2,7 @@ from datetime import datetime
import xml.etree.ElementTree as ET
from .entry import Entry
from .io_buffer import IOBuffer
class WriteEntry(Entry):
"""
@@ -13,15 +14,21 @@ class WriteEntry(Entry):
_written: Whether the content has been written
"""
def __init__(self, content: str, io_buffer, id: str, timestamp: datetime):
def __init__(
self,
id: str,
timestamp: datetime,
content: str,
io_buffer: IOBuffer,
):
"""
Initialize a new write entry.
Args:
content: Text to write to stdout
io_buffer: Buffer to use for IO operations
id: Unique identifier for this entry
timestamp: Creation timestamp for this entry
content: Text to write to stdout
io_buffer: Buffer to use for IO operations
"""
super().__init__(id, timestamp)
self.content = content

View File

@@ -30,6 +30,7 @@ Scripts and their output appear in the context.
You can use a script for starting a detached process that runs in the background.
All processes can be managed by the usual Linux tools.
The scripts defined in the script actions all run in a `bash` shell.
You are logged in as root.
# File system
@@ -71,6 +72,9 @@ Inspect your previous actions in detail.
If something didn't work, try to understand why and don't repeat the same mistake.
Don't delete mistakes until you understand them.
If you notice parse_error entries in the context you have made a mistake.
Reason about the error before trying again.
# User interaction
You are always working for a user.

View File

@@ -40,7 +40,7 @@ class BackgroundEntryTest(unittest.TestCase):
def test_initialization(self):
"""Test entry initialization"""
entry = BackgroundEntry("echo test", self.test_id, self.test_timestamp)
entry = BackgroundEntry(self.test_id, self.test_timestamp, "echo test")
context = entry.generate_context()
# Initial context should have script but no pid or exit_code
@@ -50,7 +50,7 @@ class BackgroundEntryTest(unittest.TestCase):
def test_short_running_process(self):
"""Test execution of a quick process"""
entry = BackgroundEntry("echo 'test'", self.test_id, self.test_timestamp)
entry = BackgroundEntry(self.test_id, self.test_timestamp, "echo 'test'")
# Wait for process to complete
def is_complete(ctx):
@@ -74,7 +74,7 @@ class BackgroundEntryTest(unittest.TestCase):
'"'
)
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
entry = BackgroundEntry(self.test_id, self.test_timestamp, script)
# Wait for process to complete
def has_all_output(ctx):
@@ -85,7 +85,7 @@ class BackgroundEntryTest(unittest.TestCase):
def test_process_failure(self):
"""Test handling of process failures"""
entry = BackgroundEntry("nonexistentcommand", self.test_id, self.test_timestamp)
entry = BackgroundEntry(self.test_id, self.test_timestamp, "nonexistentcommand")
# Wait for process to fail
def has_failed(ctx):
@@ -100,7 +100,7 @@ class BackgroundEntryTest(unittest.TestCase):
def test_cleanup_running_process(self):
"""Test cleanup of running process"""
entry = BackgroundEntry("sleep 10", self.test_id, self.test_timestamp)
entry = BackgroundEntry(self.test_id, self.test_timestamp, "sleep 10")
# Wait for process to start
def has_started(ctx):
@@ -122,7 +122,7 @@ class BackgroundEntryTest(unittest.TestCase):
def test_cleanup_completed_process(self):
"""Test cleanup of already completed process"""
entry = BackgroundEntry("echo test", self.test_id, self.test_timestamp)
entry = BackgroundEntry(self.test_id, self.test_timestamp, "echo test")
# Wait for completion
def is_complete(ctx):
@@ -135,7 +135,7 @@ class BackgroundEntryTest(unittest.TestCase):
def test_multiple_cleanup_calls(self):
"""Test that multiple cleanup calls are safe"""
entry = BackgroundEntry("sleep 10", self.test_id, self.test_timestamp)
entry = BackgroundEntry(self.test_id, self.test_timestamp, "sleep 10")
# Wait for process to start
def has_started(ctx):

View File

@@ -145,8 +145,9 @@ class BaseAgentTest(unittest.TestCase):
self.assertEqual(len(list(root)), 0)
def test_compile_context_with_entries(self):
# Add test entry to working memory
entry = ReasoningEntry("test reasoning", "test-id", self.test_timestamp)
"""Test context compilation with a single entry."""
# Add test entry to working memory - note id and timestamp are now first
entry = ReasoningEntry("test-id", self.test_timestamp, "test reasoning")
self.agent._working_memory.add_entry(entry)
context = self.agent._compile_context()
@@ -166,9 +167,9 @@ class BaseAgentTest(unittest.TestCase):
def test_multiple_entries(self):
"""Test handling multiple entries in working memory."""
# Add multiple entries
# Add multiple entries - note id and timestamp are now first
entries = [
ReasoningEntry(f"test {i}", f"id-{i}", self.test_timestamp)
ReasoningEntry(f"id-{i}", self.test_timestamp, f"test {i}")
for i in range(3)
]
@@ -186,4 +187,4 @@ class BaseAgentTest(unittest.TestCase):
self.assertEqual(len(reasoning_elems), 3)
for i, elem in enumerate(reasoning_elems):
self.assertEqual(elem.get("id"), f"id-{i}")
self.assertEqual(elem.text.strip(), f"test {i}")
self.assertEqual(elem.text.strip(), f"test {i}")

View File

@@ -8,8 +8,6 @@ from sia.delete_command import DeleteCommand
from sia.reasoning_entry import ReasoningEntry
from sia.background_entry import BackgroundEntry
class DeleteCommandTest(unittest.TestCase):
def setUp(self):
"""Set up test cases with a fresh working memory"""
@@ -31,7 +29,7 @@ class DeleteCommandTest(unittest.TestCase):
def test_delete_running_background_entry(self):
"""Test deleting a background entry that is still running"""
# Create and start background process
entry = BackgroundEntry("sleep 10", "test-id", self.test_timestamp)
entry = BackgroundEntry(self.test_id, self.test_timestamp, "sleep 10")
self.memory.add_entry(entry)
# Wait for process to start
@@ -58,7 +56,7 @@ class DeleteCommandTest(unittest.TestCase):
def test_delete_existing_entry(self):
"""Test deleting an existing entry"""
# Add test entry
entry = ReasoningEntry("test content", self.test_id, self.test_timestamp)
entry = ReasoningEntry(self.test_id, self.test_timestamp, "test content")
self.memory.add_entry(entry)
# Execute delete command

View File

@@ -13,31 +13,35 @@ class ParseErrorEntryTest(unittest.TestCase):
"""Test entry initialization"""
content = "invalid content"
error = "parsing failed"
entry = ParseErrorEntry(content, error, self.test_id, self.test_timestamp)
entry = ParseErrorEntry(self.test_id, self.test_timestamp, content, error)
self.assertEqual(entry.id, self.test_id)
self.assertEqual(entry.timestamp, self.test_timestamp)
self.assertEqual(entry.content, content)
self.assertEqual(entry.error, error)
def test_update_does_nothing(self):
"""Test that update operation has no effect"""
content = "invalid content"
error = "parsing failed"
entry = ParseErrorEntry(content, error, self.test_id, self.test_timestamp)
entry = ParseErrorEntry(self.test_id, self.test_timestamp, content, error)
# Store initial state
initial_context = entry.content
initial_content = entry.content
initial_error = entry.error
# Perform update
entry.update()
# Verify state hasn't changed
self.assertEqual(entry.content, initial_context)
self.assertEqual(entry.content, initial_content)
self.assertEqual(entry.error, initial_error)
def test_generate_context(self):
"""Test XML context generation"""
content = "invalid content"
error = "parsing failed"
entry = ParseErrorEntry(content, error, self.test_id, self.test_timestamp)
entry = ParseErrorEntry(self.test_id, self.test_timestamp, content, error)
element = entry.generate_context()
@@ -48,26 +52,26 @@ class ParseErrorEntryTest(unittest.TestCase):
# Verify error element
error_elem = element.find("error")
self.assertIsNotNone(error_elem)
self.assertEqual(error_elem.text, f"{error}")
self.assertEqual(error_elem.text, error)
# Verify content element
content_elem = element.find("content")
self.assertIsNotNone(content_elem)
self.assertEqual(content_elem.text, f"{content}")
self.assertEqual(content_elem.text, content)
def test_special_characters(self):
"""Test handling content and errors with special characters"""
content = "Special content: \n\t\r\'\"\\"
error = "Special error: \n\t\r\'\"\\"
entry = ParseErrorEntry(content, error, self.test_id, self.test_timestamp)
entry = ParseErrorEntry(self.test_id, self.test_timestamp, content, error)
element = entry.generate_context()
self.assertEqual(element.find("content").text, f"{content}")
self.assertEqual(element.find("error").text, f"{error}")
self.assertEqual(element.find("content").text, content)
self.assertEqual(element.find("error").text, error)
def test_empty_content_and_error(self):
"""Test handling empty content and error messages"""
entry = ParseErrorEntry("", "", self.test_id, self.test_timestamp)
entry = ParseErrorEntry(self.test_id, self.test_timestamp, "", "")
element = entry.generate_context()
self.assertEqual(element.find("content").text, "")

View File

@@ -13,7 +13,7 @@ class ReadEntryTest(unittest.TestCase):
def test_initialization(self):
"""Test entry initialization"""
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
entry = ReadEntry(self.test_id, self.test_timestamp, self.io_buffer)
self.assertEqual(entry.content, "")
self.assertEqual(entry.id, self.test_id)
@@ -24,7 +24,7 @@ class ReadEntryTest(unittest.TestCase):
test_input = "test message"
self.io_buffer.append_stdin(test_input)
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
entry = ReadEntry(self.test_id, self.test_timestamp, self.io_buffer)
entry.update()
self.assertEqual(entry.content, test_input)
@@ -35,7 +35,7 @@ class ReadEntryTest(unittest.TestCase):
test_input = "initial input"
self.io_buffer.append_stdin(test_input)
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
entry = ReadEntry(self.test_id, self.test_timestamp, self.io_buffer)
entry.update()
initial_content = entry.content
@@ -47,7 +47,7 @@ class ReadEntryTest(unittest.TestCase):
def test_empty_input(self):
"""Test reading when no input is available"""
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
entry = ReadEntry(self.test_id, self.test_timestamp, self.io_buffer)
entry.update()
self.assertEqual(entry.content, "")
@@ -57,7 +57,7 @@ class ReadEntryTest(unittest.TestCase):
test_input = "Special chars: \n\t\r\'\"\\"
self.io_buffer.append_stdin(test_input)
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
entry = ReadEntry(self.test_id, self.test_timestamp, self.io_buffer)
entry.update()
self.assertEqual(entry.content, test_input)
@@ -67,14 +67,14 @@ class ReadEntryTest(unittest.TestCase):
test_input = "x" * 10000
self.io_buffer.append_stdin(test_input)
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
entry = ReadEntry(self.test_id, self.test_timestamp, self.io_buffer)
entry.update()
self.assertEqual(entry.content, test_input)
def test_generate_context_before_read(self):
"""Test XML context generation before reading"""
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
entry = ReadEntry(self.test_id, self.test_timestamp, self.io_buffer)
element = entry.generate_context()
# Verify XML structure
@@ -87,7 +87,7 @@ class ReadEntryTest(unittest.TestCase):
test_input = "test message"
self.io_buffer.append_stdin(test_input)
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
entry = ReadEntry(self.test_id, self.test_timestamp, self.io_buffer)
entry.update()
element = entry.generate_context()
@@ -101,7 +101,7 @@ class ReadEntryTest(unittest.TestCase):
test_input = "Hello 世界 😊"
self.io_buffer.append_stdin(test_input)
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
entry = ReadEntry(self.test_id, self.test_timestamp, self.io_buffer)
entry.update()
self.assertEqual(entry.content, test_input)
@@ -113,7 +113,7 @@ class ReadEntryTest(unittest.TestCase):
Line 3"""
self.io_buffer.append_stdin(test_input)
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
entry = ReadEntry(self.test_id, self.test_timestamp, self.io_buffer)
entry.update()
self.assertEqual(entry.content, test_input)

View File

@@ -12,7 +12,7 @@ class ReasoningEntryTest(unittest.TestCase):
def test_initialization(self):
"""Test entry initialization"""
content = "test reasoning"
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
entry = ReasoningEntry(self.test_id, self.test_timestamp, content)
self.assertEqual(entry.content, content)
self.assertEqual(entry.id, self.test_id)
@@ -21,7 +21,7 @@ class ReasoningEntryTest(unittest.TestCase):
def test_update_does_nothing(self):
"""Test that update operation has no effect"""
content = "test reasoning"
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
entry = ReasoningEntry(self.test_id, self.test_timestamp, content)
# Store initial state
initial_content = entry.generate_context().text
@@ -35,7 +35,7 @@ class ReasoningEntryTest(unittest.TestCase):
def test_generate_context(self):
"""Test XML context generation"""
content = "test reasoning"
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
entry = ReasoningEntry(self.test_id, self.test_timestamp, content)
element = entry.generate_context()
@@ -47,14 +47,14 @@ class ReasoningEntryTest(unittest.TestCase):
def test_special_characters(self):
"""Test handling reasoning text with special characters"""
content = "Special reasoning: \n\t\r\'\"\\"
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
entry = ReasoningEntry(self.test_id, self.test_timestamp, content)
element = entry.generate_context()
self.assertEqual(element.text, f"{content}")
def test_empty_content(self):
"""Test handling empty reasoning text"""
entry = ReasoningEntry("", self.test_id, self.test_timestamp)
entry = ReasoningEntry(self.test_id, self.test_timestamp, "")
element = entry.generate_context()
self.assertEqual(element.text, "")
@@ -62,7 +62,7 @@ class ReasoningEntryTest(unittest.TestCase):
def test_large_content(self):
"""Test handling large reasoning text"""
content = "x" * 10000
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
entry = ReasoningEntry(self.test_id, self.test_timestamp, content)
element = entry.generate_context()
self.assertEqual(element.text, f"{content}")
@@ -72,7 +72,7 @@ class ReasoningEntryTest(unittest.TestCase):
content = """Line 1
Line 2
Line 3"""
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
entry = ReasoningEntry(self.test_id, self.test_timestamp, content)
element = entry.generate_context()
self.assertEqual(element.text, f"{content}")

View File

@@ -28,7 +28,7 @@ class RepeatEntryTest(unittest.TestCase):
def test_initialization(self):
"""Test entry initialization"""
script = "echo test"
entry = RepeatEntry(script, self.test_id, self.test_timestamp)
entry = RepeatEntry(self.test_id, self.test_timestamp, script, None, None)
self.assertEqual(entry.script, script)
self.assertEqual(entry.stdout, "")
@@ -40,7 +40,7 @@ class RepeatEntryTest(unittest.TestCase):
def test_successful_execution(self):
"""Test successful script execution"""
script = "echo 'test'"
entry = RepeatEntry(script, self.test_id, self.test_timestamp)
entry = RepeatEntry(self.test_id, self.test_timestamp, script, None, None)
entry.update()
# Verify entry state
@@ -50,7 +50,7 @@ class RepeatEntryTest(unittest.TestCase):
def test_failed_execution(self):
"""Test failed script execution with invalid command"""
entry = RepeatEntry("nonexistentcommand", self.test_id, self.test_timestamp)
entry = RepeatEntry(self.test_id, self.test_timestamp, "nonexistentcommand", None, None)
entry.update()
# Verify entry state
@@ -78,7 +78,7 @@ class RepeatEntryTest(unittest.TestCase):
f'"'
)
entry = RepeatEntry(script, self.test_id, self.test_timestamp)
entry = RepeatEntry(self.test_id, self.test_timestamp, script, None, None)
# Run update multiple times
outputs = []
@@ -92,13 +92,13 @@ class RepeatEntryTest(unittest.TestCase):
def test_generate_context(self):
"""Test XML context generation"""
script = "echo 'test'"
entry = RepeatEntry(script, self.test_id, self.test_timestamp)
entry = RepeatEntry(self.test_id, self.test_timestamp, script, None, None)
entry.update()
element = entry.generate_context()
# Verify XML structure
self.assertEqual(element.tag, "repeat")
self.assertEqual(element.get("id"), self.test_id)
self.assertEqual(element.text, f"{script}")
self.assertEqual(element.text, script)
self.assertEqual(element.get("exit_code"), "0")
stdout_text = element.find("stdout").text
self.assertEqual(stdout_text, "test\n")
@@ -124,7 +124,7 @@ class RepeatEntryTest(unittest.TestCase):
f'"'
)
entry = RepeatEntry(script, self.test_id, self.test_timestamp)
entry = RepeatEntry(self.test_id, self.test_timestamp, script, None, None)
# Execute multiple times and verify output changes
entry.update()

View File

@@ -26,7 +26,7 @@ class SingleEntryTest(unittest.TestCase):
def test_initialization(self):
"""Test entry initialization"""
script = "echo test"
entry = SingleEntry(script, self.test_id, self.test_timestamp)
entry = SingleEntry(self.test_id, self.test_timestamp, script, None, None)
self.assertEqual(entry.script, script)
self.assertEqual(entry.stdout, "")
@@ -40,7 +40,7 @@ class SingleEntryTest(unittest.TestCase):
"""Test successful script execution"""
script = "echo 'test'"
entry = SingleEntry(script, self.test_id, self.test_timestamp)
entry = SingleEntry(self.test_id, self.test_timestamp, script, None, None)
entry.update()
# Verify entry state
@@ -51,7 +51,7 @@ class SingleEntryTest(unittest.TestCase):
def test_failed_execution(self):
"""Test failed script execution with invalid command"""
entry = SingleEntry("nonexistentcommand", self.test_id, self.test_timestamp)
entry = SingleEntry(self.test_id, self.test_timestamp, "nonexistentcommand", None, None)
entry.update()
# Verify entry state
@@ -64,7 +64,7 @@ class SingleEntryTest(unittest.TestCase):
"""Test script that creates a file"""
script = f"echo 'test' > {self.test_filename}"
entry = SingleEntry(script, self.test_id, self.test_timestamp)
entry = SingleEntry(self.test_id, self.test_timestamp, script, None, None)
entry.update()
# Verify file was created and contains expected content
@@ -77,7 +77,7 @@ class SingleEntryTest(unittest.TestCase):
"""Test that script only executes once"""
script = f"echo 'test' >> {self.test_filename}"
entry = SingleEntry(script, self.test_id, self.test_timestamp)
entry = SingleEntry(self.test_id, self.test_timestamp, script, None, None)
# Run update multiple times
entry.update()
@@ -92,7 +92,7 @@ class SingleEntryTest(unittest.TestCase):
def test_generate_context_before_execution(self):
"""Test XML context generation before execution"""
entry = SingleEntry("echo test", self.test_id, self.test_timestamp)
entry = SingleEntry(self.test_id, self.test_timestamp, "echo test", None, None)
element = entry.generate_context()
# Verify XML structure
@@ -109,7 +109,7 @@ class SingleEntryTest(unittest.TestCase):
"""Test XML context generation after execution"""
script = "echo 'test'"
entry = SingleEntry(script, self.test_id, self.test_timestamp)
entry = SingleEntry(self.test_id, self.test_timestamp, script, None, None)
entry.update()
element = entry.generate_context()
@@ -128,7 +128,7 @@ class SingleEntryTest(unittest.TestCase):
"""Test executing multiple commands in one script"""
script = f"echo 'first' > {self.test_filename} && echo 'second' >> {self.test_filename}"
entry = SingleEntry(script, self.test_id, self.test_timestamp)
entry = SingleEntry(self.test_id, self.test_timestamp, script, None, None)
entry.update()
# Verify file contains both lines

View File

@@ -27,7 +27,7 @@ class StopCommandTest(unittest.TestCase):
def test_stop_command_cleanup(self):
"""Test stop command cleans up and removes all entries"""
# Add background process
entry = BackgroundEntry("sleep 10", "test-id", self.test_timestamp)
entry = BackgroundEntry("test-id", self.test_timestamp, "sleep 10")
self.memory.add_entry(entry)
# Wait for process to start and get PID

View File

@@ -123,10 +123,10 @@ class WorkingMemoryTest(unittest.TestCase):
# Add different types of entries
entries = [
ReasoningEntry("test reasoning", "reasoning-1", self.test_timestamp),
ParseErrorEntry("bad content", "error msg", "error-1", self.test_timestamp),
ReadEntry(io_buffer, "read-1", self.test_timestamp),
WriteEntry("test output", io_buffer, "write-1", self.test_timestamp)
ReasoningEntry("reasoning-1", self.test_timestamp, "test reasoning"),
ParseErrorEntry("error-1", self.test_timestamp, "bad content", "error msg"),
ReadEntry("read-1", self.test_timestamp, io_buffer),
WriteEntry("write-1", self.test_timestamp, "test output", io_buffer)
]
for entry in entries:
@@ -161,7 +161,7 @@ class WorkingMemoryTest(unittest.TestCase):
def test_remove_entry_cleanup(self):
"""Test that removing an entry triggers cleanup"""
# Create and start background process
entry = BackgroundEntry("sleep 10", "test-id", self.test_timestamp)
entry = BackgroundEntry("test-id", self.test_timestamp, "sleep 10")
self.memory.add_entry(entry)
# Wait for process to start and get PID
@@ -181,7 +181,7 @@ class WorkingMemoryTest(unittest.TestCase):
# Add multiple background processes
pids = []
for i in range(3):
entry = BackgroundEntry("sleep 10", f"id-{i}", self.test_timestamp)
entry = BackgroundEntry(f"id-{i}", self.test_timestamp, "sleep 10")
self.memory.add_entry(entry)
self.assertTrue(self.wait_for_process_start(entry))
context = entry.generate_context()
@@ -201,7 +201,7 @@ class WorkingMemoryTest(unittest.TestCase):
def test_cleanup_on_memory_deletion(self):
"""Test that deleting memory properly cleans up all entries"""
# Add a background process
entry = BackgroundEntry("sleep 10", "test-id", self.test_timestamp)
entry = BackgroundEntry("test-id", self.test_timestamp, "sleep 10")
self.memory.add_entry(entry)
# Wait for process to start and get PID
@@ -221,7 +221,7 @@ class WorkingMemoryTest(unittest.TestCase):
"""Test getting all entries"""
# Add multiple entries
entries = [
ReasoningEntry(f"content {i}", f"id-{i}", self.test_timestamp)
ReasoningEntry(f"id-{i}", self.test_timestamp, f"content {i}")
for i in range(3)
]
@@ -239,7 +239,7 @@ class WorkingMemoryTest(unittest.TestCase):
def test_get_entries_returns_copy(self):
"""Test that get_entries returns a copy of the list"""
# Add an entry
entry = ReasoningEntry("test content", "test-id", self.test_timestamp)
entry = ReasoningEntry("test-id", self.test_timestamp, "test content")
self.memory.add_entry(entry)
# Get entries and modify the returned list

View File

@@ -14,7 +14,7 @@ class WriteEntryTest(unittest.TestCase):
def test_initialization(self):
"""Test entry initialization"""
content = "test message"
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
entry = WriteEntry(self.test_id, self.test_timestamp, content, self.io_buffer)
self.assertEqual(entry.content, content)
self.assertEqual(entry.id, self.test_id)
@@ -25,7 +25,7 @@ class WriteEntryTest(unittest.TestCase):
def test_single_write(self):
"""Test writing content once"""
content = "test message"
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
entry = WriteEntry(self.test_id, self.test_timestamp, content, self.io_buffer)
# Perform update and verify content was written
entry.update()
@@ -35,7 +35,7 @@ class WriteEntryTest(unittest.TestCase):
def test_multiple_updates(self):
"""Test that content is only written once even with multiple updates"""
content = "test message"
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
entry = WriteEntry(self.test_id, self.test_timestamp, content, self.io_buffer)
# Perform multiple updates
entry.update()
@@ -52,7 +52,7 @@ class WriteEntryTest(unittest.TestCase):
def test_empty_content(self):
"""Test writing empty content"""
entry = WriteEntry("", self.io_buffer, self.test_id, self.test_timestamp)
entry = WriteEntry(self.test_id, self.test_timestamp, "", self.io_buffer)
entry.update()
self.assertEqual(self.io_buffer.get_stdout(), "")
@@ -61,7 +61,7 @@ class WriteEntryTest(unittest.TestCase):
def test_special_characters(self):
"""Test writing content with special characters"""
content = "Special chars: \n\t\r\'\"\\"
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
entry = WriteEntry(self.test_id, self.test_timestamp, content, self.io_buffer)
entry.update()
self.assertEqual(self.io_buffer.get_stdout(), content)
@@ -69,7 +69,7 @@ class WriteEntryTest(unittest.TestCase):
def test_large_content(self):
"""Test writing large content"""
content = "x" * 10000
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
entry = WriteEntry(self.test_id, self.test_timestamp, content, self.io_buffer)
entry.update()
self.assertEqual(self.io_buffer.get_stdout(), content)
@@ -77,7 +77,7 @@ class WriteEntryTest(unittest.TestCase):
def test_generate_context(self):
"""Test XML context generation"""
content = "test message"
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
entry = WriteEntry(self.test_id, self.test_timestamp, content, self.io_buffer)
# Generate context before writing
element = entry.generate_context()
@@ -97,7 +97,7 @@ class WriteEntryTest(unittest.TestCase):
def test_unicode_content(self):
"""Test writing Unicode content"""
content = "Hello 世界 😊"
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
entry = WriteEntry(self.test_id, self.test_timestamp, content, self.io_buffer)
entry.update()
self.assertEqual(self.io_buffer.get_stdout(), content)
@@ -107,7 +107,7 @@ class WriteEntryTest(unittest.TestCase):
content = """Line 1
Line 2
Line 3"""
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
entry = WriteEntry(self.test_id, self.test_timestamp, content, self.io_buffer)
entry.update()
self.assertEqual(self.io_buffer.get_stdout(), content)