No description
  • TypeScript 100%
Find a file
2026-04-17 22:08:58 -03:00
src feat: add reconnection, result queue, and state persistence 2026-04-17 19:36:32 -03:00
tests feat: add reconnection, result queue, and state persistence 2026-04-17 19:36:32 -03:00
.gitignore Initial implementation of remote-node-server 2026-04-17 01:24:53 -03:00
package-lock.json Updating package-lock.json 2026-04-17 22:08:58 -03:00
package.json Initial implementation of remote-node-server 2026-04-17 01:24:53 -03:00
README.md feat: add reconnection, result queue, and state persistence 2026-04-17 19:36:32 -03:00
tsconfig.json Initial implementation of remote-node-server 2026-04-17 01:24:53 -03:00

Remote Node Server

A standalone Node.js server that runs on a remote machine and executes bash commands on behalf of a Fractal Synapse agent. The agent connects to it over WebSocket via the remote-nodes-plugin, sends signed execute messages, and the server validates the auth token, checks each command against a local security-permissions.json using the @fractal-synapse/permissions-engine package, and either runs the command or returns a denial.

Requirements

  • Node.js 18 or later
  • npm install to install dependencies
  • npm run build to compile TypeScript to dist/

The @fractal-synapse/permissions-engine dependency must be built first (npm run build in packages/permissions-engine).

First Run

node dist/index.js

On first run the server creates a config.json in the working directory and prints the generated auth token to the console:

=== REMOTE NODE SERVER — FIRST RUN ===
Config file created at: /path/to/config.json
Auth token (copy this into your app settings):

  a1b2c3d4...64-char-hex-token

======================================

Copy the auth token into your app's remote node settings so the plugin can authenticate.

Configuration

config.json is created automatically on first run and can be edited afterward.

Field Default Description
port 7331 Port the WebSocket server listens on
authToken auto-generated (64 hex chars) Authentication token sent by the plugin as Authorization: Bearer <token>. Regenerated if missing
permissionsFile security-permissions.json Path to the permissions file. Relative to the config file directory, or absolute

Use the --config flag to point to a config file in a different location:

node dist/index.js --config /path/to/config.json

Permissions

Commands are checked against security-permissions.json before execution. The file defines which commands are allowed, denied, or require human approval.

See packages/permissions-engine/README.md for the full permissions format reference.

Hot reload: Edit the permissions file while the server is running. Changes are picked up automatically within approximately 2 seconds. If the file contains invalid JSON the reload fails and the server continues using the previous rules.

Running

# Install dependencies (permissions-engine must be built first)
npm install

# Build TypeScript
npm run build

# Start the server
node dist/index.js

# Start with a custom config location
node dist/index.js --config /path/to/config.json

Protocol

The server communicates over a single persistent WebSocket connection using JSON messages.

Plugin to Server (PluginToServer)

type Fields Description
execute commandId, command Request execution of a shell command
permission-response commandId, decision Respond to a permission-required prompt (V1 stub -- not yet active)
heartbeat (none) Keep-alive ping; server replies with heartbeat-ack

Server to Plugin (ServerToPlugin)

type Fields Description
result commandId, output, exitCode Command completed successfully
error commandId, message Runtime error during command execution
permission-required commandId, command, ruleDescription Command needs human approval (designed but not active in V1)
permission-denied commandId, reason Command was denied by the security policy
heartbeat-ack (none) Reply to a heartbeat ping

V1 Limitations

  • ask rules are treated as deny. If a command matches a rule with "permission": "ask", the server logs a warning and returns permission-denied with a message indicating V2 escalation is required. There is no mechanism to prompt for approval in V1.
  • permission-required / permission-response round-trip is not active. The protocol includes these message types for future use, but V1 never sends permission-required. The server accepts permission-response messages from the plugin but ignores them.

Reliability

  • No process timeout. Commands run until they exit naturally. There is no artificial time limit.
  • Result queue and reconnection delivery. If the client disconnects while a command is running, the result is queued server-side and delivered automatically when the client reconnects. On reconnect, all queued results are flushed immediately.
  • Duplicate command guard. If the same commandId is received while the command is already running, the second request is silently ignored. This prevents duplicate execution when a plugin reconnects and re-sends an in-flight command.
  • State persistence. A server-state.json file (stored alongside config.json) persists running commands and queued results to disk. On server restart, any commands that were running get an error result queued immediately (they were killed by the restart). Undelivered results survive restarts and are flushed on reconnect.

Security Notes

  • Run the server behind Tailscale or on a private LAN. The server does not implement TLS itself.
  • The auth token is application-level security. It prevents unauthorized WebSocket connections but is not a substitute for network-level isolation.
  • Do not expose the server port to the public internet. There is no rate limiting, account lockout, or encrypted transport.