173 lines
5.8 KiB
Python
173 lines
5.8 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
from opensquilla.engine.agent import Agent
|
|
from opensquilla.engine.runtime_state_capsule import (
|
|
build_runtime_state_capsule,
|
|
runtime_state_capsule_message,
|
|
)
|
|
from opensquilla.engine.turn_runner.agent_bootstrap_stage import (
|
|
_runtime_state_capsule_mode_from_env,
|
|
)
|
|
from opensquilla.engine.types import AgentConfig
|
|
from opensquilla.provider.types import Message
|
|
from opensquilla.tools.mutation_receipts import (
|
|
fingerprint_file,
|
|
record_semantic_mutation_receipt,
|
|
)
|
|
from opensquilla.tools.types import ToolContext
|
|
|
|
|
|
def _init_git_workspace(path: Path) -> None:
|
|
subprocess.run(["git", "init"], cwd=path, check=True, stdout=subprocess.DEVNULL)
|
|
subprocess.run(
|
|
["git", "config", "user.email", "test@example.com"],
|
|
cwd=path,
|
|
check=True,
|
|
)
|
|
subprocess.run(["git", "config", "user.name", "Test User"], cwd=path, check=True)
|
|
|
|
|
|
def _commit_file(workspace: Path, relative_path: str, text: str) -> Path:
|
|
target = workspace / relative_path
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
target.write_text(text, encoding="utf-8")
|
|
subprocess.run(["git", "add", relative_path], cwd=workspace, check=True)
|
|
subprocess.run(["git", "commit", "-m", "initial"], cwd=workspace, check=True)
|
|
return target
|
|
|
|
|
|
def test_runtime_state_capsule_reports_source_diff_and_last_mutation(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
workspace = tmp_path / "workspace"
|
|
workspace.mkdir()
|
|
_init_git_workspace(workspace)
|
|
source = _commit_file(workspace, "src/app.py", "print('base')\n")
|
|
ctx = ToolContext(workspace_dir=str(workspace), workspace_epoch=0)
|
|
|
|
before = fingerprint_file(source)
|
|
source.write_text("print('changed')\n", encoding="utf-8")
|
|
after = fingerprint_file(source)
|
|
record_semantic_mutation_receipt(
|
|
tool_name="edit_source",
|
|
path=source,
|
|
operation="edit_source",
|
|
before=before,
|
|
after=after,
|
|
partial=False,
|
|
ctx=ctx,
|
|
)
|
|
|
|
capsule = build_runtime_state_capsule(workspace=workspace, tool_context=ctx)
|
|
|
|
assert capsule["schema"] == "runtime_state_capsule_v1"
|
|
assert capsule["workspace"]["epoch"] == 1
|
|
assert capsule["workspace"]["source_diff"] is True
|
|
assert capsule["workspace"]["source_paths"] == ["src/app.py"]
|
|
assert capsule["workspace"]["scratch_only"] is False
|
|
assert capsule["last_mutation"] == {
|
|
"tool": "edit_source",
|
|
"operation": "edit_source",
|
|
"path": "src/app.py",
|
|
"classification": "source",
|
|
"changed": True,
|
|
"partial": False,
|
|
"workspace_epoch": 1,
|
|
}
|
|
|
|
|
|
def test_runtime_state_capsule_message_is_stable_json(tmp_path: Path) -> None:
|
|
workspace = tmp_path / "workspace"
|
|
workspace.mkdir()
|
|
_init_git_workspace(workspace)
|
|
_commit_file(workspace, "src/app.py", "print('base')\n")
|
|
ctx = ToolContext(workspace_dir=str(workspace), workspace_epoch=0)
|
|
|
|
message = runtime_state_capsule_message(
|
|
build_runtime_state_capsule(workspace=workspace, tool_context=ctx)
|
|
)
|
|
|
|
prefix = "Runtime state capsule:\n"
|
|
assert message.startswith(prefix)
|
|
payload = json.loads(message.removeprefix(prefix))
|
|
assert payload["schema"] == "runtime_state_capsule_v1"
|
|
assert payload["workspace"]["source_diff"] is False
|
|
|
|
|
|
def test_agent_runtime_state_capsule_injection_is_opt_in(tmp_path: Path) -> None:
|
|
workspace = tmp_path / "workspace"
|
|
workspace.mkdir()
|
|
_init_git_workspace(workspace)
|
|
source = _commit_file(workspace, "src/app.py", "print('base')\n")
|
|
ctx = ToolContext(workspace_dir=str(workspace), workspace_epoch=0)
|
|
|
|
before = fingerprint_file(source)
|
|
source.write_text("print('changed')\n", encoding="utf-8")
|
|
after = fingerprint_file(source)
|
|
record_semantic_mutation_receipt(
|
|
tool_name="edit_source",
|
|
path=source,
|
|
operation="edit_source",
|
|
before=before,
|
|
after=after,
|
|
partial=False,
|
|
ctx=ctx,
|
|
)
|
|
|
|
agent = Agent(
|
|
provider=None, # type: ignore[arg-type]
|
|
config=AgentConfig(
|
|
workspace_dir=str(workspace),
|
|
runtime_state_capsule_mode="inject",
|
|
),
|
|
tool_context=ctx,
|
|
)
|
|
request_messages, _sanitize = agent._provider_request_messages_with_sanitize(
|
|
[Message(role="user", content="fix this")],
|
|
request_context_message=None,
|
|
request_context_insert_index=1,
|
|
runtime_context_message=Agent._runtime_context_message("runtime"),
|
|
runtime_context_insert_index=1,
|
|
)
|
|
|
|
assert any(
|
|
isinstance(message.content, str)
|
|
and message.content.startswith("Runtime state capsule:\n")
|
|
for message in request_messages
|
|
)
|
|
|
|
off_agent = Agent(
|
|
provider=None, # type: ignore[arg-type]
|
|
config=AgentConfig(workspace_dir=str(workspace), runtime_state_capsule_mode="off"),
|
|
tool_context=ctx,
|
|
)
|
|
off_messages, _sanitize = off_agent._provider_request_messages_with_sanitize(
|
|
[Message(role="user", content="fix this")],
|
|
request_context_message=None,
|
|
request_context_insert_index=1,
|
|
runtime_context_message=Agent._runtime_context_message("runtime"),
|
|
runtime_context_insert_index=1,
|
|
)
|
|
|
|
assert not any(
|
|
isinstance(message.content, str)
|
|
and message.content.startswith("Runtime state capsule:\n")
|
|
for message in off_messages
|
|
)
|
|
|
|
|
|
def test_runtime_state_capsule_mode_env_parser(monkeypatch) -> None:
|
|
monkeypatch.setenv("OPENSQUILLA_RUNTIME_STATE_CAPSULE_MODE", "inject")
|
|
|
|
assert _runtime_state_capsule_mode_from_env("off") == "inject"
|
|
|
|
monkeypatch.setenv("OPENSQUILLA_RUNTIME_STATE_CAPSULE_MODE", "bad")
|
|
assert _runtime_state_capsule_mode_from_env("inject") == "off"
|
|
|
|
monkeypatch.delenv("OPENSQUILLA_RUNTIME_STATE_CAPSULE_MODE")
|
|
assert _runtime_state_capsule_mode_from_env("log") == "log"
|