after moving all to cipher

This commit is contained in:
2026-08-19 10:19:57 +00:00
parent 4df62d2609
commit 9abade1a81
100 changed files with 1286 additions and 4335 deletions

View File

@@ -22,6 +22,7 @@ import tempfile
from pathlib import Path
import atif_session
import browser_note
from harbor.agents.installed.claude_code import ClaudeCode
from harbor.models.trial.paths import EnvironmentPaths
@@ -101,7 +102,7 @@ _AGENT_CLI_NOTE_FALLBACK = (
)
def _toolset_note() -> str:
def _toolset_note(with_browser: bool = False, with_read: bool = False) -> str:
"""The toolset note appended to Claude Code's stock ``--print`` system prompt
(via ``--append-system-prompt``) for the canonical reduced toolset.
@@ -118,9 +119,23 @@ def _toolset_note() -> str:
file is missing."""
path = Path(__file__).resolve().parent / "toolset_note.md"
try:
return path.read_text(encoding="utf-8").strip()
note = path.read_text(encoding="utf-8").strip()
except OSError:
return _AGENT_CLI_NOTE_FALLBACK
note = _AGENT_CLI_NOTE_FALLBACK
# Order matters: the Read correction must come AFTER the base note, because it supersedes
# that note's "there are no Read/Grep/Glob tools" line. Shipping the base note alone to a
# Read-enabled agent would be a false statement about its own toolset.
if with_read:
read_note = Path(__file__).resolve().parent / "toolset_note_read.md"
try:
note = f"{note}\n\n{read_note.read_text(encoding='utf-8').strip()}"
except OSError:
_log.warning("toolset_note_read.md missing; Read correction omitted")
if with_browser:
extra = browser_note.browser_note()
if extra:
note = f"{note}\n\n{extra}"
return note
class PreinstalledClaudeCode(ClaudeCode):
@@ -138,6 +153,10 @@ class PreinstalledClaudeCode(ClaudeCode):
in the trial config's ``agent.import_path``.)
"""
# Set by _probe_browser() during install(); read by build_cli_flags(). Declared
# here so the full-toolset subclass (which skips the probe) still has a value.
_has_browser = False
@staticmethod
def name() -> str:
return "claude-code-reduced-toolset"
@@ -176,6 +195,14 @@ class PreinstalledClaudeCode(ClaudeCode):
editor CLI) and reuses ``_ensure_claude_binary`` directly."""
await self._stage_agent_cli(environment)
await self._ensure_claude_binary(environment)
await self._probe_browser(environment)
async def _probe_browser(self, environment) -> None:
"""Record whether this image ships the Playwright `pw` wrapper, so the toolset
note mentions the browser only on images that have one. Runs during install(),
which harbor calls before build_cli_flags() reads the result. The probe and the
note text are shared with the codex adapter via browser_note.py."""
self._has_browser = await browser_note.probe_browser(environment)
async def _ensure_claude_binary(self, environment) -> None:
"""Reuse the claude binary already baked into the task image instead
@@ -243,7 +270,8 @@ class PreinstalledClaudeCode(ClaudeCode):
subclass overrides this back to stock ``ClaudeCode.build_cli_flags``.
"""
flags = super().build_cli_flags()
extra = f"--tools Bash --append-system-prompt {shlex.quote(_toolset_note())}"
note = _toolset_note(with_browser=self._has_browser)
extra = f"--tools Bash --append-system-prompt {shlex.quote(note)}"
return f"{flags} {extra}" if flags else extra
async def _claude_format_session_path(self, environment, env, session_uuid: str) -> str:
@@ -845,6 +873,47 @@ class SnapshotClaudeCode(PreinstalledClaudeCode):
# once every snapshot session.jsonl is re-recorded in the reduced format.
class _BrowserToolsetMixin:
"""The canonical reduced toolset PLUS the ``Read`` built-in, for tasks that opt into a
browser: a screenshot is only useful to an agent that can look at it, and ``Read`` is what
turns a PNG on disk into an image the model actually sees.
This is a SEPARATE AGENT, not a flag, per the rule that the toolset is chosen by which class
harbor runs and the class name records it — so a benchmark row can never silently compare an
agent that could see against one that couldn't.
Two things to be clear-eyed about:
* ``Read`` is not image-only. It also reads text files, PDFs and notebooks, so these tasks
get back a file-reading built-in the reduced toolset deliberately removes. There is no
narrower built-in; an image-only MCP tool was rejected because the canonical agent avoids
MCP (see the async-MCP startup race note at the top of this file).
* Tasks on this agent are not comparable with tasks on the canonical one. That is the point
of the distinct name.
"""
def build_cli_flags(self) -> str:
flags = ClaudeCode.build_cli_flags(self)
note = _toolset_note(with_browser=self._has_browser, with_read=True)
extra = f"--tools Bash,Read --append-system-prompt {shlex.quote(note)}"
return f"{flags} {extra}" if flags else extra
class BrowserPreinstalledClaudeCode(_BrowserToolsetMixin, PreinstalledClaudeCode):
"""Reduced toolset + Read, manual (non-snapshot) tasks."""
@staticmethod
def name() -> str:
return "claude-code-reduced-toolset-browser"
class BrowserSnapshotClaudeCode(_BrowserToolsetMixin, SnapshotClaudeCode):
"""Reduced toolset + Read, snapshot tasks."""
@staticmethod
def name() -> str:
return "snapshot-claude-code-reduced-toolset-browser"
class _FullToolsetMixin:
"""Override the canonical reduced toolset back to Claude Code's stock full
built-in toolset: no str_replace_editor CLI to stage, and no --tools / note.