summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRider Linden <rider@lindenlab.com>2026-08-24 14:52:41 -0700
committerGitHub <noreply@github.com>2026-08-24 14:52:41 -0700
commit92289533c1b95bc0fb2cdcc1217ae539485ea244 (patch)
treef9cb1ccd42d310d01136d58d1ce06712b8708efc
parent30bd7c3e22ba7751ea59cd09cbfda733b583a871 (diff)
parentb7fb9d897e94948f3145232f9866297708506ced (diff)
Merge pull request #6178 from secondlife/rider/doc_update
Fix doc drift between viewer and plugin.
-rw-r--r--doc/external-editor-json-rpc.md778
1 files changed, 565 insertions, 213 deletions
diff --git a/doc/external-editor-json-rpc.md b/doc/external-editor-json-rpc.md
index 5c7d70185e..6fd5d20eea 100644
--- a/doc/external-editor-json-rpc.md
+++ b/doc/external-editor-json-rpc.md
@@ -1,5 +1,9 @@
# Viewer to External Editor JSON-RPC<br>Message Interfaces Documentation
+> **This file is a mirror.** The canonical source is `doc/Message_Interfaces.md` in the
+> object_publish extension repository. Do not edit this copy — edit the canonical file and
+> re-copy it here.
+
This document describes all the message interfaces defined for WebSocket communication between the Second Life viewer and an external editor such as a VSCode extension.
## Table of Contents
@@ -7,6 +11,7 @@ This document describes all the message interfaces defined for WebSocket communi
- [Usage Flow](#usage-flow)
- [VS Code Launch URI](#vs-code-launch-uri)
- [JSON-RPC Method Summary](#json-rpc-method-summary)
+- [Error Handling](#error-handling)
- [Session Management Interfaces](#session-management-interfaces)
- [SessionHandshake](#sessionhandshake)
- [SessionHandshakeResponse](#sessionhandshakeresponse)
@@ -33,8 +38,9 @@ This document describes all the message interfaces defined for WebSocket communi
- [Handler and Configuration Interfaces](#handler-and-configuration-interfaces)
- [WebSocketHandlers](#websockethandlers)
- [ClientInfo](#clientinfo)
-- [Object Content Interfaces](#object-content-interfaces)
+- [Object Explorer Interfaces](#object-explorer-interfaces)
- [Core Data Types](#core-data-types)
+ - [Common preconditions](#common-preconditions)
- [ObjectPublish](#objectpublish)
- [ObjectUnpublish](#objectunpublish)
- [ObjectUpdate](#objectupdate)
@@ -43,7 +49,9 @@ This document describes all the message interfaces defined for WebSocket communi
- [ObjectItemCreate](#objectitemcreate)
- [ObjectItemDelete](#objectitemdelete)
- [ObjectScriptSetRunning](#objectscriptsetrunning)
+ - [ObjectScriptReset](#objectscriptreset)
- [ObjectRequest](#objectrequest)
+ - [ObjectList](#objectlist)
- [ObjectModify](#objectmodify)
- [ObjectItemModify](#objectitemmodify)
- [Command Interfaces](#command-interfaces)
@@ -71,10 +79,10 @@ This document describes all the message interfaces defined for WebSocket communi
- When subscription needs to be terminated, viewer sends `script.unsubscribe` notification with `ScriptUnsubscribe` data
- Extension handles unsubscription by cleaning up local script tracking
-4. **Object Content Publishing:**
+4. **Object Explorer:**
- - Viewer sends `object.publish` notification when an in-world object's contents are made available for editing
- - Viewer sends `object.unpublish` notification when an object is removed or the owner stops publishing
+ - Viewer sends `object.publish` notification when an in-world object's contents are made available for editing (user clicks "Explore in IDE")
+ - Viewer sends `object.unpublish` notification when an object is removed or the user stops exploring
- Viewer sends `object.update` notification when object inventory changes (full replacement or delta)
- Extension calls `object.content.get` to fetch an item's content on demand
- Extension calls `object.content.save` to write modified content back to the viewer
@@ -106,8 +114,8 @@ vscode://lindenlab.sl-vscode-plugin/connect[?port=<port>][&object=<uuid>][&scrip
| Parameter | Required | Description |
| --------- | -------- | ----------- |
-| `port` | No | Port number the viewer's WebSocket server is listening on. Overrides the user's configured port for this session. Defaults to the configured `slVscodeEdit.network.websocketPort` (default `9020`) if absent. Must be in range 1024-65535. |
-| `object` | No | UUID of a root prim. After the handshake completes the extension calls `object.request` to ask the viewer to publish this object. The viewer then sends an `object.publish` notification and the object appears as a workspace folder in the Explorer. |
+| `port` | No | Port number the viewer's WebSocket server is listening on. Overrides the user's configured port for this session. Defaults to the configured `slVscodeEdit.network.websocketPort` (default `9020`) if absent. Must be in range 1024–65535. |
+| `object` | No | UUID of a root prim. After the handshake completes the extension calls `object.request` to ask the viewer to start exploring this object. The viewer then sends an `object.publish` notification and the object appears as a workspace folder in the Explorer. |
| `script` | No | UUID of a script. After the handshake completes the extension locates the corresponding temp file via `script.list` and opens it, triggering the normal `script.subscribe` + live-sync flow. |
`object` and `script` are mutually exclusive in typical use but both may be supplied; the extension will process both.
@@ -121,7 +129,7 @@ vscode://lindenlab.sl-vscode-plugin/connect
# Connect on a custom port
vscode://lindenlab.sl-vscode-plugin/connect?port=9021
-# Connect and immediately publish a specific object
+# Connect and immediately explore a specific object
vscode://lindenlab.sl-vscode-plugin/connect?port=9020&object=550e8400-e29b-41d4-a716-446655440000
# Connect and open a specific script for editing
@@ -134,18 +142,18 @@ When the URI contains an `object` or `script` parameter the extension acts only
```
URI received by extension
- |
- v
-WebSocket connects -> session.handshake -> session.ok
- |
- |- object=<uuid> -> object.request({ object_id }) call
- | |
- | v (async, when viewer is ready)
- | object.publish notification
- |
- \- script=<uuid> -> script.list call -> open temp file
- |
- v
+ │
+ ▼
+WebSocket connects → session.handshake → session.ok
+ │
+ ├─ object=<uuid> → object.request({ object_id }) call
+ │ │
+ │ ▼ (async, when viewer is ready)
+ │ object.publish notification
+ │
+ └─ script=<uuid> → script.list call → open temp file
+ │
+ ▼
script.subscribe + live-sync
```
@@ -153,56 +161,110 @@ WebSocket connects -> session.handshake -> session.ok
| Method | Direction | Type | Interface/Parameters |
| ------------------------------- | ------------------ | ------------ | -------------------------- |
-| `session.handshake` | Viewer -> Extension | Call | `SessionHandshake` |
-| `session.handshake` (response) | Extension -> Viewer | Response | `SessionHandshakeResponse` |
-| `session.ok` | Viewer -> Extension | Notification | _(no interface)_ |
+| `session.handshake` | Viewer → Extension | Call | `SessionHandshake` |
+| `session.handshake` (response) | Extension → Viewer | Response | `SessionHandshakeResponse` |
+| `session.ok` | Viewer → Extension | Notification | _(no interface)_ |
| `session.disconnect` | Bidirectional | Notification | `SessionDisconnect` |
| `session.ping` | Bidirectional | Call | `SessionPing` |
| `session.ping` (response) | Bidirectional | Response | `SessionPingResponse` |
-| `script.subscribe` | Extension -> Viewer | Call | `ScriptSubscribe` |
-| `script.subscribe` (response) | Viewer -> Extension | Response | `ScriptSubscribeResponse` |
-| `script.unsubscribe` | Viewer -> Extension | Notification | `ScriptUnsubscribe` |
-| `script.list` | Extension -> Viewer | Call | _(no parameters)_ |
-| `script.list` (response) | Viewer -> Extension | Response | `ScriptList` |
-| `language.syntax.id` | Extension -> Viewer | Call | _(no parameters)_ |
-| `language.syntax.id` (response) | Viewer -> Extension | Response | `{ id: string }` |
-| `language.syntax` | Extension -> Viewer | Call | `{ kind: string }` |
-| `language.syntax` (response) | Viewer -> Extension | Response | `LanguageInfo` |
-| `language.syntax.cache` | Extension -> Viewer | Call | _(no parameters)_ |
-| `language.syntax.cache` (response) | Viewer -> Extension | Response | `SyntaxCacheList` |
-| `language.syntax.get` | Extension -> Viewer | Call | `{ filename: string, as_json?: boolean }` |
-| `language.syntax.get` (response) | Viewer -> Extension | Response | `SyntaxCacheFile` |
-| `language.syntax.change` | Viewer -> Extension | Notification | `SyntaxChange` |
-| `script.compiled` | Viewer -> Extension | Notification | `CompilationResult` |
-| `runtime.debug` | Viewer -> Extension | Notification | `RuntimeDebug` |
-| `runtime.error` | Viewer -> Extension | Notification | `RuntimeError` |
-| `object.publish` | Viewer -> Extension | Notification | `ObjectPublishMessage` |
-| `object.unpublish` | Viewer -> Extension | Notification | `ObjectUnpublishMessage` || `object.unpublish` | Extension → Viewer | Call | `ObjectUnpublishParams` |
-| `object.unpublish` (response) | Viewer → Extension | Response | `ObjectUnpublishResponse` || `object.update` | Viewer -> Extension | Notification | `ObjectUpdateMessage` |
-| `object.content.get` | Extension -> Viewer | Call | `ObjectContentGetParams` |
-| `object.content.get` (response) | Viewer -> Extension | Response | `ObjectContentGetResponse` |
-| `object.content.save` | Extension -> Viewer | Call | `ObjectContentSaveParams` |
-| `object.content.save` (response)| Viewer -> Extension | Response | `ObjectContentSaveResponse`|
-| `object.item.create` | Extension -> Viewer | Call | `ObjectItemCreateParams` |
-| `object.item.create` (response) | Viewer -> Extension | Response | `ObjectItemCreateResponse` |
-| `object.item.delete` | Extension -> Viewer | Call | `ObjectItemDeleteParams` |
-| `object.item.delete` (response) | Viewer -> Extension | Response | `ObjectItemDeleteResponse` |
-| `object.script.set_running` | Extension -> Viewer | Call | `ObjectScriptSetRunningParams` |
-| `object.script.set_running` (response) | Viewer -> Extension | Response | `ObjectScriptSetRunningResponse` |
-| `object.script.reset` | Extension -> Viewer | Call | `ObjectScriptResetParams` |
-| `object.script.reset` (response)| Viewer -> Extension | Response | `ObjectScriptResetResponse` |
-| `object.request` | Extension -> Viewer | Call | `ObjectRequestParams` |
-| `object.request` (response) | Viewer -> Extension | Response | `ObjectRequestResponse` |
-| `object.list` | Extension -> Viewer | Call | `{}` (no params) |
-| `object.list` (response) | Viewer -> Extension | Response | `ObjectListResponse` |
-| `object.modify` | Extension -> Viewer | Call | `ObjectModifyParams` |
-| `object.modify` (response) | Viewer -> Extension | Response | `ObjectModifyResponse` |
-| `object.item.modify` | Extension -> Viewer | Call | `ObjectItemModifyParams` |
-| `object.item.modify` (response) | Viewer -> Extension | Response | `ObjectItemModifyResponse` |
-| `command.execute` | Bidirectional | Call | `CommandExecuteParams` |
-| `command.execute` (response) | Bidirectional | Response | `CommandExecuteResponse` |
-| `command.list` | Bidirectional | Call | _(no params)_ |
-| `command.list` (response) | Bidirectional | Response | `CommandListResponse` |
+| `script.subscribe` | Extension → Viewer | Call | `ScriptSubscribe` |
+| `script.subscribe` (response) | Viewer → Extension | Response | `ScriptSubscribeResponse` |
+| `script.unsubscribe` | Viewer → Extension | Notification | `ScriptUnsubscribe` |
+| `script.unsubscribe` | Extension → Viewer | Call | `ScriptUnsubscribeParams` |
+| `script.unsubscribe` (response) | Viewer → Extension | Response | `null` |
+| `script.list` | Extension → Viewer | Call | _(no parameters)_ |
+| `script.list` (response) | Viewer → Extension | Response | `ScriptList` |
+| `language.syntax.id` | Extension → Viewer | Call | _(no parameters)_ |
+| `language.syntax.id` (response) | Viewer → Extension | Response | `{ id: string }` |
+| `language.syntax` | Extension → Viewer | Call | `{ kind: string }` |
+| `language.syntax` (response) | Viewer → Extension | Response | `LanguageInfo` |
+| `language.syntax.cache` | Extension → Viewer | Call | _(no parameters)_ |
+| `language.syntax.cache` (response) | Viewer → Extension | Response | `SyntaxCacheList` |
+| `language.syntax.get` | Extension → Viewer | Call | `{ filename: string, as_json?: boolean }` |
+| `language.syntax.get` (response) | Viewer → Extension | Response | `SyntaxCacheFile` |
+| `language.syntax.change` | Viewer → Extension | Notification | `SyntaxChange` |
+| `script.compiled` | Viewer → Extension | Notification | `CompilationResult` |
+| `runtime.debug` | Viewer → Extension | Notification | `RuntimeDebug` |
+| `runtime.error` | Viewer → Extension | Notification | `RuntimeError` |
+| `object.publish` | Viewer → Extension | Notification | `ObjectPublishMessage` |
+| `object.unpublish` | Viewer → Extension | Notification | `ObjectUnpublishMessage` |
+| `object.unpublish` | Extension → Viewer | Call | `ObjectUnpublishParams` |
+| `object.unpublish` (response) | Viewer → Extension | Response | `ObjectUnpublishResponse` |
+| `object.update` | Viewer → Extension | Notification | `ObjectUpdateMessage` |
+| `object.content.get` | Extension → Viewer | Call | `ObjectContentGetParams` |
+| `object.content.get` (response) | Viewer → Extension | Response | `ObjectContentGetResponse` |
+| `object.content.save` | Extension → Viewer | Call | `ObjectContentSaveParams` |
+| `object.content.save` (response)| Viewer → Extension | Response | `ObjectContentSaveResponse`|
+| `object.item.create` | Extension → Viewer | Call | `ObjectItemCreateParams` |
+| `object.item.create` (response) | Viewer → Extension | Response | `ObjectItemCreateResponse` |
+| `object.item.delete` | Extension → Viewer | Call | `ObjectItemDeleteParams` |
+| `object.item.delete` (response) | Viewer → Extension | Response | `ObjectItemDeleteResponse` |
+| `object.script.set_running` | Extension → Viewer | Call | `ObjectScriptSetRunningParams` |
+| `object.script.set_running` (response) | Viewer → Extension | Response | `ObjectScriptSetRunningResponse` |
+| `object.script.reset` | Extension → Viewer | Call | `ObjectScriptResetParams` |
+| `object.script.reset` (response)| Viewer → Extension | Response | `ObjectScriptResetResponse` |
+| `object.request` | Extension → Viewer | Call | `ObjectRequestParams` |
+| `object.request` (response) | Viewer → Extension | Response | `ObjectRequestResponse` |
+| `object.list` | Extension → Viewer | Call | `{}` (no params) |
+| `object.list` (response) | Viewer → Extension | Response | `ObjectListResponse` |
+| `object.modify` | Extension → Viewer | Call | `ObjectModifyParams` |
+| `object.modify` (response) | Viewer → Extension | Response | `ObjectModifyResponse` |
+| `object.item.modify` | Extension → Viewer | Call | `ObjectItemModifyParams` |
+| `object.item.modify` (response) | Viewer → Extension | Response | `ObjectItemModifyResponse` |
+| `command.execute` | Bidirectional | Call | `CommandExecuteParams` |
+| `command.execute` (response) | Bidirectional | Response | `CommandExecuteResponse` |
+| `command.list` | Bidirectional | Call | _(no params)_ |
+| `command.list` (response) | Bidirectional | Response | `CommandListResponse` |
+
+## Error Handling
+
+A call that fails returns a JSON-RPC 2.0 `error` object rather than a result. There is no partial
+success: a response carries either `result` or `error`, never both.
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 12,
+ "error": {
+ "code": -32602,
+ "message": "Invalid params: No syntax category specified"
+ }
+}
+```
+
+**Standard JSON-RPC codes:**
+
+| Code | Meaning |
+| ---- | ------- |
+| `-32700` | Parse error — invalid JSON was received |
+| `-32600` | Invalid Request — the JSON sent is not a valid Request object |
+| `-32601` | Method not found |
+| `-32602` | Invalid params |
+| `-32603` | Internal error |
+
+**Server-specific codes.** The range `-32000` to `-32099` is reserved for server errors. The
+transport defines the following; `-32001` and `-32003` are the ones handlers commonly raise.
+
+| Code | Meaning |
+| ---- | ------- |
+| `-32000` | Connection closed unexpectedly |
+| `-32001` | Request timed out |
+| `-32002` | Authentication required |
+| `-32003` | Access denied |
+| `-32004` | Too many requests |
+| `-32005` | Service temporarily unavailable |
+| `-32006` | Message exceeds maximum size |
+| `-32007` | Session expired or invalid |
+
+**Message format.** Standard errors prefix the handler's detail text with a fixed label, so
+`error.message` reads `"Invalid params: <detail>"`, `"Internal error: <detail>"`,
+`"Method not found: <method>"`, and so on. Server-specific errors carry the detail text alone
+(e.g. `"Access denied"`). Clients should branch on `error.code`, not on `error.message`.
+
+**`success` is not an error channel.** Several results carry a `success` field. Where a method
+reports failure through a JSON-RPC error, that field is `true` on every response it ever
+appears in, and a failed call produces no result to inspect. Do not treat a missing or false
+`success` as the failure signal unless the method's own section documents it as one.
## Session Management Interfaces
@@ -243,6 +305,7 @@ interface SessionHandshake {
- `compilation`: Viewer will forward compilation results via `script.compiled`
- `syntax_cache`: Viewer supports `language.syntax.cache` and `language.syntax.get` for retrieving syntax definition files
- `commands`: Both sides support `command.execute` and `command.list`
+ - `unified_diagnostics`: Viewer supports the unified diagnostic reporting format
### SessionHandshakeResponse
@@ -270,7 +333,14 @@ interface SessionHandshakeResponse {
- `protocol_version`: Protocol version the client supports
- `challenge_response` (optional): The UUID read from the temporary file identified by the `challenge` field in the handshake. Must be provided if `challenge` was present, otherwise the connection will be closed.
- `languages`: Array of languages supported by the client
-- `features`: Dictionary of features supported by the client
+- `features`: Dictionary of features supported by the client. Known flags:
+ - `live_sync`: Client supports live script synchronisation
+ - `error_reporting`: Client accepts `runtime.error` notifications
+ - `unified_diagnostics`: Client supports the unified diagnostic reporting format
+ - `object_publish`: Client supports the object explorer methods and notifications
+ - `commands`: Both sides support `command.execute` and `command.list`
+ - `debugging`: Advertised as `false`. Reserved; no debugging support.
+ - `breakpoints`: Advertised as `false`. Reserved; no breakpoint support.
- `script_name` (optional): Name of the script currently open in the editor
- `script_language` (optional): Language of the script currently open in the editor (e.g. `"lsl"`, `"luau"`)
@@ -309,6 +379,10 @@ interface SessionDisconnect {
Heartbeat call used to verify the connection is alive and measure latency. Either side can initiate a ping; the recipient responds with the original timestamp plus its own server time.
+In practice the extension initiates and the viewer only answers — the viewer never sends
+`session.ping` itself. The extension pings every 30 seconds and tears the connection down after
+two consecutive failures.
+
```typescript
interface SessionPing {
timestamp: number;
@@ -330,7 +404,7 @@ interface SessionPingResponse {
**Response Fields:**
-- `timestamp`: The original timestamp from the request (echoed back)
+- `timestamp`: The original timestamp from the request. Echoed back only when the request supplied one.
- `server_time`: Unix timestamp in milliseconds when the response was generated
**Example Request:**
@@ -411,23 +485,24 @@ Requests the in-memory keyword definitions for a specific language. These defini
```typescript
interface LanguageInfo {
id: string;
- defs?: object; // Present only on success
+ defs: object;
success: boolean;
- error?: string; // Present only on failure
}
```
**Response Fields:**
- `id`: The current syntax version identifier
-- `defs` (optional): The keyword definitions object. Only present when `success` is `true`. Structure varies by language.
-- `success`: Whether the definitions were found and returned successfully
-- `error` (optional): Human-readable error description. Only present when `success` is `false`
+- `defs`: The keyword definitions object. Structure varies by language.
+- `success`: Always `true`. Failures are returned as JSON-RPC errors — see below.
-**Error cases:**
+**Errors:**
-- No `kind` parameter supplied: `success: false`, `error: "No syntax category specified"`
-- Unknown `kind` value: `success: false`, `error: "Unknown syntax category requested"`
+| Condition | Code | `error.message` |
+| --------- | ---- | --------------- |
+| No `kind` parameter supplied | `-32602` | `Invalid params: No syntax category specified` |
+| Unknown `kind` value | `-32602` | `Invalid params: Unknown syntax category requested` |
+| Definitions unavailable for a valid `kind` | `-32603` | `Internal error: Syntax definitions are unavailable` |
### Language Syntax Cache List
@@ -464,7 +539,7 @@ interface SyntaxCacheList {
| `lua_keywords_pretty.xml` | Luau keyword definitions in formatted LLSD XML format |
| `secondlife_selene.yml` | Luau Selene linter configuration in YAML format |
-Not all files may be present in every cache - the actual list returned by `language.syntax.cache` reflects only what is available on the viewer's local filesystem at the time of the request.
+Not all files may be present in every cache — the actual list returned by `language.syntax.cache` reflects only what is available on the viewer's local filesystem at the time of the request.
### Language Syntax Cache Get
@@ -490,23 +565,24 @@ Requests the content of a specific file from the syntax definition cache. The fi
```typescript
interface SyntaxCacheFile {
- content?: string | object; // Present only on success. String if as_json is false/omitted, object if as_json is true
+ content: string | object; // String if as_json is false/omitted, object if as_json is true
success: boolean;
- error?: string; // Present only on failure
}
```
**Response Fields:**
-- `content`: The file content. Only present when `success` is `true`. Is a raw text string when `as_json` is omitted or `false`; is a parsed object when `as_json` is `true`.
-- `success`: Whether the file was found and read successfully
-- `error` (optional): Human-readable error description. Only present when `success` is `false`
+- `content`: The file content. Is a raw text string when `as_json` is omitted or `false`; is a parsed object when `as_json` is `true`.
+- `success`: Always `true`. Failures are returned as JSON-RPC errors — see below.
-**Error cases:**
+**Errors:**
-- No `filename` parameter supplied: `success: false`, `error: "No filename specified"`
-- Name not found in cache: `success: false`, `error: "Requested syntax cache file not found"`
-- File could not be loaded: `success: false`, `error: "Failed to load syntax cache file"` (or `"Failed to load and format syntax cache file."` when `as_json` is `true`)
+| Condition | Code | `error.message` |
+| --------- | ---- | --------------- |
+| No `filename` parameter supplied | `-32602` | `Invalid params: No filename specified` |
+| Name not found in cache | `-32602` | `Invalid params: Requested syntax cache file not found` |
+| File could not be loaded (`as_json` omitted or `false`) | `-32603` | `Internal error: Failed to load syntax cache file` |
+| File could not be loaded or parsed (`as_json` is `true`) | `-32603` | `Internal error: Failed to load and format syntax cache file.` |
## Script Subscription Interfaces
@@ -541,9 +617,10 @@ interface ScriptSubscribeResponse {
script_id: string;
success: boolean;
status: number;
- object_id?: string;
- item_id?: string;
- message?: string;
+ message: string;
+ object_id?: string; // Success only
+ root_id?: string; // Success only
+ item_id?: string; // Success only
}
```
@@ -553,19 +630,30 @@ interface ScriptSubscribeResponse {
- `success`: Whether the subscription was successful
- `status`: Numeric status code indicating the result:
- `0`: Success
- - `1`: Invalid editor - the script editor panel is no longer open
- - `2`: Invalid subscription - no subscription found for the given `script_id`
- - `3`: Already subscribed - another connection is already subscribed to this script
+ - `1`: Invalid editor — the script editor panel is no longer open
+ - `2`: Invalid subscription — no subscription found for the given `script_id`
+ - `3`: Already subscribed — another connection is already subscribed to this script
- `4`: Internal server error
-- `object_id` (optional): The in-world UUID of the object containing the script
-- `item_id` (optional): The inventory item UUID of the script within the object
-- `message` (optional): Additional information about the subscription result
+- `message`: Always present. Fixed text matching `status`:
+
+| `status` | `message` |
+| -------- | --------- |
+| `0` | `OK` |
+| `1` | `Invalid editor handle` |
+| `2` | `No subscription found for script` |
+| `3` | `Script already subscribed` |
+| `4` | `Internal server error` |
+
+- `object_id`: UUID of the **prim** that owns the script. For a script in a child prim this is the child's UUID, not the linkset root's. Present only when `success` is `true`.
+- `root_id`: UUID of the root prim of the linkset containing the script. Present only when `success` is `true`. If the prim cannot be resolved, `root_id` is set equal to `object_id`, so equality does not by itself mean the script lives in the root prim.
+- `item_id`: The inventory item UUID of the script within the prim. Present only when `success` is `true`.
### ScriptUnsubscribe
**JSON-RPC Method:** `script.unsubscribe` (notification from viewer)
-Notification sent by the viewer when a script subscription should be terminated.
+Notification sent by the viewer when a script subscription should be terminated. It is delivered
+only to the connection holding the subscription, not to all connections.
```typescript
interface ScriptUnsubscribe {
@@ -577,6 +665,23 @@ interface ScriptUnsubscribe {
- `script_id`: Unique identifier for the script to unsubscribe from
+**JSON-RPC Method:** `script.unsubscribe` (call from extension to viewer)
+
+The extension may also call `script.unsubscribe` to end a subscription it holds.
+
+```typescript
+interface ScriptUnsubscribeParams {
+ script_id: string; // Script whose subscription should be dropped
+}
+```
+
+The result is `null`.
+
+The viewer drops the subscription only when the calling connection owns it. The call is
+idempotent: an unknown `script_id`, or one held by a different connection, is ignored and still
+returns a successful `null` result. A client therefore cannot use the response to detect that it
+targeted the wrong script.
+
### ScriptList
**JSON-RPC Method:** `script.list` (call from extension to viewer)
@@ -631,7 +736,6 @@ Result of a compilation operation in the viewer.
```typescript
interface CompilationResult {
- /** Deprecated: retained during migration to item-based routing. */
script_id: string;
success: boolean;
running: boolean;
@@ -646,6 +750,25 @@ interface CompilationResult {
- `running`: Whether the compiled script is currently running
- `diagnostics` (optional): Array of `Diagnostic` records if any occurred
+**Delivery:** routed only to the connection subscribed to that script. This differs from
+`runtime.debug` and `runtime.error`, which are broadcast to every connection.
+
+**Two compile-feedback paths.** Compilation results reach a client by one of two routes,
+depending on how the save was made:
+
+| Save route | Feedback |
+| ---------- | -------- |
+| `object.content.save` (object explorer) | `compiled` and `diagnostics` returned inline in the response |
+| Live-sync editing of a subscribed script | `script.compiled` notification |
+
+A client using only the object explorer never receives `script.compiled`; a client waiting for
+`script.compiled` after an `object.content.save` will wait indefinitely. Consolidating these two
+paths is tracked separately.
+
+**Known limitation.** `script.compiled` is produced only while the viewer's script editor for that
+script is open. If that editor has closed, compilation results are dropped without notice — no
+result, no error, and not necessarily a preceding `script.unsubscribe`. Tracked separately.
+
## Runtime Event Interfaces
### RuntimeDebug
@@ -656,24 +779,27 @@ Debug message notification sent by the viewer during script execution.
```typescript
interface RuntimeDebug {
- /** Deprecated: use item.item_id when available. */
- script_id: string;
+ script_id: string; // Not currently sent — see note below
object_id: string;
- prim_id?: string;
- item_id?: string;
+ prim_id: string;
+ item_id: string;
object_name: string;
message: string;
- channel?: "debug" | "owner_say";
- item?: ItemRef;
+ channel: "debug" | "owner_say";
+ item: ItemRef;
}
```
**Fields:**
-- `script_id`: Unique identifier for the script generating the debug message
-- `object_id`: Unique identifier for the object containing the script
+- `script_id`: Identifier for the script generating the debug message
+- `object_id`: UUID of the root prim of the object containing the script
+- `prim_id`: UUID of the prim that owns the script
+- `item_id`: Inventory item UUID of the script
- `object_name`: Human-readable name of the object
- `message`: The debug message content
+- `channel`: Source of the text. `"debug"` for script debug output, `"owner_say"` for owner-directed chat.
+- `item`: Reference identifying the originating script. See `ItemRef` under `RuntimeError`.
### RuntimeError
@@ -683,39 +809,54 @@ Runtime error notification sent by the viewer when a script encounters an error
```typescript
interface RuntimeError {
- /** Deprecated: use item.item_id when available. */
- script_id: string;
+ script_id: string; // Not currently sent — see note below
object_id: string;
- prim_id?: string;
- item_id?: string;
+ prim_id: string;
+ item_id: string;
object_name: string;
message: string;
error: string;
line: number;
- column?: number;
- stack?: string[];
- channel?: "debug" | "owner_say";
- item?: ItemRef;
+ column: number;
+ stack: string[];
+ channel: "debug" | "owner_say";
+ item: ItemRef;
}
interface ItemRef {
root_id: string;
- prim_id?: string;
- item_id?: string;
- name?: string;
- language?: "lsl" | "luau";
+ prim_id: string;
+ item_id: string;
+ name: string;
+ language: "lsl" | "luau";
}
```
**Fields:**
-- `script_id`: Unique identifier for the script that encountered the error
-- `object_id`: Unique identifier for the object containing the script
+- `script_id`: Identifier for the script that encountered the error
+- `object_id`: UUID of the root prim of the object containing the script
+- `prim_id`: UUID of the prim that owns the script
+- `item_id`: Inventory item UUID of the script
- `object_name`: Human-readable name of the object
- `message`: The full raw chat text of the runtime error message as received from the simulator
- `error`: Extracted runtime error description. This remains a top-level compatibility field while the protocol stays on version `1.0`.
- `line`: Line number where the error occurred when the runtime format can be parsed; otherwise `0`.
-- `stack` (optional): Stack trace lines if they could be extracted from the error message
+- `column`: Column position where the error occurred when the runtime format can be parsed; otherwise `0`.
+- `stack`: Stack trace lines extracted from the error message. Always present; an empty array when no trace could be extracted, so test its length rather than its presence.
+- `channel`: Source of the text. `"debug"` for script debug output, `"owner_say"` for owner-directed chat.
+- `item`: Reference identifying the originating script.
+ - `root_id`: UUID of the root prim of the linkset.
+ - `prim_id`: UUID of the prim that owns the script.
+ - `item_id`: Inventory item UUID of the script.
+ - `name`: Script name as it appears in the prim's inventory.
+ - `language`: The script's source language. Independent of the compile target; the VM is not carried in runtime messages.
+
+**Note on `script_id`:** this field is part of the contract but is **not currently sent** by the
+viewer for either `runtime.debug` or `runtime.error`. Implementation is tracked separately.
+
+**Delivery:** `runtime.debug` and `runtime.error` are broadcast to all connections. An event is
+emitted when the originating object is published or its script is subscribed.
## Handler and Configuration Interfaces
@@ -771,9 +912,14 @@ interface ClientInfo {
---
-## Object Content Interfaces
+## Object Explorer Interfaces
-These interfaces support publishing in-world object inventories (scripts and notecards) to the external editor as a browseable virtual filesystem. The extension exposes published objects under the `sl://objects/` URI scheme.
+These interfaces support exploring in-world object inventories (scripts and notecards) from the external editor as a browseable virtual filesystem. The extension exposes explored objects under the `sl://objects/` URI scheme.
+
+Explored objects are shared across all connections rather than owned by the connection that
+requested them. `object.publish`, `object.update` and `object.unpublish` are broadcast to every
+connected client, so a client will receive notifications for objects it never requested, and must
+drop an object from its own state when an `object.unpublish` for it arrives.
### Core Data Types
@@ -808,7 +954,7 @@ interface ObjectInventoryItem {
/** A linked (child) prim within a linkset */
interface LinkedObject {
link_id: string; // UUID of the linked prim
- link_number: number; // Link number (root=1, children>=2)
+ link_number: number; // Link number (root=1, children≥2)
link_name: string;
link_description?: string;
inventory: ObjectInventoryItem[];
@@ -827,18 +973,59 @@ interface PublishedObject {
region?: string;
owner_id?: string;
permissions?: ObjectPermissions;
+ can_save_back?: boolean; // Whether Save Back to Contents is currently available for this object
inventory: ObjectInventoryItem[]; // Root prim's scripts and notecards
linked_objects?: LinkedObject[]; // Child prims
}
```
-**Script display extensions** (synthetic, derived from `subtype`):
+**Display names.** The extension synthesises a file extension for scripts when presenting items in
+the virtual filesystem:
+
+| Item | Extension |
+| ---- | --------- |
+| Script, `subtype` `0` (LSL) | `.lsl` |
+| Script, `subtype` `1` (Luau) | `.luau` |
+| Notecard | _(none)_ |
+
+Notecards receive no synthetic extension; the inventory name is used verbatim, so an extension the
+user gave the notecard is preserved and none is added.
+
+These extensions exist only for display and are never part of the item's inventory name. Names sent
+to and received from the viewer — including in `object.item.create` and `object.item.modify` — are
+always the pure inventory name, without an extension.
-| `subtype` | Extension |
-| --------- | --------- |
-| `0` (LSL) | `.lsl` |
-| `1` (Luau)| `.luau` |
-| notecard | `.txt` |
+### Common preconditions
+
+Every method that addresses an item by `prim_id` + `item_id` — `object.content.get`,
+`object.content.save`, `object.item.delete` and `object.item.modify` — runs the same validation
+before doing any work, in this order:
+
+1. Both `prim_id` and `item_id` are present.
+2. The prim exists.
+3. The object containing the prim is currently published.
+4. The item exists in that prim's inventory.
+5. The item is a script or a notecard.
+6. The caller holds the permissions that method requires.
+
+An object must be published before any of its items can be addressed. Learning an object's id from
+`object.list` is not sufficient on its own — the object must be published, which `object.list`
+reports and `object.request` initiates.
+
+**Errors:**
+
+| Condition | Code | `error.message` |
+| --------- | ---- | --------------- |
+| `prim_id` or `item_id` missing | `-32602` | `Invalid params: prim_id and item_id are required` |
+| Prim not found | `-32602` | `Invalid params: Prim not found` |
+| Object is not published | `-32003` | `Object is not published` |
+| Item not in the prim's inventory | `-32602` | `Invalid params: Item not found in prim inventory` |
+| Item is not a script or notecard | `-32602` | `Invalid params: Item is not a script or notecard` |
+| Required item permission denied | `-32003` | `Insufficient permissions` |
+| Modify denied on the containing prim | `-32003` | `No modify permission on object` |
+
+Writes require modify permission on both the item **and** the prim that contains it. A no-modify
+object can be published and read, but its contents cannot be changed.
---
@@ -856,7 +1043,8 @@ interface ObjectPublishMessage {
**Fields:**
-- `object`: The full published object tree, including root prim inventory and all linked prim inventories.
+- `object`: The full object tree being explored, including root prim inventory and all linked prim inventories.
+ - `can_save_back` (optional): Capability hint for UI actions. When `true`, the object currently supports the `viewer.object.save_back_to_contents` command.
---
@@ -864,7 +1052,7 @@ interface ObjectPublishMessage {
**JSON-RPC Method:** `object.unpublish` (notification from viewer)
-Sent when the viewer removes a previously published object - for example when the owner deselects it, moves away, or the object is deleted.
+Sent when the viewer stops exploring a previously explored object — for example when the user stops exploring it from the viewer UI, the extension calls `object.unpublish`, or the object is deleted or linked into another object.
```typescript
interface ObjectUnpublishMessage {
@@ -875,12 +1063,23 @@ interface ObjectUnpublishMessage {
**Fields:**
-- `object_id`: UUID of the root prim that is being unpublished
-- `reason` (optional): Human-readable explanation (e.g. `"object deleted"`, `"out of range"`)
+- `object_id`: UUID of the root prim that is no longer being explored
+- `reason` (optional): Machine-readable token identifying why exploring stopped. One of:
+
+| Value | Meaning |
+| ----- | ------- |
+| `manual` | The extension called `object.unpublish` for this object. |
+| `republish` | The object is being re-published; a fresh `object.publish` follows. |
+| `user` | The user stopped exploring the object from the viewer UI. |
+| `deleted` | The object was deleted. |
+| `linked` | The object became a child prim of a linkset and is no longer a root. |
+
+The field is omitted only when no reason was supplied; in practice every unpublish carries one
+of the values above.
**JSON-RPC Method:** `object.unpublish` (call from extension to viewer)
-The extension may also call `object.unpublish` to manually stop tracking an object. The viewer will stop publishing it and send a corresponding `object.unpublish` notification back to the caller.
+The extension may also call `object.unpublish` to manually stop exploring an object. The viewer will stop and broadcast a corresponding `object.unpublish` notification to all connections.
```typescript
interface ObjectUnpublishParams {
@@ -895,10 +1094,10 @@ interface ObjectUnpublishResponse {
**Fields:**
-- `object_id`: UUID of the root prim to unpublish.
-- `success`: `true` if the object was published and has been removed.
+- `object_id`: UUID of the root prim to stop exploring.
+- `success`: `true` if the object was being explored and has been removed.
-**Note:** The viewer also sends an `object.unpublish` notification to the caller immediately after responding. Extensions should handle that notification idempotently.
+**Note:** The viewer also broadcasts an `object.unpublish` notification immediately after responding. The caller receives it too, so extensions should handle that notification idempotently.
---
@@ -906,43 +1105,70 @@ interface ObjectUnpublishResponse {
**JSON-RPC Method:** `object.update` (notification from viewer)
-Sent when the inventory of a published object changes. Supports two modes:
-- **Full replacement**: `inventory` and/or `linked_objects` fields replace the entire prior state.
-- **Delta update**: `changes` field describes only what changed. Takes precedence over full replacement fields when present.
+Sent when an explored object's inventory, properties, or linkset membership change.
-```typescript
-interface InventoryChanges {
- added?: ObjectInventoryItem[];
- removed?: string[]; // item_ids removed
- modified?: ObjectInventoryItem[]; // metadata-only changes
- content_changed?: string[]; // item_ids whose content changed (invalidates cache)
- running_changed?: { item_id: string; running: boolean }[]; // running state toggled
-}
+**Shapes currently emitted.** The viewer sends exactly the following five payloads:
-interface LinkedObjectChanges {
- added?: LinkedObject[];
- removed?: string[]; // link_ids removed
- modified?: {
- link_id: string;
- link_name?: string;
- inventory?: InventoryChanges;
- }[];
-}
+| Trigger | Payload |
+| ------- | ------- |
+| Root prim inventory changed | `object_id`, `inventory` (complete replacement array) |
+| Child prim inventory changed | `object_id`, `changes.linked_objects.modified[]` with `link_id` and `inventory` (complete replacement array for that child) |
+| Root prim name/description changed | `object_id`, `object_name` and/or `object_description` |
+| Child prim name/description changed | `object_id`, `changes.linked_objects.modified[]` with `link_id` and `link_name` and/or `link_description` |
+| Linkset membership changed | `object_id`, `linked_objects` (complete replacement array covering every child prim) |
+`inventory` and `linked_objects` are always complete replacements of the prior state, never
+increments. Linkset membership changes are coalesced behind a short flush delay, so several
+rapid link or unlink operations may arrive as a single update.
+
+```typescript
interface ObjectUpdateMessage {
object_id: string;
object_name?: string;
- // Full replacement (used when changes is absent)
+ object_description?: string;
+ // Full replacement
inventory?: ObjectInventoryItem[];
linked_objects?: LinkedObject[];
- // Delta (takes precedence when present)
changes?: {
- inventory?: InventoryChanges;
+ inventory?: InventoryChanges; // Not implemented — see below
linked_objects?: LinkedObjectChanges;
};
}
+
+interface LinkedObjectChanges {
+ added?: LinkedObject[]; // Not implemented — see below
+ removed?: string[]; // Not implemented — see below
+ modified?: {
+ link_id: string;
+ link_name?: string;
+ link_description?: string;
+ inventory?: ObjectInventoryItem[]; // Complete replacement array, not a delta
+ }[];
+}
+```
+
+**Note:** `changes.linked_objects.modified` is emitted, as shown in the table above.
+`changes.linked_objects.added` and `changes.linked_objects.removed` are not.
+
+#### Not implemented
+
+The delta sub-protocol below is specified but is **not currently emitted by the viewer**.
+Implementation is tracked separately. Clients must rely on the full-replacement shapes listed
+above; code written to consume these types will never run against the current viewer.
+
+```typescript
+interface InventoryChanges {
+ added?: ObjectInventoryItem[];
+ removed?: string[]; // item_ids removed
+ modified?: ObjectInventoryItem[]; // metadata-only changes
+ content_changed?: string[]; // item_ids whose content changed (invalidates cache)
+ running_changed?: { item_id: string; running: boolean }[]; // running state toggled
+}
```
+Also not implemented: `LinkedObjectChanges.added` and `LinkedObjectChanges.removed`. Linkset
+membership changes are sent as a complete `linked_objects` replacement array instead.
+
---
### ObjectContentGet
@@ -953,7 +1179,7 @@ Requests the text content of a script or notecard. The extension calls this lazi
```typescript
interface ObjectContentGetParams {
- prim_id: string; // UUID of any prim (root or child) - no object_id + link_id needed
+ prim_id: string; // UUID of any prim (root or child) — no object_id + link_id needed
item_id: string;
}
@@ -970,7 +1196,15 @@ interface ObjectContentGetResponse {
- `prim_id`: UUID of the prim that owns the item. Child prims are addressable directly by UUID without knowing the root object_id.
- `item_id`: Inventory item UUID.
- `success`: `true` on success.
-- `content`: The raw text content of the item. For notecards, the `Linden text version 2` envelope is stripped - only the body text is returned.
+- `content`: The raw text content of the item. For notecards, the `Linden text version 2` envelope is stripped — only the body text is returned.
+
+**Permissions.** Scripts require both `PERM_COPY` and `PERM_MODIFY` on the item: the source of a
+no-copy or no-modify script is never exposed. Notecards require no permission at all, so that
+no-modify notecards remain readable in the external editor. See
+[Common preconditions](#common-preconditions) for the shared checks and errors.
+
+**Timeout.** 30 seconds to fetch the asset, after which the call fails with `-32001`
+(`Asset fetch timed out`).
---
@@ -986,6 +1220,7 @@ interface ObjectContentSaveParams {
item_id: string;
content: string;
vm?: "mono" | "lsl2" | "luau";
+ running?: boolean; // Scripts only: run state applied after compilation. Defaults to false.
}
interface ObjectContentSaveResponse {
@@ -994,7 +1229,6 @@ interface ObjectContentSaveResponse {
item_id?: string;
compiled?: boolean;
diagnostics?: Diagnostic[];
- message?: string;
}
```
@@ -1004,10 +1238,19 @@ interface ObjectContentSaveResponse {
- `item_id`: UUID of the saved inventory item.
- `content`: Raw script/notecard source text to store.
- `vm` (optional): Scripts only compile target. Accepted values are `"mono"`, `"lsl2"`, `"luau"`. When `"luau"` is specified for an LSL script (as opposed to a native Luau script), the viewer automatically selects the correct LSL-on-Luau compile path. If omitted, inferred from item metadata or content analysis.
+- `running` (optional): Scripts only. The run state the viewer applies to the script once the upload and compilation complete. Defaults to `false` when omitted. To preserve a script's current run state across a save, echo the `running` value from the corresponding `ObjectInventoryItem` in the most recent `object.publish` or `object.update`.
- `success`: Whether the upload/save operation succeeded.
- `compiled` (optional): Scripts only. `true` when compilation succeeded, `false` when source saved but compile failed.
-- `diagnostics` (optional): Scripts only. Array of `Diagnostic` records when `compiled` is `false`.
-- `message` (optional): Error description on failure.
+- `diagnostics` (optional): Scripts only. Compiler diagnostics when `compiled` is `false`.
+
+> **Warning:** Omitting `running` does not leave the script's run state unchanged — it stops the script. A client that saves a running script without sending `running: true` will silently stop it.
+
+**Permissions.** Requires `PERM_MODIFY` on the item and modify permission on the containing prim.
+See [Common preconditions](#common-preconditions) for the shared checks and errors.
+
+**Timeouts.** Scripts allow 60 seconds for upload and compilation; notecards allow 30 seconds. A
+client's own timeout must exceed the longer of the two. Exceeding either fails the call with
+`-32001`.
---
@@ -1015,18 +1258,17 @@ interface ObjectContentSaveResponse {
**JSON-RPC Method:** `object.item.create` (call from extension to viewer)
-Creates a new script in a prim's inventory. The call is asynchronous - the viewer sends
-`RezScript` to the simulator and waits for the inventory-changed callback before returning
-the created item's details. The simulator may rename the item if a duplicate name exists.
-
-Notecard creation is not yet supported and will return an error.
+Creates a new script or notecard in a prim's inventory. The call is asynchronous and returns the
+created item's details once the simulator confirms the item exists. The simulator may rename the
+item if a duplicate name exists.
```typescript
interface ObjectItemCreateParams {
prim_id: string; // UUID of the prim to create the item in
- name: string; // Pure SL inventory name - no file extension
- type: InventoryItemType; // "script" ("notecard" reserved for future)
- vm: ScriptVM; // Required for scripts: "luau" | "mono" | "lsl2"
+ name: string; // Pure SL inventory name — no file extension
+ type: InventoryItemType; // "script" | "notecard"
+ vm?: ScriptVM; // Scripts only (required): "luau" | "mono" | "lsl2"
+ text?: string; // Notecards only (optional): initial body text
}
// On success, returns an ObjectInventoryItem with prim_id:
@@ -1035,12 +1277,32 @@ interface ObjectItemCreateResponse extends ObjectInventoryItem {
}
```
+**Fields:**
+
+- `prim_id`: UUID of the prim to create the item in. Child prims are addressable directly by UUID.
+- `name`: Pure SL inventory name, without a file extension.
+- `type`: `"script"` or `"notecard"`.
+- `vm`: Scripts only. Required when `type` is `"script"`; accepted values are `"luau"`, `"mono"` and `"lsl2"`. Ignored for notecards.
+- `text` (optional): Notecards only. Initial body text for the new notecard. Ignored for scripts.
+
**Notes:**
- The response matches the `ObjectInventoryItem` structure (same fields as items in
`object.publish` and `object.update` notifications).
- The `name` in the response may differ from the request if the simulator renamed it.
- An `object.update` notification will also fire for the prim (since inventory changed).
-- Timeout: 30 seconds. Returns a JSON-RPC internal error if the simulator does not respond.
+
+**Errors:**
+
+| Condition | Code | `error.message` |
+| --------- | ---- | --------------- |
+| `type` is not `"script"` or `"notecard"` | `-32602` | `Invalid params: Unsupported item type: <type>` |
+| `prim_id` missing | `-32602` | `Invalid params: prim_id is required` |
+| Prim not found | `-32602` | `Invalid params: Prim not found` |
+| `name` missing | `-32602` | `Invalid params: name is required` |
+| `vm` missing or invalid for a script | `-32602` | `Invalid params: vm must be 'luau', 'mono', or 'lsl2'` |
+| Object is not published | `-32003` | `Object is not published` |
+| Another `object.item.create` is already in flight for this prim | `-32600` | `Invalid Request: An item.create is already in flight for this prim` |
+| Simulator did not respond within 30 seconds | `-32001` | `Timed out waiting for item creation` |
---
@@ -1063,6 +1325,9 @@ interface ObjectItemDeleteResponse {
}
```
+**Permissions.** Requires `PERM_MODIFY` on the item and modify permission on the containing prim.
+See [Common preconditions](#common-preconditions) for the shared checks and errors.
+
---
### ObjectScriptSetRunning
@@ -1080,10 +1345,29 @@ interface ObjectScriptSetRunningParams {
interface ObjectScriptSetRunningResponse {
success: boolean;
- message?: string;
}
```
+**`success` means dispatched, not applied.** The viewer sends a message to the simulator and
+returns immediately. `success: true` confirms the message was sent — it does not confirm the
+simulator started or stopped the script. Confirmation, if it arrives, comes later as an
+`object.update` notification. Treat the response as an acknowledgement and wait for the update
+before reporting the new run state to the user.
+
+**Permissions.** Requires `PERM_MODIFY` on the script. This method validates independently of the
+shared item validator and accepts scripts only.
+
+**Errors:**
+
+| Condition | Code | `error.message` |
+| --------- | ---- | --------------- |
+| `prim_id` or `item_id` missing | `-32602` | `Invalid params: prim_id and item_id are required` |
+| Prim not found | `-32602` | `Invalid params: Prim not found` |
+| Object is not published | `-32003` | `Object is not published` |
+| Script not in the prim's inventory | `-32602` | `Invalid params: Script not found in prim inventory` |
+| Item is not a script | `-32602` | `Invalid params: Item is not a script` |
+| No modify permission on the script | `-32003` | `No modify permission on script` |
+
---
### ObjectScriptReset
@@ -1100,10 +1384,18 @@ interface ObjectScriptResetParams {
interface ObjectScriptResetResponse {
success: boolean;
- message?: string;
}
```
+**`success` means dispatched, not applied.** As with `object.script.set_running`, the viewer sends
+a message to the simulator and returns immediately. `success: true` does not confirm the script
+was reset.
+
+**Permissions.** Requires `PERM_MODIFY` on the script. This method validates independently of the
+shared item validator and accepts scripts only.
+
+**Errors:** identical to `object.script.set_running` above.
+
---
### ObjectRequest
@@ -1116,20 +1408,29 @@ This is typically called immediately after the handshake completes when the exte
```typescript
interface ObjectRequestParams {
- object_id: string; // UUID of the root prim to request publishing for
+ object_id: string; // UUID of the root prim to request exploring
}
interface ObjectRequestResponse {
success: boolean;
- message?: string; // reason on failure (e.g. "object not found", "permission denied")
}
```
**Fields:**
-- `object_id`: UUID of the root prim of the linkset to publish.
-- `success`: Whether the viewer accepted the request. A `true` response does not mean `object.publish` has been sent yet - it means the viewer will send it.
-- `message` (optional): Human-readable failure reason. Only present when `success` is `false`.
+- `object_id`: UUID of the root prim of the linkset to explore.
+- `success`: Whether the viewer accepted the request. A `true` response does not mean `object.publish` has been sent yet — it means the viewer will send it. Failures are returned as JSON-RPC errors — see below.
+
+**Permissions.** Requires modify permission on the object.
+
+**Errors:**
+
+| Condition | Code | `error.message` |
+| --------- | ---- | --------------- |
+| `object_id` missing | `-32602` | `Invalid params: No object_id specified` |
+| Object not found | `-32602` | `Invalid params: Object not found` |
+| No modify permission on the object | `-32003` | `Permission denied` |
+| Publish could not be started | `-32603` | `Internal error: Failed to initiate publish` |
**Sequence:**
1. Extension calls `object.request`
@@ -1142,21 +1443,21 @@ interface ObjectRequestResponse {
**JSON-RPC Method:** `object.list` (call from extension to viewer)
-Requests the complete list of currently published objects. Called by the extension immediately after the handshake completes (`session.ok`) to restore state for any objects the viewer already has published.
+Requests the complete list of currently explored objects. Called by the extension immediately after the handshake completes (`session.ok`) to restore state for any objects the viewer already has open for exploration.
-The viewer responds synchronously with all published objects in the same format as `object.publish` notifications. No follow-up notifications are sent.
+The viewer responds synchronously with all explored objects in the same format as `object.publish` notifications. No follow-up notifications are sent.
```typescript
// No request parameters
interface ObjectListResponse {
- objects: PublishedObject[]; // All currently published objects; empty array if none
+ objects: PublishedObject[]; // All currently explored objects; empty array if none
}
```
**Fields:**
-- `objects`: Array of `PublishedObject` records (same shape as the `object` field in `object.publish`). Empty array when no objects are currently published.
+- `objects`: Array of `PublishedObject` records (same shape as the `object` field in `object.publish`). Empty array when no objects are currently being explored.
**Sequence:**
1. Viewer sends `session.ok`
@@ -1184,7 +1485,6 @@ interface ObjectModifyParams {
interface ObjectModifyResponse {
success: boolean;
prim_id: string; // Echoed back from request
- message?: string; // Error description on failure
}
```
@@ -1195,8 +1495,24 @@ interface ObjectModifyResponse {
- `description` (optional): New description for the prim. If omitted, description remains unchanged.
- `permissions` (optional): Permission changes.
- `next_owner`: Permission mask applied when the object is transferred. Uses same bit flags as `ItemPermissions` (e.g., `PERM_MODIFY=0x4000`, `PERM_COPY=0x8000`, `PERM_TRANSFER=0x2000`).
-- `success`: Whether the update operation succeeded.
-- `message` (optional): Error description. Only present when `success` is `false`.
+- `success`: Whether the property messages were dispatched.
+
+**`success` means dispatched, not applied.** Each supplied property is sent to the simulator as a
+separate message and the viewer returns immediately. `success: true` confirms the messages were
+sent, not that any of them took effect — and because they travel independently, one may be applied
+while another is not. Confirmation arrives later as an `object.update` notification.
+
+**Permissions.** Requires modify permission on the prim.
+
+**Errors:**
+
+| Condition | Code | `error.message` |
+| --------- | ---- | --------------- |
+| `prim_id` missing | `-32602` | `Invalid params: prim_id is required` |
+| No property supplied | `-32602` | `Invalid params: At least one property (name, description, or permissions) must be specified` |
+| Prim not found | `-32602` | `Invalid params: Prim not found` |
+| Object is not published | `-32003` | `Object is not published` |
+| No modify permission on the prim | `-32003` | `No modify permission on object` |
**Notes:**
- At least one property field (`name`, `description`, or `permissions`) must be specified.
@@ -1226,7 +1542,6 @@ interface ObjectItemModifyResponse {
success: boolean;
prim_id: string; // Echoed back from request
item_id: string; // Echoed back from request
- message?: string; // Error description on failure
}
```
@@ -1238,8 +1553,16 @@ interface ObjectItemModifyResponse {
- `description` (optional): New description for the item. If omitted, description remains unchanged.
- `permissions` (optional): Permission changes.
- `next_owner`: Permission mask applied when the item is transferred. Uses same bit flags as `ItemPermissions` (e.g., `PERM_MODIFY=0x4000`, `PERM_COPY=0x8000`, `PERM_TRANSFER=0x2000`).
-- `success`: Whether the update operation succeeded.
-- `message` (optional): Error description. Only present when `success` is `false`.
+- `success`: Whether the property messages were dispatched.
+
+**`success` means dispatched, not applied.** The viewer sends the change to the simulator and
+returns immediately; confirmation arrives later as an `object.update` notification.
+
+**Permissions.** Requires `PERM_MODIFY` on the item and modify permission on the containing prim.
+See [Common preconditions](#common-preconditions) for the shared checks and errors.
+
+**Errors:** as listed under [Common preconditions](#common-preconditions), plus `-32602` when no
+property field is supplied.
**Notes:**
- At least one property field (`name`, `description`, or `permissions`) must be specified.
@@ -1268,29 +1591,35 @@ interface CommandExecuteParams {
interface CommandExecuteResponse {
success: boolean;
result?: unknown; // optional command-specific return value
- error_code?: number;
- message?: string;
}
```
**Fields:**
- `command`: Namespaced command identifier. The prefix before the first `.` identifies the side that owns and executes the command:
- - `viewer.*` - commands executed by the viewer (e.g. `viewer.teleport`, `viewer.script.recompile_all`)
- - `editor.*` - commands executed by the extension (e.g. `editor.open_file`, `editor.show_message`)
+ - `viewer.*` — commands executed by the viewer (e.g. `viewer.teleport`, `viewer.camera.focus`)
+ - `editor.*` — commands executed by the extension (e.g. `editor.open_file`, `editor.show_message`)
- `params` (optional): Command-specific argument map. Structure varies by command.
-- `success`: Whether the command was found and executed without error.
-- `result` (optional): Command-specific return value. Only present when `success` is `true` and the command produces output.
-- `error_code` (optional): Numeric failure code. Only present when `success` is `false`:
- - `1` - Unknown command
- - `2` - Invalid or missing parameters
- - `3` - Not permitted
- - `4` - Execution error
-- `message` (optional): Human-readable error description. Only present when `success` is `false`.
+- `success`: `true` when the command executed. Failures are returned as JSON-RPC errors — see below.
+- `result` (optional): Command-specific return value. Only present when the command produces output.
+
+**Errors:**
-**Capability gate:** A side MUST NOT send `command.execute` unless the peer advertised `commands: true` in the handshake. A receiver that receives the call without having negotiated the feature MUST respond with `success: false, error_code: 1`.
+| Condition | Code | `error.message` |
+| --------- | ---- | --------------- |
+| `command` missing | `-32602` | `Invalid params: command is required` |
+| Command not registered | `-32602` | `Invalid params: Unknown command: <command>` |
-**Example - extension asks viewer to teleport:**
+The invoked command's own handler may raise further errors — `-32602` for bad arguments,
+`-32003` when the action is not permitted, `-32603` on internal failure. Clients must handle any
+error code, not only the two above.
+
+**Capability gate:** A side MUST NOT send `command.execute` unless the peer advertised
+`commands: true` in the handshake. A receiver that receives the call without having negotiated the
+feature should respond with a JSON-RPC error. **Not currently enforced on receive by the viewer**
+— the gate is applied only when sending. Implementation is tracked separately.
+
+**Example — extension asks viewer to teleport:**
```json
{
@@ -1299,7 +1628,7 @@ interface CommandExecuteResponse {
"id": 7,
"params": {
"command": "viewer.teleport",
- "params": { "region": "Aditi", "position": [128, 128, 25] }
+ "params": { "object_id": "550e8400-e29b-41d4-a716-446655440000" }
}
}
```
@@ -1312,7 +1641,29 @@ interface CommandExecuteResponse {
}
```
-**Example - viewer asks extension to show a message:**
+**Example — extension asks viewer to save object back to contents:**
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "command.execute",
+ "id": 9,
+ "params": {
+ "command": "viewer.object.save_back_to_contents",
+ "params": { "object_id": "550e8400-e29b-41d4-a716-446655440000" }
+ }
+}
+```
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 9,
+ "result": { "success": true, "result": { "object_id": "550e8400-e29b-41d4-a716-446655440000" } }
+}
+```
+
+**Example — viewer asks extension to show a message:**
```json
{
@@ -1361,20 +1712,21 @@ interface CommandParamInfo {
- `commands`: Array of commands the responder supports. Each entry describes one command.
- `command`: The namespaced command identifier.
- `description` (optional): Human-readable description of what the command does.
- - `params` (optional): Map of parameter names to their type descriptors.
+ - `params` (optional): Map of parameter names to their type descriptors. **Not currently
+ populated** — the viewer returns only `command` and `description`, so parameter discovery
+ does not work. Implementation is tracked separately.
**Known viewer commands:**
| Command | Required params | Description |
|---------|----------------|-------------|
-| `viewer.teleport` | `region: string` | Teleport agent to a region. Optional `position: [x, y, z]`. |
-| `viewer.script.recompile_all` | `object_id: string` | Recompile all scripts in an object. |
-| `viewer.script.reset_all` | `object_id: string` | Reset all scripts in an object. |
-| `viewer.camera.focus` | `object_id: string` | Move camera focus to an in-world object. |
+| `viewer.teleport` | `object_id: string` | Teleport agent to an in-world object. |
+| `viewer.camera.focus` | `object_id: string` | Zoom camera to an in-world object (same behavior as context menu Zoom In). |
+| `viewer.object.save_back_to_contents` | `object_id: string` | Save an in-world object back to source object contents. |
**Known extension commands:**
| Command | Required params | Description |
|---------|----------------|-------------|
| `editor.open_file` | `path: string` | Open a file in the editor. Optional `line: number`. |
-| `editor.show_message` | `message: string` | Show a notification. Optional `level: "info" | "warn" | "error"`. |
+| `editor.show_message` | `message: string` | Show a notification. Optional `level: "info" \| "warn" \| "error"`. |