Skip to content

Commit 4d6f274

Browse files
takemi-ohamaclaude
andcommitted
feat(editor): ssh host 自動検出を複数エディタのサーバーディレクトリへ拡張
DEVBASE_EDITOR=cursor / code-insiders 等でも跨ホスト自動検出が効くよう、 ~/.vscode-server に加え ~/.cursor-server / ~/.vscode-server-insiders / ~/.vscodium-server / ~/.windsurf-server を横断し、全 entries.json から最新 mtime のホストを採用する (_detect_ssh_host_from_dirs)。gemini 指摘 / major。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e2747d5 commit 4d6f274

3 files changed

Lines changed: 60 additions & 25 deletions

File tree

docs/user/environment-variables.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ VS Code 公式には「起動時にターミナルを開く」専用設定が無
183183

184184
これを解決するには、ネスト URI `vscode-remote://attached-container+<hex>@ssh-remote+<host>/work/...` を使い、docker ルックアップを ssh 先(コンテナのある Mac)で行わせます。`<host>`**手元 `~/.ssh/config``Host` 別名**(例 `mac2`)で、これは「今の VS Code 接続の authority ラベル」と完全一致する必要があります(ネスト attach は新規 ssh 接続を張らず既存接続を再利用するため。IP や `user@IP` は "Parent authority found without ExecServer" で不可)。
185185

186-
このラベルは VS Code が ssh 先の端末 env に渡さない(`SSH_CONNECTION` は IP のみ)ものの、**devbase は ssh 先(Mac)の `~/.vscode-server` の File History から自動検出**します。よって**通常は設定不要**です。docker context は `docker context show` から自動取得します。
186+
このラベルは VS Code が ssh 先の端末 env に渡さない(`SSH_CONNECTION` は IP のみ)ものの、**devbase は ssh 先(Mac)の VS Code 系サーバーディレクトリ(`~/.vscode-server` / `~/.cursor-server` / `~/.vscode-server-insiders` 等)の File History から自動検出**します`DEVBASE_EDITOR` で cursor 等を使う場合も横断)。よって**通常は設定不要**です。docker context は `docker context show` から自動取得します。
187187

188188
自動検出が外れる場合(複数 ssh-remote ホストを使い分けている等)のみ明示します:
189189

lib/devbase/editor/opener.py

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@
4141
# resource URI 中の ssh-remote authority ラベルを拾う ('+' は URL エンコードで %2B)。
4242
_SSH_REMOTE_RE = re.compile(r"ssh-remote(?:\+|%2[Bb])([A-Za-z0-9._@-]+)")
4343

44+
# ssh host 自動検出で探索する VS Code 系サーバーディレクトリ (DEVBASE_EDITOR で
45+
# code / code-insiders / cursor / vscodium 等を使い分けても拾えるよう横断する)。
46+
_SERVER_DIR_CANDIDATES = (
47+
"~/.vscode-server",
48+
"~/.vscode-server-insiders",
49+
"~/.cursor-server",
50+
"~/.vscodium-server",
51+
"~/.windsurf-server",
52+
)
53+
4454

4555
@dataclass(frozen=True)
4656
class EditorContext:
@@ -327,33 +337,36 @@ def resolve_workdir(environ=None, project_name: Optional[str] = None) -> str:
327337
return f"/work/{repo}" if repo else "/work"
328338

329339

330-
def _detect_ssh_host_from_vscode(vscode_server_dir: str) -> Optional[str]:
331-
"""``~/.vscode-server`` の File History から ssh-remote authority ラベルを推測する。
340+
def _detect_ssh_host_from_dirs(server_dirs) -> Optional[str]:
341+
"""複数の VS Code 系サーバーディレクトリの File History を横断して ssh-remote
342+
authority ラベルを推測する。
332343
333344
Remote-SSH / attached-container 窓で開いたファイルの resource URI が
334-
``data/User/History/*/entries.json`` に ``ssh-remote%2B<host>`` (URL エンコード) /
335-
``ssh-remote+<host>`` 形で残るため、そこから ``<host>`` (= クライアントの接続ラベル。
336-
例 ``mac2``) を回収する。複数ホストが見つかった場合は **最後に使われた (entries.json
337-
の mtime が最新の) ホスト**を返す。見つからなければ None。
345+
``<server>/data/User/History/*/entries.json`` に ``ssh-remote%2B<host>`` (URL
346+
エンコード) / ``ssh-remote+<host>`` 形で残るため、そこから ``<host>`` (= クライアントの
347+
接続ラベル。例 ``mac2``) を回収する。
348+
349+
全ディレクトリの ``entries.json`` 候補を **mtime 降順**で集め、**新しい方から 1 ファイル
350+
ずつ読み、最初に ssh-remote ホストが見つかった時点で即 return** する (History が数千
351+
ファイルに膨れても全読み込みを避け、devbase up の遅延を防ぐ)。mtime 収集は stat のみで安価。
352+
見つからなければ None。
338353
339354
.. note:: VS Code 内部データ依存のヒューリスティックで、バージョン差や multi-host 運用で
340355
外し得る。確実性が要る場合は ``DEVBASE_EDITOR_SSH_HOST`` を明示する (本関数より優先)。
341356
"""
342-
history = os.path.join(vscode_server_dir, "data", "User", "History")
343-
if not os.path.isdir(history):
344-
return None
345-
# entries.json 候補を mtime 降順で集め、**新しい方から 1 ファイルずつ読み、最初に
346-
# ssh-remote ホストが見つかった時点で即 return** する (History が数千ファイルに
347-
# 膨れても全読み込みを避け、devbase up の遅延を防ぐ)。mtime 収集は stat のみで安価。
348-
candidates = []
349-
for root, _dirs, files in os.walk(history):
350-
if "entries.json" not in files: # resource authority は entries.json に載る
351-
continue
352-
path = os.path.join(root, "entries.json")
353-
try:
354-
candidates.append((os.path.getmtime(path), path))
355-
except OSError:
357+
candidates = [] # (mtime, path)
358+
for base in server_dirs:
359+
history = os.path.join(base, "data", "User", "History")
360+
if not os.path.isdir(history):
356361
continue
362+
for root, _dirs, files in os.walk(history):
363+
if "entries.json" not in files: # resource authority は entries.json に載る
364+
continue
365+
path = os.path.join(root, "entries.json")
366+
try:
367+
candidates.append((os.path.getmtime(path), path))
368+
except OSError:
369+
continue
357370
for _mtime, path in sorted(candidates, key=lambda t: t[0], reverse=True):
358371
try:
359372
with open(path, encoding="utf-8", errors="ignore") as f:
@@ -366,29 +379,40 @@ def _detect_ssh_host_from_vscode(vscode_server_dir: str) -> Optional[str]:
366379
return None
367380

368381

382+
def _detect_ssh_host_from_vscode(vscode_server_dir: str) -> Optional[str]:
383+
"""単一サーバーディレクトリ版 (:func:`_detect_ssh_host_from_dirs` の薄ラッパ)。"""
384+
return _detect_ssh_host_from_dirs([vscode_server_dir])
385+
386+
369387
def resolve_editor_ssh_host(environ=None,
370388
vscode_server_dir: Optional[str] = None) -> Optional[str]:
371389
"""Remote-SSH ネスト URI 用の ssh ホスト名 (authority ラベル) を解決する。
372390
373391
優先順位:
374392
375393
1. ``DEVBASE_EDITOR_SSH_HOST`` 明示 (最優先・確実)
376-
2. ``~/.vscode-server`` の File History からの自動推測
377-
(:func:`_detect_ssh_host_from_vscode`)
394+
2. VS Code 系サーバーディレクトリ (``~/.vscode-server`` / ``~/.cursor-server`` /
395+
``~/.vscode-server-insiders`` 等) の File History からの自動推測
396+
(:func:`_detect_ssh_host_from_dirs`)
378397
379398
ネスト attach は新規 ssh 接続を張らず **既存 Remote-SSH 接続 (ExecServer) の authority
380399
ラベルと完全一致**する必要がある (実機確認: IP / user@IP は "Parent authority found
381400
without ExecServer" で不可)。そのラベル (例 ``mac2``) はクライアント側の名前で SSH_CONNECTION
382401
等の env には現れない (IP のみ) ため、自動取得は VS Code が残す痕跡からの回収に頼る。
383402
どちらでも得られなければ None で :func:`build_attach_uri` はフラット URI に degrade する。
403+
404+
``vscode_server_dir`` はテスト用の単一ディレクトリ差し替え口 (指定時はそれだけを探索)。
384405
"""
385406
env = os.environ if environ is None else environ
386407
explicit = env.get("DEVBASE_EDITOR_SSH_HOST")
387408
if explicit and explicit.strip():
388409
return explicit.strip()
389-
base = vscode_server_dir or os.path.expanduser("~/.vscode-server")
410+
if vscode_server_dir is not None:
411+
server_dirs = [vscode_server_dir]
412+
else:
413+
server_dirs = [os.path.expanduser(d) for d in _SERVER_DIR_CANDIDATES]
390414
try:
391-
return _detect_ssh_host_from_vscode(base)
415+
return _detect_ssh_host_from_dirs(server_dirs)
392416
except Exception: # noqa: BLE001 - 自動推測失敗で up を倒さない
393417
return None
394418

tests/editor/test_opener.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,17 @@ def test_resolve_editor_ssh_host_autodetect_none_when_absent(tmp_path):
211211
{}, vscode_server_dir=str(tmp_path / "nope")) is None
212212

213213

214+
def test_detect_ssh_host_across_multiple_server_dirs(tmp_path):
215+
"""cursor-server / vscode-server を横断し最新 mtime のホストを返す。"""
216+
vsc = str(tmp_path / ".vscode-server")
217+
cur = str(tmp_path / ".cursor-server")
218+
old = _write_history(vsc, "a", "ssh-remote%2BmacOLD/work")
219+
new = _write_history(cur, "b", "ssh-remote%2BmacNEW/work")
220+
os.utime(old, (1000, 1000))
221+
os.utime(new, (2000, 2000))
222+
assert opener._detect_ssh_host_from_dirs([vsc, cur]) == "macNEW"
223+
224+
214225
def test_resolve_editor_ssh_host_explicit_beats_autodetect(tmp_path):
215226
base = str(tmp_path / ".vscode-server")
216227
_write_history(base, "a", "ssh-remote%2Bauto/work")

0 commit comments

Comments
 (0)