Files
sessions/README.md
Myeongseon Choi 7b43de90ad refactor(catalog)+feat(settings): excise Track D residue + add LSP-style project-level override
Two threads landing together because they share the
``Sessions.sublime-settings`` header comment edits.

Track D residue cleanup
-----------------------
v0.6.7 dropped the in-Sublime agent integration (Track D, 2026-04-27)
but left install-flow leftovers behind — now removed:

* ``BUILTIN_MANAGED_REMOTE_EXTENSION_CATALOG`` drops the three
  ``kind="agent"`` rows (``tmux``, ``claude-code``, ``codex-cli``) and
  the ``kind="jupyter"`` row (``jupyterlab``, superseded by
  ``marimo_hosting``). Twelve ``_BUILTIN_BASH_*`` install/remove/probe
  blocks deleted. ``managed_remote_extension_catalog.py`` shrinks
  358 → 182 lines.
* ``rust/crates/local_bridge/src/agent_remote_payload.rs`` (279 lines)
  + ``parse-agent-editor-envelope`` CLI subcommand removed — used
  only by the deleted ``agent_proposal_watcher``; verified zero live
  callers.
* Tests: drop ``test_catalog_contains_jupyter_extension_entry`` and
  ``test_catalog_contains_agent_extension_entries``; ``debugpy``
  ``kind="debugger"`` test stays. ``test_settings_model.py`` builtin
  id assertions trimmed to four entries.
* Comments: ``frozen-experimental`` docstring + matching
  ``Sessions.sublime-settings`` block deleted; ``commands.py``
  ``_managed_extension_project_client_keys_for_spec`` example
  jupyter → debugger; Open-Remote-Terminal docstring drops the "no
  tmux session multiplexing" framing; ``marimo_hosting.py`` drops
  dead ``tmux``-children + ``jupyter_hosting.py`` postmortem
  references.
* Planning: ``AGENT_TMUX_LAYOUT.md`` and ``V0_6_5_REPRO.md`` deleted
  (both reference deleted features); ``BACKLOG.md`` Track D entry,
  ``REVIEW_v0_6_4_DISTRIBUTION_PLAN.md`` Stage 4 obsolete +
  follow-up cleanup section, ``PYTHON_RUST_BOUNDARY.md``
  agent_remote_payload row, ``README.md`` Track D bullet — all
  updated to reflect the 2026-04-30 residue removal.

No backward-compat shim. ``debugpy`` ``kind="debugger"`` row
untouched.

LSP-style project-level override for the on-save pipeline
---------------------------------------------------------
The original Sessions design wired toolchain settings with the same
package → user → ``.sublime-project`` precedence Sublime LSP uses,
and ``merge_sessions_lsp_into_project_data`` already follows that
for the ``settings.LSP`` row writer. The on-save toggle path
(``_effective_sessions_settings_for_remote_python`` →
``load_sessions_settings_from_sublime``) skipped the project layer,
so per-workspace toggling required editing global user settings.

Fix: ``_effective_sessions_settings_for_remote_python`` accepts an
optional ``window`` and overlays
``window.project_data().get("settings", {})`` on top of the user
merge for ``sessions_remote_python_auto_diagnostics_on_save``,
``sessions_remote_python_auto_diagnostics_on_open``, and
``sessions_remote_python_tool_pipeline``. New
``_project_settings_block_for_window`` helper tolerates missing
``project_data`` callable / ``None`` payloads / non-mapping values.
Bool keys reject non-bool values silently (fall through to user);
pipeline runs through ``normalize_remote_python_tool_pipeline``.

All five callers in ``commands_python_pipeline.py`` now pass
``window``; the two listeners (``on_post_save``,
``on_activated_async``) reorder window-resolution before the toggle
check so the project block is consultable when the listener fires.

Six new regression tests in ``test_commands.py`` pin
project-overrides-user / user-wins-when-absent / pipeline-override /
wrong-type-rejected / null-project_data-safe / no-window-legacy.

``Sessions.sublime-settings`` header comment now documents the
precedence chain inline so users discover the
``.sublime-project`` ``"settings"`` block path without code-diving.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 23:45:21 +09:00

17 KiB
Raw Permalink Blame History

Sessions

Sessions is a Sublime Text package and Rust helper toolkit for remote workspaces over SSH.

Current focus:

  • Completed milestones: Phase 06.2 (all closed), Phase 7 - Stability Hardening (closed), Phase 8 - Rust Transport Expansion (closed), Remote LSP integration track (#34, #35, #36, #37 — all closed; local_bridge lsp-stdio, persistent broker attach IPC, session_helper lsp_stdio supervision, URI rewrite + save barrier, host-scoped install with workspace-scoped env/config). See planning/GITEA_ISSUES.md.
  • Open milestones: Phase 9 - Quality Gates & Scale (#10, #32 large-file streaming). #29 (diff-centric review) was reframed in the 2026-04-25 distribution review and is no longer the next feature — see planning/REVIEW_v0_6_4_DISTRIBUTION_PLAN.md and planning/SHIPPED.md. Track D (in-Sublime agent integration) was dropped 2026-04-27 and the residual tmux/claude-code/codex-cli/jupyterlab catalog entries were excised on 2026-04-30 — see planning/BACKLOG.md and planning/SHIPPED.md.
  • Execution order (2026-04, Rust-first): P0.5 stabilization → crate consolidation → artifact publish + manifest/checksum → #24 Rust runtime ownership → #32 large-file → Track G v1 (multi-repo, refs/ fast-path, line-staging polish). #29 diff-centric review/apply is deprioritized, not on this order. Normative detail: planning/GITEA_ISSUES.md (execution priority and schedule), migration waves: planning/PYTHON_RUST_BOUNDARY.md. Distribution-readiness + ownership-migration plan: planning/REVIEW_v0_6_4_DISTRIBUTION_PLAN.md.
  • P0.5 stabilization (2026-04, closed): persistent bridge, download-only helper, reconnect, mirror ignore patterns, save conflict UI, wire contract test coverage (bridge stdout fixtures, binary smoke test, ABI smoke test), stability hardening (prune symlink/permission edges, multi-window dedup, refresh race prevention), remote file auto-reload via periodic stat → revert, LSP-ready on-demand fetch via external path mapper + on_window_command interceptor.
  • SSH config driven workspace selection
  • session-bound helper over SSH stdio
  • local cache with local-host-independent workspace identity
  • formatter and linter execution in the remote environment (baseline + #30 pipeline on save)
  • long-term evolution toward a multi-session agent windowdropped 2026-04-27, residue removed 2026-04-30: the v0.6.0v0.6.7 in-Sublime agent code (agent_tmux, agent_window_layout, agent_switcher_view, agent palette commands) was deleted in v0.6.7; the tmux/claude-code/codex-cli catalog entries and the parallel jupyterlab (kind="jupyter") entry were excised on 2026-04-30. Agents now run in an external terminal that the user manages outside Sublime; marimo replaces in-tree Jupyter hosting. See planning/BACKLOG.md Track D and planning/SHIPPED.md.

Repository layout

  • sublime/: Sublime package code and Python tests
  • rust/: Rust workspace for bridge/helper/shared crates
  • docs/: human-facing project documentation (when present)
  • planning/: roadmap, Gitea issue bootstrap, transport model, Python/Rust boundary, deep-research notes

Planning index (browse on Gitea)

Document Role
planning/GITEA_ISSUES.md Milestones, parent issue text, execution priority
planning/PYTHON_RUST_BOUNDARY.md Python vs Rust split, Rust-first migration waves 05
planning/VSCODE_REMOTE_TRANSPORT_MODEL.md Envelope + logical channels (VS Codealigned)
planning/REMOTE_DEV_MVP_LSP.md Phase 6.2 LSP / tool transport choices
planning/DEEP-RESEARCH-REPORT.md External audit + priority reconciliation (end)
planning/TRACK_G_V1_BIDIRECTIONAL_SYNC.md Track G v1 plan: bidirectional .git sync redesign (op-log + ref snapshot + git bundle, replaces tar-wipe)

Installing In Sublime Text

sublime/ is the actual Sublime package root for this repository.

  • Development install: link or copy sublime/ to Packages/Sessions
  • Current example: Packages/Sessions -> /path/to/sessions/sublime
  • Unsupported layout: linking the repository root directly

The repository root remains a development workspace for Python tooling, Rust crates, docs, and planning files. Sublime does not discover the nested sublime/ directory automatically when the whole repository is linked as one package.

Building A Release Archive

Build a distributable archive from the sublime/ package root with:

uv run python scripts/build_sublime_package.py

The script writes dist/Sessions.sublime-package and excludes development-only artifacts such as tests/ and __pycache__/.

To build the current platform's Rust bridge/helper in release mode and bundle them automatically, run:

uv run python scripts/build_sublime_package.py --bundle-built-rust-binaries

To ship pre-built Rust binaries (or any other release-only files), pass an explicit layout rather than relying on discovery inside sublime/:

  • Repeat --bundle-file SOURCE=PATH_IN_PACKAGE so each PATH_IN_PACKAGE is the zip entry path (POSIX separators, no ..).
  • Or point --bundle-manifest at a JSON file: a list of objects with source and path_in_package strings. Relative source paths resolve from the manifest directory, which keeps CI artifacts and the install tree layout in one place.

Example manifest:

[
  {
    "source": "artifacts/darwin-aarch64/local_bridge",
    "path_in_package": "sessions/bin/local-bridge/darwin-aarch64/local_bridge"
  },
  {
    "source": "artifacts/linux-x86_64/session_helper",
    "path_in_package": "sessions/bin/remote-helper/linux-x86_64/session_helper"
  }
]

Rust Binary Status

Current product policy (local vs remote):

  • Remote host: Linux only. The remote session_helper is fetched by the editor (not the remote): local_bridge downloads the matching binary from the Gitea generic registry into the editor cache, then pushes it to the remote over the existing SSH session. No curl / wget runs on the remote, and no remote cargo build. Binary is keyed by Linux platform tag + workspace semver from rust/Cargo.toml.
  • Local machine (editor side): Linux, macOS, and Windows are supported for running Sublime + this package. For day-to-day development, treat local_bridge as built on that machine (cargo build -p local_bridge, see Development). The plugin discovers rust/target/debug/local_bridge (or .exe on Windows) when no shipped binary is present under sessions/bin/....

Planned packaging (later): pre-built local_bridge per local OS/arch inside release archives; until then, local build is the supported path for macOS and Windows editors.

Current behavior:

  • Persistent bridge session: local_bridge --persistent runs as a long-lived process per host, communicating with session_helper over a single SSH stdio session. Python sends NDJSON request envelopes and receives async responses via a background reader thread. Each request gets a unique monotonic envelope_id to prevent response mis-routing under concurrency.
  • Editor-cache helper resolution: session_helper is downloaded by the editor host (local_bridge) from the Gitea generic registry into the editor cache, then pushed to the remote over the existing SSH session — curl / wget never run on the remote. Identified by workspace semver + Linux platform tag, the remote-side cache lives at $HOME/.cache/sessions/helpers/<revision>/session_helper. If the editor-side download fails, the connection fails explicitly (no cargo build fallback).
  • Required handshake fields: Handshake.remote_home and Handshake.arch are required (no Option, no fallback). The bridge merges helper ensure + launch into a single SSH command to avoid double authentication.
  • Async multiplexer: local_bridge acts as an async multiplexer with a background mirror thread performing BFS via session_helper. Python sends commands and manages async I/O; file opens are never blocked by mirror progress.
  • Reconnect: SessionsReconnectCurrentWorkspaceCommand runs in a background thread with ssh_prompt_callback, explicitly resets the bridge, and fails fast on handshake timeout (kills the bridge process immediately instead of returning a broken session).
  • After you open a remote file into the workspace cache, a normal editor save (Cmd+S / Ctrl+S) writes the local cache file and then pushes those bytes to the remote path automatically; Sessions: Save Remote File remains available as an explicit palette action.

Planned end-user install behavior (when releases ship all local binaries):

  • the local local_bridge binary will ship inside the released Sessions package for each supported local platform/architecture
  • the remote session_helper binary is downloaded on demand from the Gitea generic registry by the remote host itself (curl/wget)
  • the uploaded helper path is versioned as $HOME/.cache/sessions/helpers/<revision>/session_helper
  • the bridge rejects helper handshakes whose reported helper version does not match the bridge version, so mixed bridge/helper bundles fail fast
  • end users should not need a repository checkout or Cargo installed

Windows: running Sessions on the editor host

Typical flow when your PC is Windows and the SSH server is Linux:

  1. Sublime Text — Install a current build (ST4). This package is loaded from Packages/Sessions pointing at this repos sublime/ tree (symlink or copy; see Installing In Sublime Text above).
  2. SSH client — Use Windows optional OpenSSH Client or Git for Windows so ssh is on PATH. Put hosts in %USERPROFILE%\.ssh\config (same idea as ~/.ssh/config on Unix).
  3. Rust toolchain — Install rustup for Windows. From the repository root (the directory that contains rust/ and sublime/), run: cargo build --manifest-path rust/Cargo.toml -p local_bridge That produces rust\target\debug\local_bridge.exe, which the package picks up when no shipped binary exists under sessions/bin/local-bridge/windows-*/.
  4. Gitea / registry (if required) — If helper downloads need auth or a non-default host, set the sessions_gitea_* keys in Preferences → Settings for Sessions (see comments in sublime/Sessions.sublime-settings). The Linux remote still performs the download; these settings only affect URL and headers resolved on the editor side.
  5. Use the package — In Sublime, run the Sessions connect flow, pick your Host from SSH config, then open a remote folder. The first connection builds or reuses the persistent local_bridge session; the remote runs session_helper after fetching the Linux artifact.

If bridge commands fail with “local_bridge binary not found”, confirm the Cargo debug output path exists and that you opened a workspace whose sublime/ parent is the repo root (so rust/target/debug resolves correctly).

Troubleshooting: duplicate “Sessions:” commands in the palette

The package ships a single Sessions.sublime-commands file; if every Sessions entry appears twice, Sublime is almost certainly loading two package trees that both contribute commands (merged resources).

Typical causes:

  1. Unpacked + packaged copy — e.g. Packages/Sessions/ (dev link) and Installed Packages/Sessions.sublime-package from Package Control. Remove one: disable the package in Package Control, or delete the shipped archive, or set ignored_packages so only your dev link is active.
  2. Wrong link target — install only sublime/ as Packages/Sessions, not the repository root (see Installing In Sublime Text). A mis-linked tree can lead to confusing layouts; fix the symlink/copy so there is exactly one package root.
  3. Case-insensitive volume duplicates — on Windows or default macOS disks, avoid having two folder names that only differ in case both pointing at the same code.

cargo build --workspace does not register Sublime commands by itself; rebuilding Rust only changes binaries. If duplicates appeared right after a package change, reload the editor (Developer: Reload from Disk / restart) once duplicates are resolved so stale command tables are cleared.

The shipped Sessions.sublime-commands intentionally lists sessions_run_remote_python_tool twice with different args (lint vs format); that is not a duplicate installation.

Troubleshooting: Remote LSP install vs save-time ruff/pyright

Install Remote LSP Server (and the status panel) use the persistent bridges exec/once path to run install/remove scripts and probe_argv on the remote host. That checks that tools such as ruff --version exist in the remote environment.

Save-time diagnostics for mirrored .py files are controlled separately by sessions_remote_python_auto_diagnostics_on_save and sessions_remote_python_tool_pipeline in Sessions.sublime-settings. That pipeline still needs an active bridge session. If you see Rust bridge closed the persistent session (or similar), reconnect before expecting on-save lint to run.

Enable sessions_debug_trace_enabled for <Sublime cache>/Sessions/logs/debug-trace.log (correlates Python bridge.* lines with Rust bridge.rust_* when SESSIONS_BRIDGE_DIAG_LOG is set). After this package version, remote LSP install/remove/probe also emit [Sessions LSP] lines to the console (stderr).

Remote workspace window title

Generated .sublime-project files include a name field such as app [SSH: prod] (remote root basename plus SSH host alias) so the Sublime window title is easier to recognize when several projects are open.

Output panels and remote diagnostics

Sessions output panels (LSP status, tool output) scroll to the end after open so the latest lines stay visible. Remote Python diagnostics from Sessions use add_regions on the mirrored buffer; if underlines from another package (for example Sublime LSP) look stale until a window reload, that is usually editor refresh ordering rather than the remote ruff run being skipped.

Per-project LSP settings are owned by the LSP package you use, not by Sessions: configure them in .sublime-project / project-specific user settings as documented for that package (for example client enablement, server paths, and syntax scopes). Sessions writes/updates sessions_workspace_key and workspace display name, while preserving existing project settings (including settings.LSP) on rematerialize/reconnect. sessions_remote_code_servers in Sessions.sublime-settings is for Sessions transport wiring, not Sublime LSP UI.

Troubleshooting: git status noise (GitSavvy)

The Sessions sidebar mirror adds your workspace cache folder (under Sublimes cache path) to the project. That directory is a plain file tree, not a Git repository. GitSavvy (and similar packages) may still run git status for the current working directory and log errors such as:

fatal: not a git repository (or any of the parent directories): .git

with a stack trace under GitSavvy.sublime-package/.../status_bar.py or git_mixins/status.py. That comes from GitSavvy, not from Sessions own code.

Mitigations:

  1. Turn off GitSavvys status-bar polling (simplest): in Preferences → Package Settings → GitSavvy → Settings, set "git_status_in_status_bar": false. Branch/dirty text disappears globally, but GitSavvys palette commands still work when you are inside a real repo.
  2. Project-only override: if GitSavvy exposes project settings for your build, add the same key under the .sublime-project settings block for Sessions-only windows.
  3. Unrelated console line top level value must be an array: usually means some JSON (often folders in a .sublime-project) is not shaped the way Sublime expects. If it appears right after editing the project file, validate that "folders" is a JSON array of objects.

Sessions does not ship GitSavvy; avoiding non-repo git status spam is a third-party / user-settings concern.

Development

The expected workflow is:

  1. add or refine a skeleton
  2. add or update focused tests
  3. implement the smallest coherent behavior
  4. run the relevant checks
  5. commit only after pre-commit passes