aacf0a212a
┌──────────────────┬───────────────────────────┬───────────────────────────────────────────────┐ │ Category │ npm Source │ Items │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ Product metadata │ product.ts │ URLs, base URLs │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ System prefixes │ system.ts │ 3 prompt prefixes │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ Cyber risk │ cyberRiskInstruction.ts │ Safety instruction │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ API limits │ apiLimits.ts │ 10 image/PDF/media limits │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ Tool limits │ toolLimits.ts │ 6 result size constants │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ Spinner verbs │ spinnerVerbs.ts │ 187 whimsical gerunds │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ Completion verbs │ turnCompletionVerbs.ts │ 8 past-tense verbs │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ Figures/symbols │ figures.ts │ 25 Unicode UI symbols │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ XML tags │ xml.ts │ 30+ tag constants │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ Messages │ messages.ts │ NO_CONTENT_MESSAGE │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ Date utilities │ common.ts │ 4 functions │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ Section caching │ systemPromptSections.ts │ Memoized/volatile sections │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ Output styles │ outputStyles.ts │ 3 built-in configs │ ├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤ │ Prompt helpers │ prompts.ts │ Knowledge cutoff, language, scratchpad, hooks │ └──────────────────┴───────────────────────────┴───────────────────────────────────────────────┘ 91 new tests in tests/test_prompt_constants.py. All 17 SQL todos done.
300 lines
12 KiB
Python
300 lines
12 KiB
Python
"""Tests for /files, /copy, /export, /stats, /tag, /rename, /branch, /effort, /doctor."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from src.agent_runtime import LocalCodingAgent
|
|
from src.agent_session import AgentMessage, AgentSessionState
|
|
from src.agent_types import AgentRuntimeConfig, ModelConfig, UsageStats
|
|
|
|
|
|
def _make_agent(tmp_dir: str) -> LocalCodingAgent:
|
|
return LocalCodingAgent(
|
|
model_config=ModelConfig(model='test-model'),
|
|
runtime_config=AgentRuntimeConfig(cwd=Path(tmp_dir)),
|
|
)
|
|
|
|
|
|
def _set_session(agent: LocalCodingAgent, messages: list[AgentMessage]) -> None:
|
|
session = AgentSessionState(
|
|
system_prompt_parts=('You are a helper.',),
|
|
messages=messages,
|
|
)
|
|
agent.last_session = session
|
|
|
|
|
|
class TestFilesCommand(unittest.TestCase):
|
|
def test_no_session(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/files')
|
|
self.assertIn('No active session', result.final_output)
|
|
|
|
def test_no_files_in_context(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
_set_session(agent, [
|
|
AgentMessage(role='user', content='Hello'),
|
|
AgentMessage(role='assistant', content='Hi'),
|
|
])
|
|
result = agent.run('/files')
|
|
self.assertIn('No files loaded', result.final_output)
|
|
|
|
def test_files_from_tool_calls(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
_set_session(agent, [
|
|
AgentMessage(role='user', content='Read main.py'),
|
|
AgentMessage(
|
|
role='assistant', content='',
|
|
tool_calls=(
|
|
{'id': 'tc1', 'type': 'function', 'function': {
|
|
'name': 'Read',
|
|
'arguments': json.dumps({'file_path': '/home/user/project/main.py'}),
|
|
}},
|
|
),
|
|
),
|
|
AgentMessage(
|
|
role='tool', content='print("hello")',
|
|
name='Read',
|
|
tool_call_id='tc1',
|
|
metadata={'path': '/home/user/project/main.py'},
|
|
),
|
|
])
|
|
result = agent.run('/files')
|
|
self.assertIn('Files in context', result.final_output)
|
|
self.assertIn('main.py', result.final_output)
|
|
|
|
|
|
class TestCopyCommand(unittest.TestCase):
|
|
def test_no_session(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/copy')
|
|
self.assertIn('No active session', result.final_output)
|
|
|
|
def test_no_assistant_messages(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
_set_session(agent, [
|
|
AgentMessage(role='user', content='Hello'),
|
|
])
|
|
result = agent.run('/copy')
|
|
self.assertIn('No assistant responses', result.final_output)
|
|
|
|
def test_copies_latest_response(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
_set_session(agent, [
|
|
AgentMessage(role='user', content='Hello'),
|
|
AgentMessage(role='assistant', content='First response.'),
|
|
AgentMessage(role='user', content='More'),
|
|
AgentMessage(role='assistant', content='Second response with details.'),
|
|
])
|
|
result = agent.run('/copy')
|
|
self.assertIn('Copied', result.final_output)
|
|
self.assertIn('response.md', result.final_output)
|
|
# Verify the file was written
|
|
tmp_file = Path(tempfile.gettempdir()) / 'claw-code' / 'response.md'
|
|
self.assertTrue(tmp_file.exists())
|
|
content = tmp_file.read_text()
|
|
self.assertEqual(content, 'Second response with details.')
|
|
|
|
def test_copies_nth_response(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
_set_session(agent, [
|
|
AgentMessage(role='user', content='Hello'),
|
|
AgentMessage(role='assistant', content='First.'),
|
|
AgentMessage(role='user', content='More'),
|
|
AgentMessage(role='assistant', content='Second.'),
|
|
])
|
|
result = agent.run('/copy 1')
|
|
tmp_file = Path(tempfile.gettempdir()) / 'claw-code' / 'response.md'
|
|
content = tmp_file.read_text()
|
|
self.assertEqual(content, 'First.')
|
|
|
|
|
|
class TestExportCommand(unittest.TestCase):
|
|
def test_no_session(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/export')
|
|
self.assertIn('No active session', result.final_output)
|
|
|
|
def test_exports_with_auto_filename(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
_set_session(agent, [
|
|
AgentMessage(role='user', content='Hello'),
|
|
AgentMessage(role='assistant', content='Hi there'),
|
|
])
|
|
result = agent.run('/export')
|
|
self.assertIn('Exported 2 messages', result.final_output)
|
|
self.assertIn('.txt', result.final_output)
|
|
|
|
def test_exports_with_custom_filename(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
_set_session(agent, [
|
|
AgentMessage(role='user', content='Hello'),
|
|
AgentMessage(role='assistant', content='Hi'),
|
|
])
|
|
result = agent.run('/export my_chat')
|
|
self.assertIn('my_chat.txt', result.final_output)
|
|
out_file = Path(tmp) / 'my_chat.txt'
|
|
self.assertTrue(out_file.exists())
|
|
content = out_file.read_text()
|
|
self.assertIn('Hello', content)
|
|
self.assertIn('Hi', content)
|
|
|
|
|
|
class TestStatsCommand(unittest.TestCase):
|
|
def test_shows_statistics(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
_set_session(agent, [
|
|
AgentMessage(role='user', content='Hello'),
|
|
AgentMessage(role='assistant', content='Hi'),
|
|
AgentMessage(role='user', content='Question'),
|
|
])
|
|
agent.cumulative_usage = UsageStats(input_tokens=500, output_tokens=200)
|
|
result = agent.run('/stats')
|
|
self.assertIn('Session Statistics', result.final_output)
|
|
self.assertIn('3 total', result.final_output)
|
|
self.assertIn('2 user', result.final_output)
|
|
self.assertIn('1 assistant', result.final_output)
|
|
self.assertIn('500', result.final_output)
|
|
self.assertIn('200', result.final_output)
|
|
|
|
|
|
class TestTagCommand(unittest.TestCase):
|
|
def test_no_tags_initially(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/tag')
|
|
self.assertIn('No tags set', result.final_output)
|
|
|
|
def test_add_tag(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/tag important')
|
|
self.assertIn('Added tag: important', result.final_output)
|
|
|
|
def test_toggle_tag(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
agent.run('/tag my-tag')
|
|
result = agent.run('/tag my-tag')
|
|
self.assertIn('Removed tag: my-tag', result.final_output)
|
|
|
|
def test_list_tags(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
agent.run('/tag alpha')
|
|
agent.run('/tag beta')
|
|
result = agent.run('/tag')
|
|
self.assertIn('alpha', result.final_output)
|
|
self.assertIn('beta', result.final_output)
|
|
|
|
|
|
class TestRenameCommand(unittest.TestCase):
|
|
def test_no_name(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/rename')
|
|
self.assertIn('Usage', result.final_output)
|
|
|
|
def test_rename_session(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/rename My Cool Session')
|
|
self.assertIn('renamed to: My Cool Session', result.final_output)
|
|
|
|
|
|
class TestBranchCommand(unittest.TestCase):
|
|
def test_no_session(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/branch')
|
|
self.assertIn('No active session', result.final_output)
|
|
|
|
def test_branch_with_session(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
_set_session(agent, [
|
|
AgentMessage(role='user', content='Hello'),
|
|
AgentMessage(role='assistant', content='Hi'),
|
|
])
|
|
result = agent.run('/branch my-feature')
|
|
self.assertIn('Created branch "my-feature"', result.final_output)
|
|
self.assertIn('Saved to:', result.final_output)
|
|
|
|
def test_branch_auto_name(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
_set_session(agent, [
|
|
AgentMessage(role='user', content='Hello'),
|
|
])
|
|
result = agent.run('/branch')
|
|
self.assertIn('Created branch "branch-', result.final_output)
|
|
|
|
|
|
class TestEffortCommand(unittest.TestCase):
|
|
def test_show_current_effort(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/effort')
|
|
self.assertIn('Current effort level: auto', result.final_output)
|
|
|
|
def test_set_effort_level(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/effort high')
|
|
self.assertIn('Set effort level to: high', result.final_output)
|
|
|
|
def test_invalid_effort_level(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/effort extreme')
|
|
self.assertIn('Invalid effort level', result.final_output)
|
|
|
|
def test_all_valid_levels(self) -> None:
|
|
for level in ('low', 'medium', 'high', 'max', 'auto'):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run(f'/effort {level}')
|
|
self.assertIn(f'Set effort level to: {level}', result.final_output)
|
|
|
|
|
|
class TestDoctorCommand(unittest.TestCase):
|
|
def test_shows_report(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/doctor')
|
|
output = result.final_output
|
|
self.assertIn('Doctor Report', output)
|
|
self.assertIn('Python version', output)
|
|
self.assertIn('git', output)
|
|
self.assertIn('Model', output)
|
|
self.assertIn('Working directory', output)
|
|
|
|
def test_detects_claude_md(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
(Path(tmp) / 'CLAUDE.md').write_text('memory file')
|
|
agent = _make_agent(tmp)
|
|
result = agent.run('/doctor')
|
|
self.assertIn('CLAUDE.md', result.final_output)
|
|
self.assertIn('found', result.final_output)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|