runa/vscode
Steve Biedermann fcec43d44b fix: complete the CLI WebSocket close handshake
Close the client explicitly on REPL exit and wait for the peer with a bounded deadline. Cover quit, exit, EOF, pending notifications, and incomplete handshakes without suppressing genuine protocol failures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-03 17:05:29 +01:00
..
integration fix: complete the CLI WebSocket close handshake 2026-11-03 17:05:29 +01:00
scripts feat!: add image source files and system messages 2026-11-03 17:01:24 +01:00
src feat!: add image source files and system messages 2026-11-03 17:01:24 +01:00
test feat!: add image source files and system messages 2026-11-03 17:01:24 +01:00
.prettierignore feat: add a localhost VS Code actor inspector 2026-11-03 14:52:28 +01:00
.prettierrc.json feat: add a localhost VS Code actor inspector 2026-11-03 14:52:28 +01:00
README.md feat!: add image source files and system messages 2026-11-03 17:01:24 +01:00
package-lock.json feat!: use runa-debug WebSockets with live image notifications 2026-11-03 15:36:21 +01:00
package.json feat!: add image source files and system messages 2026-11-03 17:01:24 +01:00
tsconfig.json feat: add a localhost VS Code actor inspector 2026-11-03 14:52:28 +01:00

README.md

Runa VS Code client

This is a development client for a trusted localhost Runa image. It uses the version 4 protocol over WebSockets with the runa-debug subprotocol, not Rust APIs, and never hosts actors or starts its own image.

Run from source

Requirements: VS Code 1.96+, Node.js 22+, npm, and the repository's Rust toolchain.

From the repository root:

npm --prefix vscode ci
mise run start

Open the repository in VS Code. In Run and Debug, select Run Runa Extension and press F5. The launch task compiles TypeScript before opening an Extension Development Host.

In that host:

  1. Run Runa: Connect to Local Image. The default is 127.0.0.1:3737; runa.address changes the suggested endpoint.
  2. Open the image's source folder (see Image source files). Right-click a .rn file and choose Spawn Actor (initial state null) or Spawn Actor with State (enter JSON, for example 0 for actors/counter). Runa: Spawn Actor in Runa Actors prompts for a source path and state. The command reports and returns the new actor reference. Mailbox capacity is 64.
  3. Click an actor or run Runa: Inspect Actor to open its JSON snapshot.
  4. Right-click an actor to send a JSON message or a system message: pause, resume, stop, kill, restart, reload, or become another source path. These actions are also available in the Command Palette with an actor picker.
  5. Use Runa: Advance Scheduler to execute an explicit number of ticks (instruction budget 32 per actor). Send only queues a message; it does not execute it. For the counter, send {"op":"add","value":7}, then advance.
  6. Run Runa: Disconnect to close only this client connection.

JSON input supports null, booleans, signed 64-bit integers, strings, arrays, and objects. Floats and out-of-range integers are rejected rather than rounded. Use {"$actor":"@<21-character-reference>"} for a typed actor reference and {"$record":{"$actor":"literal"}} to escape a marker-shaped object. REPL-local names are not shared with the extension. Input uses the same depth/node/string limits as the runtime; the full wire request must also fit 1 MiB. Cancelling a prompt sends no mutation. Changing connections during a prompt rejects the action instead of applying it to the new image.

Connection prompts accept numeric IPv4 loopback addresses (127.x.x.x) or [::1]:port. DNS names, wildcard addresses, remote addresses, and IPv4-mapped IPv6 addresses are rejected. There is no authentication: connect only to a trusted local server. In a remote VS Code window, localhost refers to the machine running the extension host, not necessarily the desktop.

Snapshot semantics

  • The tree shows actor references, lifecycle status, and queued/reserved counts. Tooltips include implementation ID, incarnation, and failure text.
  • Inspect fetches a fresh snapshot through inspect_actor. Documents show committed state, FIFO queued messages, the reserved message, status, and active-execution telemetry. Tagged values distinguish actor references from strings and preserve signed 64-bit integers exactly.
  • Documents are read-only snapshots, not editable actor state or live VM frames. The timestamp records when this client received the data, not a runtime clock. Server image_changed notifications automatically update the tree and open documents. Bursts are coalesced, and changes arriving during a fetch trigger a follow-up fetch. Refresh Image is also available on demand.
  • Open documents are retained and marked connected: false on disconnect. Each connection gets distinct document URIs; reconnecting never replaces snapshots from an earlier connection or another image. Closing a document releases its cache; snapshots are not persisted across VS Code restarts.
  • There is no auto-connect, polling, automatic reconnect, or request retry. Timeout, peer disconnect, malformed data, and version/correlation failures invalidate the connection and are surfaced in a notification and the Runa output channel. Server-reported operation errors leave the connection usable.
  • The client never ticks the scheduler implicitly or drains get_events. Notifications are non-destructive invalidations, not a replayable runtime event stream; reconnect fetches the current image rather than replaying history.

For automation, the runa.connect command also accepts an explicit endpoint string, and runa.inspect accepts an object with an actor_ref field. runa.spawn accepts { path: string, state: string }, where state is natural JSON text, and resolves to the new reference. runa.spawnSource and runa.spawnSourceWithState accept a source file URI (the latter also optional JSON state text). runa.send accepts { actor_ref: string } and JSON message text. runa.pause, runa.resume, runa.stop, runa.kill, runa.restart, and runa.reload accept { actor_ref: string }; runa.become also accepts a target source path. runa.tick accepts a positive integer tick count. Command failures are reported in the Runa output channel and an error notification; commands are never retried.

Image source files

The image owns actor source text. On connect, the extension mounts it as a writable workspace folder named source (runa-image://<endpoint>/source). In a saved multi-root workspace this happens automatically; otherwise accept the prompt or run Runa: Add Image Source Folder (VS Code may restart extensions when a single-folder window becomes multi-root). Directories are derived from source paths: actors/counter appears as source/actors/counter.rn. Source paths are canonical and exclude the source/ prefix and .rn suffix.

  • Save writes the image's working source with compare-and-swap against the last observed source_id. If another client changed the file, the save fails instead of overwriting; reopen or revert to load the image's text. Saving never commits. New .rn files are created in the image on first save. Deleting and renaming are not supported.
  • Spawn uses the latest saved working source, committed or not. If the editor has unsaved changes, spawn and commit ask to save first; cancelling does nothing. Unsaved editor text is never sent implicitly.
  • Runa: Commit Source records the saved working source as a new revision and reports its revision string.
  • Runa: Source History lists revisions for the file and opens the selected one as a read-only runa-source: document. Revision documents are cached per connection session, never change, and remain open after disconnect.
  • Runa: Roll Back Source asks for confirmation and replaces the working source with the selected revision. It does not change running actors; use Reload or Become to install source into an actor.

Server notifications refresh the source tree and clean open editors. For automation, runa.commitSource accepts a source URI and returns the revision; runa.sourceHistory accepts a URI and optional revision and returns the opened document URI; runa.rollbackSource accepts a URI and optional revision and returns the new source_id after confirmation.

Development and validation

From the repository root:

mise run format
mise run lint
mise run test
mise run test-extension-host

The normal test task covers portable-value validation, framing and UTF-8 fragmentation, subprotocol negotiation, interleaved notifications, refresh coalescing, timeout/disconnect behavior, exact 64-bit integer display, and interoperation with the real Rust server on an ephemeral loopback port.

The separate host test downloads VS Code 1.96.4 to the ignored .vscode-test/ cache in a temporary multi-root workspace and runs source folder save/spawn/ commit/history/rollback, spawn/send/system-message/tick, connection, inspection, disconnect, and reconnect commands against a test-owned Rust server. It verifies automatic document updates after another client sends messages, pauses, and advances an actor into failure, as well as session isolation. On headless Linux:

xvfb-run -a mise run test-extension-host

Compilation diagnostics, detailed runtime event subscriptions, packaging for distribution, and debugging remain future work.