Audience:
contributor·maintainer·integrator(internals)TL;DR: How the standalone TCP DAP server wraps
SorobanDebugSession— one session per connection over a socket — plus the backend-selector constructor and the per-connection teardown that keeps a dropped connection from leaking akomet-nodeprocess. User-facing usage is indap-cli.md. Thevscode-free shared core (backendFor/buildStopModel/pcAtIndex) is documented intrace-cli-internal.md.
The server reuses the exact DAP handlers and stepping engine (S1–S20, see
stepping.md) the VS Code extension runs — it only changes the
transport from an in-process inline adapter to a TCP socket.
flowchart TB
C["DAP client<br/>VS Code · nvim-dap · IntelliJ · Emacs"]
C -->|"TCP socket"| SRV["net.createServer"]
SRV -->|"one per connection"| SDS["SorobanDebugSession(backendFor)<br/>DAP handlers + stepping engine"]
SDS --> RESOLVE["backendFor(args) → resolve → ResolvedTrace"]
SDS --> CORE["buildStopModel + pcAtIndex<br/>(shared core)"]
CLOSE["socket close / error"] -->|"session.teardown()"| DISPOSE["backend.dispose()"]
startDapServer({ host?, port, backendFor? }) opens a net.createServer. For each
connection it:
- creates
new SorobanDebugSession(select)whereselectdefaults to the sharedbackendForselector (injectable viaopts.backendForso tests can observe teardown with a spy backend); - calls
session.setRunAsServer(true)(see below); - pipes the DAP wire protocol over the socket with
session.start(socket, socket); - registers
socket.on('close', …)andsocket.on('error', …)→session.teardown().
It resolves the actual listening port (so port: 0 yields an OS-assigned port) and
returns { port, close }, where close() shuts the server down.
-
setRunAsServer(true)is mandatory. The base@vscode/debugadapterDebugSessioncallsprocess.exit(0)roughly 100 ms after any socket closes unless it is in server mode. Without this flag a single client disconnect would kill the whole server process (and every other connection with it). -
teardown(), notshutdown().DebugSession.shutdown()is a no-op in server mode, so it can't dispose the backend.SorobanDebugSession.teardown()is the single, idempotent (disposed-guarded) placebackend.dispose()runs. It is reached both by a cleandisconnectrequest and by the socketclose/errorhandlers — so an abrupt disconnect (editor crash, network drop, SIGKILL) that sends nodisconnectover the wire still tears down theLiveBackend'skomet-nodesubprocess. Thedisposedguard makes the second call after a clean disconnect a harmless no-op.
SorobanDebugSession accepts either a concrete SessionBackend (unchanged; used by
the extension and the stdio test harness) or a selector
(args: SorobanLaunchArgs) => SessionBackend, resolved on the first line of
launchRequest (the backend is unused before then). The TCP server passes the selector
so the backend can depend on the per-connection launch config, which only arrives with
the client's launch request.
Plain request/response HTTP is intentionally not offered: DAP is a bidirectional,
event-driven protocol (async stopped/output/terminated events) that does not fit
HTTP's request/response shape. TCP is DAP's canonical "server mode", supported by every
mainstream DAP client. A WebSocket transport could be added later for browser-fronted
clients.
src/server/main.ts is the thin, coverage-excluded entry: it delegates argv parsing to
the pure parseServerArgs (src/server/cliArgs.ts, unit-tested), starts the server, and
logs the listening address. Help goes to stdout (exit 0); a usage error goes to stderr
(exit 2).