fix(opencode): debug paths runs once per binary and home in a process (Issue #60, Linux item 6)
Every run appends a line to opencode's log, and one discover resolved the database once per candidate. A successful answer is kept per (binary, HOME, XDG_DATA_HOME) - the values the run sees; a failed run is not remembered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ac5f1154c7
commit
614ab1420f
2 files changed
+62
-1
No files matched your search
@@ -19,6 +19,9 @@ def _ro_connect(db: str):
|
||||
return sqlite3.connect(f"file:{quote(path)}?mode=ro" + ("" if both else "&immutable=1"), uri=True)
|
||||
|
||||
|
||||
# (binary, HOME, XDG_DATA_HOME) -> the db `debug paths` printed for them.
|
||||
_DB_PATHS = {}
|
||||
|
||||
_PROVIDER_ENV_VARS = (
|
||||
'ANTHROPIC_API_KEY',
|
||||
'OPENAI_API_KEY',
|
||||
@@ -126,13 +129,23 @@ class OpenCodeAgentAdapter(BaseAgentAdapter):
|
||||
def _resolve_db(self, ctx: DiscoveryContext) -> Optional[str]:
|
||||
# opencode 2.x prints its paths with `debug paths` (the 1.x `db path`
|
||||
# is gone: it prints the help and exits 0). Fallback: the default.
|
||||
# Each run writes a line to opencode's log, so a process asks once
|
||||
# per (binary, HOME, XDG_DATA_HOME) the run would see (Issue #60).
|
||||
bin_path = self._bin(ctx)
|
||||
if ctx.home_dir:
|
||||
key = (bin_path, ctx.home_dir, self._data_home(ctx))
|
||||
else:
|
||||
key = (bin_path, os.environ.get("HOME"), os.environ.get("XDG_DATA_HOME"))
|
||||
if bin_path and key in _DB_PATHS:
|
||||
return _DB_PATHS[key]
|
||||
try:
|
||||
res = self._run(ctx, ["debug", "paths"], timeout=5)
|
||||
if res is not None and res.returncode == 0:
|
||||
for line in res.stdout.splitlines():
|
||||
parts = line.split(None, 1)
|
||||
if len(parts) == 2 and parts[0] == "db":
|
||||
return parts[1].strip()
|
||||
_DB_PATHS[key] = parts[1].strip()
|
||||
return _DB_PATHS[key]
|
||||
except Exception:
|
||||
pass
|
||||
return os.path.join(self._data_home(ctx), "opencode", "opencode.db")
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
5. The adapter's queries neither create nor remove anything beside
|
||||
opencode's database (a read-write open checkpoints the WAL and deletes
|
||||
the sidecars on close; a plain mode=ro open creates missing ones).
|
||||
6. `debug paths` (which writes a line to opencode's log) runs once per
|
||||
(binary, HOME, XDG_DATA_HOME) in a process.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -28,6 +30,8 @@ from lib_py import stall_watch # noqa: E402
|
||||
from lib_py.agents.base import DiscoveryContext # noqa: E402
|
||||
from lib_py.agents.input_region import extract_input_region # noqa: E402
|
||||
from lib_py.agents.registry import get_adapter # noqa: E402
|
||||
from lib_py.agents.adapters import opencode as opencode_mod # noqa: E402
|
||||
import _fake_opencode # noqa: E402
|
||||
|
||||
# 140 columns (herdr capture) and 60 columns, where the example loses its
|
||||
# closing quote.
|
||||
@@ -35,6 +39,11 @@ HOME_SCREENS = ("opencode_idle_2x_home.txt", "opencode_idle_2x_home_narrow.txt")
|
||||
PLACEHOLDER_LINE = re.compile(r'^(\s*┃\s*)Ask anything… ".*$', re.M)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_cached_db_paths(monkeypatch):
|
||||
monkeypatch.setattr(opencode_mod, "_DB_PATHS", {}, raising=False)
|
||||
|
||||
|
||||
def _pane(name: str) -> str:
|
||||
return "".join(l for l in (PANES / name).read_text(encoding="utf-8").splitlines(True) if not l.startswith("# "))
|
||||
|
||||
@@ -165,3 +174,42 @@ def test_a_query_reads_right_and_leaves_the_sidecars_alone(wal_home, state, list
|
||||
run, expected = QUERIES[query]
|
||||
assert run(adapter, ctx, db) == expected
|
||||
assert sorted(os.listdir(os.path.dirname(db))) == listing
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. `debug paths` once per (binary, HOME, XDG_DATA_HOME)
|
||||
# ---------------------------------------------------------------------------
|
||||
def test_debug_paths_runs_once_per_home(tmp_path, monkeypatch):
|
||||
log = tmp_path / "calls.log"
|
||||
fake = _fake_opencode.write(tmp_path / "fakebin" / "opencode-real")
|
||||
wrapper = tmp_path / "fakebin" / "opencode"
|
||||
wrapper.write_text(f'#!/bin/sh\necho "$*" >> "{log}"\nexec "{fake}" "$@"\n')
|
||||
wrapper.chmod(0o755)
|
||||
adapter = get_adapter("opencode")
|
||||
monkeypatch.setattr(adapter, "_bin", lambda ctx: str(wrapper))
|
||||
homes = [tmp_path / "home1", tmp_path / "home2"]
|
||||
ctxs = [DiscoveryContext(workspace=str(tmp_path), agent_name="opencode", home_dir=str(h)) for h in homes]
|
||||
|
||||
def calls():
|
||||
return log.read_text().splitlines() if log.exists() else []
|
||||
|
||||
want = [str(h / ".local" / "share" / "opencode" / "opencode.db") for h in homes]
|
||||
assert [adapter._resolve_db(ctxs[0]) for _ in range(2)] == [want[0]] * 2
|
||||
assert calls() == ["debug paths"]
|
||||
assert adapter._resolve_db(ctxs[1]) == want[1]
|
||||
assert adapter._resolve_db(ctxs[0]) == want[0]
|
||||
assert calls() == ["debug paths"] * 2
|
||||
|
||||
|
||||
def test_a_failed_debug_paths_is_not_remembered(tmp_path, monkeypatch):
|
||||
log = tmp_path / "calls.log"
|
||||
failing = tmp_path / "fakebin" / "opencode"
|
||||
failing.parent.mkdir()
|
||||
failing.write_text(f'#!/bin/sh\necho "$*" >> "{log}"\nexit 3\n')
|
||||
failing.chmod(0o755)
|
||||
adapter = get_adapter("opencode")
|
||||
monkeypatch.setattr(adapter, "_bin", lambda ctx: str(failing))
|
||||
ctx = DiscoveryContext(workspace=str(tmp_path), agent_name="opencode", home_dir=str(tmp_path / "home"))
|
||||
default = str(tmp_path / "home" / ".local" / "share" / "opencode" / "opencode.db")
|
||||
assert [adapter._resolve_db(ctx) for _ in range(2)] == [default] * 2
|
||||
assert log.read_text().splitlines() == ["debug paths"] * 2
|
||||
Reference in new issue
Block a user