-
Notifications
You must be signed in to change notification settings - Fork 12.5k
Expand file tree
/
Copy pathtest_background_tasks.py
More file actions
157 lines (129 loc) · 5.19 KB
/
Copy pathtest_background_tasks.py
File metadata and controls
157 lines (129 loc) · 5.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import copy
import importlib.util
import os
import sys
import tempfile
import time
import types
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
LESSON = ROOT / "s11_background_tasks" / "code.py"
def load_lesson(workdir: Path):
fake_anthropic = types.ModuleType("anthropic")
class FakeAnthropic:
def __init__(self, *args, **kwargs):
self.messages = types.SimpleNamespace(create=None)
fake_dotenv = types.ModuleType("dotenv")
fake_anthropic.Anthropic = FakeAnthropic
fake_dotenv.load_dotenv = lambda override=True: None
previous_modules = {
"anthropic": sys.modules.get("anthropic"),
"dotenv": sys.modules.get("dotenv"),
}
previous_cwd = Path.cwd()
previous_model = os.environ.get("MODEL_ID")
module_name = f"background_tasks_test_{time.time_ns()}"
spec = importlib.util.spec_from_file_location(module_name, LESSON)
module = importlib.util.module_from_spec(spec)
sys.modules["anthropic"] = fake_anthropic
sys.modules["dotenv"] = fake_dotenv
sys.modules[module_name] = module
try:
os.chdir(workdir)
os.environ["MODEL_ID"] = "test-model"
spec.loader.exec_module(module)
return module
finally:
os.chdir(previous_cwd)
if previous_model is None:
os.environ.pop("MODEL_ID", None)
else:
os.environ["MODEL_ID"] = previous_model
for name, previous in previous_modules.items():
if previous is None:
sys.modules.pop(name, None)
else:
sys.modules[name] = previous
def wait_until(predicate, timeout: float = 2.0) -> bool:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
if predicate():
return True
time.sleep(0.01)
return False
def test_s11_keeps_the_s04_kernel_and_adds_one_bash_option():
with tempfile.TemporaryDirectory() as tmp:
lesson = load_lesson(Path(tmp))
assert {tool["name"] for tool in lesson.TOOLS} == {
"bash", "read_file", "write_file", "edit_file", "glob"
}
bash = next(tool for tool in lesson.TOOLS if tool["name"] == "bash")
assert "run_in_background" in bash["input_schema"]["properties"]
assert set(lesson.HOOKS) == {
"UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"
}
assert not hasattr(lesson, "Task")
assert not hasattr(lesson, "MEMORY_DIR")
def test_background_execution_requires_an_explicit_bash_flag():
with tempfile.TemporaryDirectory() as tmp:
lesson = load_lesson(Path(tmp))
assert not lesson.should_run_background("bash", {"command": "npm install"})
assert lesson.should_run_background(
"bash", {"command": "printf ready", "run_in_background": True}
)
assert not lesson.should_run_background(
"write_file", {"run_in_background": True}
)
def test_background_bash_passes_permission_before_dispatch():
with tempfile.TemporaryDirectory() as tmp:
lesson = load_lesson(Path(tmp))
block = types.SimpleNamespace(
id="tool_denied",
name="bash",
input={"command": "rm -rf /tmp/example", "run_in_background": True},
type="tool_use",
)
responses = [
types.SimpleNamespace(stop_reason="tool_use", content=[block]),
types.SimpleNamespace(
stop_reason="end_turn",
content=[types.SimpleNamespace(type="text", text="Denied.")],
),
]
lesson.client.messages.create = lambda **_: responses.pop(0)
history = [{"role": "user", "content": "Delete the directory"}]
lesson.agent_loop(history)
assert not lesson.background_tasks
result = history[2]["content"][0]
assert result["type"] == "tool_result"
assert "Permission denied" in result["content"]
def test_completed_result_is_collected_once_before_a_later_llm_call():
with tempfile.TemporaryDirectory() as tmp:
lesson = load_lesson(Path(tmp))
block = types.SimpleNamespace(
id="tool_ready",
name="bash",
input={"command": "printf ready", "run_in_background": True},
)
task_id = lesson.start_background_task(block)
assert wait_until(
lambda: lesson.background_tasks[task_id]["status"] == "completed"
)
seen_messages = []
def respond(**kwargs):
seen_messages.append(copy.deepcopy(kwargs["messages"]))
return types.SimpleNamespace(
stop_reason="end_turn",
content=[types.SimpleNamespace(type="text", text="Received.")],
)
lesson.client.messages.create = respond
history = [{"role": "user", "content": "Continue"}]
lesson.agent_loop(history)
delivered = str(seen_messages[0])
assert "<task_notification>" in delivered
assert f"<task_id>{task_id}</task_id>" in delivered
assert "<status>completed</status>" in delivered
assert "ready" in delivered
assert lesson.collect_background_results() == []
def test_s11_code_is_ascii():
LESSON.read_text(encoding="ascii")

