419 lines
24 KiB
Markdown
419 lines
24 KiB
Markdown
# Runa VS Code client
|
|
|
|
This is a development client for a trusted localhost Runa image. It uses
|
|
the [version 5 protocol](../docs/protocol.md) 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:
|
|
|
|
```sh
|
|
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 **Runa** in the Activity Bar. Its **Image Sources** view automatically shows the image's
|
|
`source` directory (see [Image source files](#image-source-files)).
|
|
Right-click a `.rn` file and choose **Spawn** (initial state `null`) or
|
|
**Spawn with State...** (enter JSON in the input field, for example `0` for
|
|
`actors/counter`). **Runa: Spawn Actor** in **Actors** prompts for a source
|
|
path and state. The command reports and returns the new actor reference.
|
|
Mailbox capacity is 64.
|
|
3. Expand an actor directory to open its metadata, configuration, state,
|
|
mailbox, or log files. **Runa: Inspect Actor** also opens a combined 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 the **Advance Scheduler** button in the Actors toolbar (or
|
|
**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 bundled counter, send `{"op":"inc"}`, then advance.
|
|
It defaults missing state to zero on its first message and queues further
|
|
increments to itself. Pause/resume controls its loop.
|
|
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 directories with references, lifecycle status, and queued/reserved counts.
|
|
Tooltips include implementation ID, incarnation, and failure text.
|
|
- Actor directories contain:
|
|
- `meta.json`: read-only identity, lifecycle, execution telemetry, source path
|
|
(`behavior_path`), pending source path, and temporary-behavior flag.
|
|
- `config.json`: `{"log_level":"info"}`, editable even while running.
|
|
- `state.json`: pretty-printed natural JSON state with two-space indentation,
|
|
using `$actor`/`$record` markers as needed and preserving exact 64-bit integers.
|
|
- `mailbox.json`: `{"reserved":[],"queued":[]}`, with at most one reserved
|
|
message. State and mailbox are writable only when paused or failed with no
|
|
active invocation. JSON uses two-space indentation. Saving never resumes the actor.
|
|
- `actor.log`: read-only retained entries, automatically refreshed on image changes,
|
|
recognized by VS Code's built-in Log language for syntax highlighting.
|
|
Entries use an RFC 5424-style syslog header:
|
|
`<134>1 - - runa <actor-ref> <sequence> - [INFO] message`.
|
|
Priorities use the local0 facility and mapped severity (trace/debug: debug,
|
|
info: informational, warn: warning, error: error, fatal: critical).
|
|
Timestamp and hostname are `-` because the image does not record them;
|
|
sequence IDs preserve ordering without inventing timestamps. Embedded
|
|
CR/LF characters are escaped so each entry occupies one line. Only the server's
|
|
latest 1,000 accepted entries per actor are available.
|
|
- `behavior`: present after an actor-specific temporary behavior is saved.
|
|
These files use connection-scoped `runa-actor:` URIs. Removed/disconnected
|
|
actors' open JSON files retain their last values and become read-only; already
|
|
opened log/behavior files retain cached 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 can be edited and saved while the actor is paused or failed
|
|
without an active invocation. Edit the tagged `actor.state`, `actor.reserved`,
|
|
`actor.queued`, and `actor.log_level` fields. Saving applies changed writable
|
|
fields; changes to identity, status, telemetry, connection metadata, and other
|
|
protected fields are discarded with a warning. The editor then reloads the
|
|
canonical server snapshot. Invalid writable fields reject the save.
|
|
Running, idle, suspended, and disconnected actors are read-only.
|
|
Unsaved edits are not replaced by automatic refreshes; saving rechecks the
|
|
current actor status. State/message edits and log-level changes are separate
|
|
server operations, not one transaction.
|
|
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.
|
|
- Stop removes the actor after graceful shutdown (advance the scheduler); kill
|
|
removes it immediately, including failed actors. Failure alone does not remove
|
|
an actor. Removal discards state, mailbox, and retained logs. Removed actors
|
|
disappear from the tree; open snapshots keep their last values with
|
|
`removed: true` and become read-only. Restart preserves a live/failed actor's
|
|
identity but cannot recover a removed actor.
|
|
- Explicit disconnect, reconnect, and extension deactivation complete the
|
|
WebSocket close handshake. Deactivation waits for outstanding closes; an
|
|
unresponsive peer is forcibly disconnected after five seconds. Protocol
|
|
corruption and request failures still terminate the connection without retry.
|
|
Each connection gets distinct document URIs; reconnecting never replaces
|
|
snapshots from an earlier connection or another image. Snapshots are kept
|
|
for the extension session, 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.
|
|
|
|
## Execution stack inspection
|
|
|
|
Right-click an actor and choose **Inspect Execution Stack...** (also available
|
|
in the Command Palette). Select a frame to open the exact loaded Rune source
|
|
in a read-only `runa-execution:` document and highlight its source span.
|
|
The picker identifies the incarnation, completed VM steps, functions, and source
|
|
positions. The current frame points to the next instruction, or the pending
|
|
await instruction at the last safe snapshot; callers point to call sites, not
|
|
their saved return addresses. At most 64 innermost frames are shown, with an
|
|
explicit truncation notice. Missing debug locations are labeled unavailable.
|
|
|
|
This never advances the scheduler or pauses the actor. Pause first for a stable
|
|
snapshot; running inspection is a point-in-time view. Idle/failed actors have no
|
|
active stack, and removed actors cannot be inspected. Pending source/temporary
|
|
behavior changes do not replace the loaded code. Open loaded documents retain
|
|
their text after edits, actor removal, and disconnect; connections use distinct
|
|
URIs. These are not source editors or temporary behavior documents.
|
|
|
|
Only the head Rune VM's visible frames are available, not native Rust frames or
|
|
stacks hidden inside nested futures. There are no locals, post-mortem stacks,
|
|
or Debug Adapter Protocol support yet.
|
|
For automation, `runa.inspectExecution` accepts `{actor_ref: string}` and an
|
|
optional zero-based frame index to navigate without the picker.
|
|
|
|
### Source breakpoints and stepping
|
|
|
|
Use the actor context menu or Command Palette:
|
|
|
|
1. **Set Actor Breakpoints...** accepts 1-based lines separated by commas in the
|
|
actor's **loaded source**, not a newer working/temporary buffer. It replaces
|
|
the actor's list; blank input clears it. Up to 128 lines are accepted. Lines
|
|
without executable entries are reported as unverified, never moved silently.
|
|
2. Queue a message and **Advance Scheduler**. A breakpoint pauses that actor
|
|
before a mapped line entry, preserving draft state and buffered sends.
|
|
**Inspect Execution Stack...** shows the `breakpoint` stop reason and opens
|
|
the exact loaded code. Other actors continue on ordinary ticks.
|
|
3. **Step Into Source Line** advances only a paused actor with an active
|
|
invocation, up to 1,000 instruction polls. It stops at another mapped line,
|
|
call-depth change, breakpoint, pending await, completion/failure, or budget.
|
|
It automatically opens the current frame. The Runa output channel records
|
|
the reason; budget/await/completion outcomes also produce a notification.
|
|
4. **Resume Actor** and advance the scheduler to continue normally. Resume
|
|
crosses the stopped breakpoint once; a loop can hit it again.
|
|
|
|
**Step Over Source Line** skips deeper function calls and stops at the next
|
|
mapped line in the original frame or at a mapped caller location.
|
|
**Step Out of Function** runs toward a mapped caller location; from the outermost
|
|
handler frame it runs toward completion. Both still stop at nested breakpoints,
|
|
pending awaits, failures, or the 1,000-instruction budget. Every command uses the
|
|
current frame/line as its origin: after an interrupted step, the next command
|
|
starts a new step rather than resuming a hidden plan. All three commands open
|
|
the exact loaded-source location and leave surviving nonfailed actors paused.
|
|
|
|
An idle actor cannot be stepped: set a breakpoint, send, then tick first, or
|
|
pause an already active handler. A completed step commits normally and leaves
|
|
the actor paused at the handler boundary, without consuming another message.
|
|
Failure is surfaced with its error text; successful shutdown may remove the actor.
|
|
Same-line loops are bounded and pending awaits return immediately.
|
|
Installation clears breakpoints to prevent stale source mappings; pending edits
|
|
do not. Breakpoints belong to the actor/image, so disconnect does not clear
|
|
them. Restart on unchanged code retains them. These commands also remain
|
|
available independently of the native debugger described below. Conditions,
|
|
hit counts, and variables remain planned.
|
|
|
|
For automation, `runa.setBreakpoints` accepts `{actor_ref: string}` and optional
|
|
comma-separated 1-based line text (including `""` to clear). `runa.stepSource`
|
|
accepts `{actor_ref: string}` and returns its stop reason.
|
|
`runa.stepOver` and `runa.stepOut` accept the same argument and return stop reasons.
|
|
Use a server built with mode-aware stepping for these new commands.
|
|
|
|
### Native actor debugger
|
|
|
|
Connect to an image, then right-click an actor and choose **Debug Actor**.
|
|
Alternatively choose **Runa Actor** in Run and Debug and use an attach
|
|
configuration (omit `actorRef` to choose an actor):
|
|
|
|
```json
|
|
{
|
|
"type": "runa",
|
|
"request": "attach",
|
|
"name": "Attach to Runa Actor"
|
|
}
|
|
```
|
|
|
|
**Debug Actor** does not save open editors. Starting an attach configuration
|
|
directly through Run and Debug uses VS Code's `debug.saveBeforeStart` setting.
|
|
The extension's inline Debug Adapter Protocol adapter uses the existing image
|
|
connection; it does not start a server, spawn an actor, or open a separate debug
|
|
port. Each session represents one actor/thread and is bound to the current
|
|
connection, installed implementation, and incarnation. Only one session per
|
|
actor is allowed. Attach pauses a live nonfailed actor, retaining its active
|
|
invocation if any. An idle actor can be attached, but has no frames until it
|
|
starts a handler. Failed actors must be repaired/restarted before attachment.
|
|
|
|
- Set gutter breakpoints in the actor's immutable loaded source, opened by the
|
|
command or Call Stack view. The debugger serves the exact compiled text,
|
|
including temporary/older implementations, using source references.
|
|
Breakpoints in the image source editor are accepted only when both the saved
|
|
image file and any open editor buffer exactly match the loaded text. Other,
|
|
stale, or unsaved-different files are unverified, not silently mapped to code.
|
|
Changing a working document's breakpoint list removes that document's old
|
|
locations if it no longer matches. Loaded and matching working documents'
|
|
breakpoint groups are combined, with at most 128 distinct lines per actor.
|
|
Blank/nonexecutable lines remain unverified. Conditions, hit counts, and
|
|
logpoints are unsupported.
|
|
- **Continue** resumes scheduling eligibility but does **not** execute image
|
|
ticks. Send a message and use **Runa: Advance Scheduler** to run it toward a
|
|
breakpoint. Ordinary ticks can advance all eligible actors. **Pause** pauses
|
|
only the selected actor between ticks.
|
|
- Toolbar **Step Into**, **Step Over**, and **Step Out** use the existing bounded
|
|
source steps (1,000 instruction polls) and advance only this actor. A pending
|
|
await, breakpoint, budget, completion, or failure ends the request. The Debug
|
|
Console reports the exact reason; a surviving nonfailed actor stays paused.
|
|
Steps require an active invocation and never consume the next queued message.
|
|
- Call Stack displays at most the innermost 64 head-VM frames; truncation and
|
|
last-safe-await snapshots are identified in the stop description. Unmapped
|
|
frames have no invented source location. At handler completion the stack is
|
|
empty, state commits normally, and the actor remains paused. A failure shows
|
|
an exception stop with its error text, not a post-mortem stack.
|
|
- Variables/scopes, watches/evaluation, restart, instruction-level stepping,
|
|
exception filters, and native/hidden async frames are not implemented. Rune
|
|
native calls remain nonpreemptible. Unsupported requests fail explicitly.
|
|
- Disconnecting/stopping the debug session leaves the actor's current status
|
|
and breakpoint list unchanged: it does not kill, resume, or restart the actor.
|
|
Actor removal, implementation installation, restart/incarnation change, or
|
|
image disconnect terminates the session with a console explanation. Attach
|
|
again to use the new version. Pending code edits alone do not change the
|
|
loaded version.
|
|
|
|
Breakpoint lists are actor/image state shared with other clients and the
|
|
standalone **Set Actor Breakpoints...** command; a later writer replaces the
|
|
runtime list. Native verification updates when an external change removes or
|
|
restores a session breakpoint. Disconnect does not clear them.
|
|
For automation, `runa.debugActor` accepts `{actor_ref: string}` and returns
|
|
whether VS Code started the session.
|
|
|
|
For automation, the `runa.connect` command also accepts an explicit endpoint
|
|
string, and `runa.inspect` accepts an object with an `actor_ref` field.
|
|
**Copy Actor Reference** in the actor context menu or inline button copies the
|
|
raw reference to the clipboard.
|
|
`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
|
|
|
|
Actor context menus also offer **Edit Actor State...** and
|
|
**Edit Reserved and Queued Messages...**. They prefill JSON from the current
|
|
snapshot; editing requires a paused or failed actor with no active
|
|
execution. Message input uses `{"reserved":[],"queued":[]}` with zero or one
|
|
reserved message. Clearing the reservation removes the failed input that would
|
|
otherwise retry on restart. Edits do not resume actors. **Set Actor Log Level...**
|
|
selects trace/debug/info/warn/error/fatal (default info); snapshots show the level.
|
|
The actor's `actor.log` file shows retained output; CLI `logs [-f] [actor...]` also
|
|
supports a live event subscription.
|
|
|
|
### Actor-specific behaviors
|
|
|
|
**Edit Current Behavior** opens the actor's loaded script, not the potentially
|
|
newer working source. Saving compiles and schedules a temporary actor-specific
|
|
implementation for the next safe handler boundary; it never mutates active
|
|
frames, state, mailbox, shared source, or commit history. Compilation errors
|
|
reject the save and keep the previous behavior. Repeating the action reuses the
|
|
same document and temporary text. Other clients can read/edit the same temporary
|
|
behavior through the server; stale saves fail with a source-identity conflict.
|
|
Temporary behaviors survive actor restart, but not actor removal or server exit.
|
|
For failed actors, the new implementation is installed without clearing failure
|
|
or the reserved message; restart explicitly to retry.
|
|
|
|
**Reload Actor Source** clears the override and schedules current shared source.
|
|
**Go to Behavior** opens the original image source. **Update Behavior...** asks
|
|
before copying the temporary script into shared working source, checking both
|
|
source identities and the actor's source path for concurrent changes. It does
|
|
not commit or reload other actors. Unsaved temporary editor text is not copied.
|
|
|
|
The dedicated **Runa** Activity Bar sidebar contains **Image Sources** first,
|
|
followed by **Actors**. Both views are visible by default and do not require
|
|
an open workspace. The source tree uses native VS Code tree items, file icons,
|
|
directory expansion, editor opening, and file context menus.
|
|
|
|
The image owns actor source text. On connect, **Image Sources** automatically
|
|
shows its virtual filesystem beneath `source` (`runa-image://<endpoint>/source`).
|
|
No workspace changes, folder prompt, or extension restart is required.
|
|
Opening a file reads it from the image; saving writes back to the image.
|
|
The extension never adds image folders to the workspace. VS Code's public API
|
|
does not support overlaying virtual files onto the built-in workspace file tree,
|
|
so image files appear in the Runa sidebar's source tree backed by the filesystem provider.
|
|
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 Source File...** in the toolbar or a directory context
|
|
menu creates a file at a relative path (including nested directories).
|
|
- **Rename or Move Source...** changes a file's path and moves its commit history.
|
|
If installed or pending actor behaviors use it, a warning asks to update their
|
|
paths atomically. Declining cancels the operation. Loaded code does not change;
|
|
future reloads follow the new path. Existing destinations are not overwritten.
|
|
- **Delete Source** confirms deletion of a file and its history. The server
|
|
rejects deletion if any nonfailed actor still uses its installed or pending
|
|
path, including paused actors: stop them first. Failed actors may retain loaded
|
|
code after deletion, but reload/go-to-source then fail until the path is recreated.
|
|
Directory rename/delete and cross-image moves 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 used for spawning/committing;
|
|
automatic diagnostics do send it to the connected trusted image for validation.
|
|
- **Spawn with State...** opens a JSON input field; cancelling the field does
|
|
not spawn an actor.
|
|
- **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.
|
|
|
|
## Compilation diagnostics
|
|
|
|
Open image sources and actor-specific `behavior` documents are checked after a
|
|
300 ms edit debounce. Rune errors and warnings appear inline and in **Problems**,
|
|
with zero-based UTF-16 ranges converted from Rune's UTF-8 byte spans. Non-ASCII
|
|
text and CRLF line endings retain correct editor locations. Diagnostics without
|
|
a source location are displayed at the start of the document.
|
|
|
|
Validation compiles the current editor text through the connected image's native
|
|
context; it never executes the script, saves a source, commits, installs an
|
|
implementation, or changes actor state/mailboxes. Validation artifacts are
|
|
discarded rather than filling the image's compile cache on every keystroke.
|
|
Shared source may still be saved/committed with compilation errors; spawn and
|
|
reload require successful compilation. Temporary behavior saves still reject
|
|
invalid code without replacing the previously installed implementation.
|
|
|
|
Checks are serialized and edits coalesced. Responses for old document versions,
|
|
closed editors, or disconnected sessions are discarded. Closing a document or
|
|
disconnecting clears its diagnostics; reconnect validates current open image
|
|
sources but not old-session actor documents. Compiler errors are normal
|
|
validation results, not connection failures. Transport and compiler-service
|
|
failures remain explicit notifications in the Runa output channel.
|
|
|
|
Compilation checking is single-source, not an LSP: completion, cross-file
|
|
import resolution through image source paths, and quick fixes remain planned.
|
|
Debugger controls are described separately above; variable inspection is not
|
|
implemented.
|
|
|
|
## Development and validation
|
|
|
|
From the repository root:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
xvfb-run -a mise run test-extension-host
|
|
```
|
|
|
|
Detailed runtime event subscriptions, packaging for
|
|
distribution, and debugging remain future work.
|